mirror of
https://github.com/Noratrieb/riverdelta.git
synced 2026-01-14 08:25:02 +01:00
Imports
This commit is contained in:
parent
50e82066c9
commit
6d2a2fe474
9 changed files with 443 additions and 98 deletions
|
|
@ -24,6 +24,7 @@
|
|||
nodejs-18_x # Node.js 18, plus npm, npx, and corepack
|
||||
wasmtime
|
||||
wasm-tools
|
||||
binaryen
|
||||
];
|
||||
};
|
||||
});
|
||||
|
|
|
|||
25
opt.wat
Normal file
25
opt.wat
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
(module
|
||||
(type (;0;) (func (param i32 i32 i32 i32) (result i32)))
|
||||
(type (;1;) (func))
|
||||
(import "wasi_snapshot_preview1" "fd_write" (func (;0;) (type 0)))
|
||||
(func (;1;) (type 1)
|
||||
i32.const 1028
|
||||
i32.const 0
|
||||
i32.store
|
||||
i32.const 1032
|
||||
i32.const 2
|
||||
i32.store
|
||||
i32.const 1
|
||||
i32.const 1028
|
||||
i32.const 2
|
||||
i32.const 1024
|
||||
call 0
|
||||
drop
|
||||
)
|
||||
(table (;0;) 0 0 funcref)
|
||||
(memory (;0;) 65536 65536)
|
||||
(export "memory" (memory 0))
|
||||
(export "__indirect_function_table" (table 0))
|
||||
(export "_start" (func 1))
|
||||
(data (;0;) (i32.const 0) "a\0a")
|
||||
)
|
||||
57
src/ast.ts
57
src/ast.ts
|
|
@ -1,4 +1,5 @@
|
|||
import { Span } from "./error";
|
||||
import { LitIntType } from "./lexer";
|
||||
|
||||
export type Ast = { items: Item[]; typeckResults?: TypeckResults };
|
||||
|
||||
|
|
@ -18,6 +19,10 @@ export type ItemKind =
|
|||
| {
|
||||
kind: "type";
|
||||
node: TypeDef;
|
||||
}
|
||||
| {
|
||||
kind: "import";
|
||||
node: ImportDef;
|
||||
};
|
||||
|
||||
export type Item = ItemKind & {
|
||||
|
|
@ -50,6 +55,15 @@ export type FieldDef = {
|
|||
type: Type;
|
||||
};
|
||||
|
||||
export type ImportDef = {
|
||||
module: StringLiteral;
|
||||
func: StringLiteral;
|
||||
name: string;
|
||||
params: FunctionArg[];
|
||||
returnType?: Type;
|
||||
ty?: TyFn;
|
||||
};
|
||||
|
||||
export type ExprEmpty = { kind: "empty" };
|
||||
|
||||
export type ExprLet = {
|
||||
|
|
@ -143,14 +157,18 @@ export type Expr = ExprKind & {
|
|||
ty?: Ty;
|
||||
};
|
||||
|
||||
export type StringLiteral = {
|
||||
kind: "str";
|
||||
value: string;
|
||||
span: Span;
|
||||
};
|
||||
|
||||
export type Literal =
|
||||
| {
|
||||
kind: "str";
|
||||
value: string;
|
||||
}
|
||||
| StringLiteral
|
||||
| {
|
||||
kind: "int";
|
||||
value: number;
|
||||
type: LitIntType;
|
||||
};
|
||||
|
||||
export type BinaryKind =
|
||||
|
|
@ -265,6 +283,13 @@ export const BUILTINS = [
|
|||
"Bool",
|
||||
"true",
|
||||
"false",
|
||||
// Intrinsics:
|
||||
"__i32_store",
|
||||
"__i64_store",
|
||||
"__i32_load",
|
||||
"__i64_load",
|
||||
"__string_ptr",
|
||||
"__string_len",
|
||||
] as const;
|
||||
|
||||
export type BuiltinName = (typeof BUILTINS)[number];
|
||||
|
|
@ -399,15 +424,14 @@ export function superFoldItem(item: Item, folder: Folder): Item {
|
|||
}));
|
||||
|
||||
return {
|
||||
...item,
|
||||
kind: "function",
|
||||
span: item.span,
|
||||
node: {
|
||||
name: item.node.name,
|
||||
params: args,
|
||||
body: folder.expr(item.node.body),
|
||||
returnType: item.node.returnType && folder.type(item.node.returnType),
|
||||
},
|
||||
id: item.id,
|
||||
};
|
||||
}
|
||||
case "type": {
|
||||
|
|
@ -417,10 +441,27 @@ export function superFoldItem(item: Item, folder: Folder): Item {
|
|||
}));
|
||||
|
||||
return {
|
||||
...item,
|
||||
kind: "type",
|
||||
span: item.span,
|
||||
node: { name: item.node.name, fields },
|
||||
id: item.id,
|
||||
};
|
||||
}
|
||||
case "import": {
|
||||
const args = item.node.params.map(({ name, type, span }) => ({
|
||||
name,
|
||||
type: folder.type(type),
|
||||
span,
|
||||
}));
|
||||
return {
|
||||
...item,
|
||||
kind: "import",
|
||||
node: {
|
||||
module: item.node.module,
|
||||
func: item.node.func,
|
||||
name: item.node.name,
|
||||
params: args,
|
||||
returnType: item.node.returnType && folder.type(item.node.returnType),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
34
src/index.ts
34
src/index.ts
|
|
@ -10,20 +10,32 @@ import fs from "fs";
|
|||
import { exec } from "child_process";
|
||||
|
||||
const input = `
|
||||
// import "wasi_snapshot_preview1" "fd_write"(a: I32, b: I32, c: I32, d: I32): I32;
|
||||
import("wasi_snapshot_preview1" "fd_write")
|
||||
fd_write(a: I32, b: I32, c: I32, d: I32): I32;
|
||||
|
||||
function main() = (
|
||||
loop (no(break););
|
||||
uwu(10);
|
||||
function coolerPrint(a: String) = (
|
||||
let ptr = __string_ptr(a);
|
||||
let len = __string_len(a);
|
||||
|
||||
let mem = 1024_I32;
|
||||
|
||||
__i32_store(mem + 4_I32, ptr);
|
||||
__i32_store(mem + 8_I32, 2_I32);
|
||||
|
||||
fd_write(
|
||||
// stdout
|
||||
1_I32,
|
||||
// iovec
|
||||
mem + 4_I32,
|
||||
// len
|
||||
len,
|
||||
// return value
|
||||
mem,
|
||||
);
|
||||
);
|
||||
|
||||
function meow(a: I32): I32 = a;
|
||||
|
||||
function no(a: !): String = a;
|
||||
|
||||
function uwu(a: Int) = if a != 0 then (
|
||||
print("uwu\n");
|
||||
uwu(a - 1);
|
||||
function main() = (
|
||||
coolerPrint("uwu\\n");
|
||||
);
|
||||
`;
|
||||
|
||||
|
|
|
|||
35
src/lexer.ts
35
src/lexer.ts
|
|
@ -9,6 +9,7 @@ export type DatalessToken =
|
|||
| "type"
|
||||
| "loop"
|
||||
| "break"
|
||||
| "import"
|
||||
| "("
|
||||
| ")"
|
||||
| "{"
|
||||
|
|
@ -36,14 +37,19 @@ export type DatalessToken =
|
|||
|
||||
export type TokenIdent = { kind: "identifier"; ident: string };
|
||||
|
||||
export type TokenLitString = {
|
||||
kind: "lit_string";
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type LitIntType = "Int" | "I32";
|
||||
|
||||
export type TokenLit =
|
||||
| {
|
||||
kind: "lit_string";
|
||||
value: string;
|
||||
}
|
||||
| TokenLitString
|
||||
| {
|
||||
kind: "lit_int";
|
||||
value: number;
|
||||
type: LitIntType;
|
||||
};
|
||||
|
||||
export type TokenKind = { kind: DatalessToken } | TokenIdent | TokenLit;
|
||||
|
|
@ -80,13 +86,13 @@ export function tokenize(input: string): Token[] {
|
|||
const next = input[i];
|
||||
const span: Span = { start: i, end: i + 1 };
|
||||
|
||||
if (next === "/" && input[i + 1] === "/") {
|
||||
if (next === "/" && input[i + 1] === "/") {
|
||||
while (input[i] !== "\n") {
|
||||
i++;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (SINGLE_PUNCT.includes(next)) {
|
||||
tokens.push({ kind: next as DatalessToken, span });
|
||||
|
|
@ -207,7 +213,21 @@ export function tokenize(input: string): Token[] {
|
|||
);
|
||||
}
|
||||
|
||||
tokens.push({ kind: "lit_int", value: int, span });
|
||||
let type: LitIntType = "Int";
|
||||
console.log(input[i + 2]);
|
||||
if (input[i + 1] === "_" && isIdentStart(input[i + 2])) {
|
||||
console.log("yes", input.slice(i+2, i+5));
|
||||
|
||||
if (input.slice(i+2, i+5) === "Int") {
|
||||
i += 4;
|
||||
type = "Int";
|
||||
} else if (input.slice(i+2, i+5) === "I32") {
|
||||
i += 4;
|
||||
type = "I32";
|
||||
}
|
||||
}
|
||||
|
||||
tokens.push({ kind: "lit_int", value: int, span, type });
|
||||
} else if (isIdentStart(next)) {
|
||||
while (isIdentContinue(input[i + 1])) {
|
||||
span.end++;
|
||||
|
|
@ -267,6 +287,7 @@ const KEYOWRDS: DatalessToken[] = [
|
|||
"type",
|
||||
"loop",
|
||||
"break",
|
||||
"import",
|
||||
];
|
||||
|
||||
const KEYWORD_SET = new Set<string>(KEYOWRDS);
|
||||
|
|
|
|||
168
src/lower.ts
168
src/lower.ts
|
|
@ -3,6 +3,7 @@ import {
|
|||
Expr,
|
||||
ExprBlock,
|
||||
FunctionDef,
|
||||
ImportDef,
|
||||
Item,
|
||||
Resolution,
|
||||
Ty,
|
||||
|
|
@ -12,8 +13,6 @@ import {
|
|||
import { encodeUtf8 } from "./utils";
|
||||
import * as wasm from "./wasm/defs";
|
||||
|
||||
type StringifiedForMap<T> = string;
|
||||
|
||||
const USIZE: wasm.ValType = "i32";
|
||||
// POINTERS ARE JUST INTEGERS
|
||||
const POINTER: wasm.ValType = USIZE;
|
||||
|
|
@ -28,22 +27,25 @@ type Relocation = {
|
|||
instr: wasm.Instr & { func: wasm.FuncIdx };
|
||||
} & { res: Resolution };
|
||||
|
||||
function setMap<K, V>(map: Map<StringifiedForMap<K>, V>, key: K, value: V) {
|
||||
map.set(JSON.stringify(key), value);
|
||||
type StringifiedMap<K, V> = { _map: Map<string, V> };
|
||||
|
||||
function setMap<K, V>(map: StringifiedMap<K, V>, key: K, value: V) {
|
||||
map._map.set(JSON.stringify(key), value);
|
||||
}
|
||||
|
||||
function getMap<K, V>(
|
||||
map: Map<StringifiedForMap<K>, V>,
|
||||
key: K
|
||||
): V | undefined {
|
||||
return map.get(JSON.stringify(key));
|
||||
function getMap<K, V>(map: StringifiedMap<K, V>, key: K): V | undefined {
|
||||
return map._map.get(JSON.stringify(key));
|
||||
}
|
||||
|
||||
type FuncOrImport =
|
||||
| { kind: "func"; idx: wasm.FuncIdx }
|
||||
| { kind: "import"; idx: number };
|
||||
|
||||
export type Context = {
|
||||
mod: wasm.Module;
|
||||
funcTypes: Map<StringifiedForMap<wasm.FuncType>, wasm.TypeIdx>;
|
||||
funcTypes: StringifiedMap<wasm.FuncType, wasm.TypeIdx>;
|
||||
reservedHeapMemoryStart: number;
|
||||
funcIndices: Map<StringifiedForMap<Resolution>, wasm.FuncIdx>;
|
||||
funcIndices: StringifiedMap<Resolution, FuncOrImport>;
|
||||
ast: Ast;
|
||||
relocations: Relocation[];
|
||||
};
|
||||
|
|
@ -117,8 +119,8 @@ export function lower(ast: Ast): wasm.Module {
|
|||
|
||||
const cx: Context = {
|
||||
mod,
|
||||
funcTypes: new Map(),
|
||||
funcIndices: new Map(),
|
||||
funcTypes: { _map: new Map() },
|
||||
funcIndices: { _map: new Map() },
|
||||
reservedHeapMemoryStart: 0,
|
||||
ast,
|
||||
relocations: [],
|
||||
|
|
@ -128,6 +130,11 @@ export function lower(ast: Ast): wasm.Module {
|
|||
switch (item.kind) {
|
||||
case "function": {
|
||||
lowerFunc(cx, item, item.node);
|
||||
break;
|
||||
}
|
||||
case "import": {
|
||||
lowerImport(cx, item, item.node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -145,13 +152,13 @@ export function lower(ast: Ast): wasm.Module {
|
|||
cx.relocations.forEach((rel) => {
|
||||
switch (rel.kind) {
|
||||
case "funccall": {
|
||||
const idx = getMap<Resolution, number>(cx.funcIndices, rel.res);
|
||||
const idx = getMap<Resolution, FuncOrImport>(cx.funcIndices, rel.res);
|
||||
if (idx === undefined) {
|
||||
throw new Error(
|
||||
`no function found for relocation '${JSON.stringify(rel.res)}'`
|
||||
);
|
||||
}
|
||||
rel.instr.func = offset + idx;
|
||||
rel.instr.func = idx.kind === "func" ? offset + idx.idx : idx.idx;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -160,6 +167,37 @@ export function lower(ast: Ast): wasm.Module {
|
|||
return mod;
|
||||
}
|
||||
|
||||
function lowerImport(cx: Context, item: Item, def: ImportDef) {
|
||||
const existing = cx.mod.imports.findIndex(
|
||||
(imp) => imp.module === def.module.value && imp.name === def.func.value
|
||||
);
|
||||
|
||||
let idx;
|
||||
if (existing !== -1) {
|
||||
idx = existing;
|
||||
} else {
|
||||
const abi = computeAbi(def.ty!);
|
||||
const { type: wasmType } = wasmTypeForAbi(abi);
|
||||
const type = internFuncType(cx, wasmType);
|
||||
|
||||
idx = cx.mod.imports.length;
|
||||
cx.mod.imports.push({
|
||||
module: def.module.value,
|
||||
name: def.func.value,
|
||||
desc: {
|
||||
kind: "func",
|
||||
type,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setMap<Resolution, FuncOrImport>(
|
||||
cx.funcIndices,
|
||||
{ kind: "item", index: item.id },
|
||||
{ kind: "import", idx }
|
||||
);
|
||||
}
|
||||
|
||||
type FuncContext = {
|
||||
cx: Context;
|
||||
item: Item;
|
||||
|
|
@ -198,10 +236,10 @@ function lowerFunc(cx: Context, item: Item, func: FunctionDef) {
|
|||
|
||||
const idx = fcx.cx.mod.funcs.length;
|
||||
fcx.cx.mod.funcs.push(wasmFunc);
|
||||
setMap<Resolution, number>(
|
||||
setMap<Resolution, FuncOrImport>(
|
||||
fcx.cx.funcIndices,
|
||||
{ kind: "item", index: fcx.item.id },
|
||||
idx
|
||||
{ kind: "func", idx }
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -213,7 +251,7 @@ Expression lowering.
|
|||
function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
|
||||
const ty = expr.ty!;
|
||||
|
||||
switch (expr.kind) {
|
||||
exprKind: switch (expr.kind) {
|
||||
case "empty": {
|
||||
// A ZST, do nothing.
|
||||
return;
|
||||
|
|
@ -265,7 +303,14 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
|
|||
|
||||
break;
|
||||
case "int":
|
||||
instrs.push({ kind: "i64.const", imm: expr.value.value });
|
||||
switch (expr.value.type) {
|
||||
case "Int":
|
||||
instrs.push({ kind: "i64.const", imm: expr.value.value });
|
||||
break;
|
||||
case "I32":
|
||||
instrs.push({ kind: "i32.const", imm: expr.value.value });
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
|
@ -306,49 +351,55 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
|
|||
lowerExpr(fcx, instrs, expr.lhs);
|
||||
lowerExpr(fcx, instrs, expr.rhs);
|
||||
|
||||
if (expr.lhs.ty!.kind === "int" && expr.rhs.ty!.kind === "int") {
|
||||
const lhsTy = expr.lhs.ty!;
|
||||
const rhsTy = expr.rhs.ty!;
|
||||
if (
|
||||
(lhsTy.kind === "int" && rhsTy.kind === "int") ||
|
||||
(lhsTy.kind === "i32" && rhsTy.kind === "i32")
|
||||
) {
|
||||
let kind: wasm.Instr["kind"];
|
||||
const valty = lhsTy.kind === "int" ? "i64" : "i32";
|
||||
switch (expr.binaryKind) {
|
||||
case "+":
|
||||
kind = "i64.add";
|
||||
kind = `${valty}.add`;
|
||||
break;
|
||||
case "-":
|
||||
kind = "i64.sub";
|
||||
kind = `${valty}.sub`;
|
||||
break;
|
||||
case "*":
|
||||
kind = "i64.mul";
|
||||
kind = `${valty}.mul`;
|
||||
break;
|
||||
case "/":
|
||||
kind = "i64.div_u";
|
||||
kind = `${valty}.div_u`;
|
||||
break;
|
||||
case "&":
|
||||
kind = "i64.and";
|
||||
kind = `${valty}.and`;
|
||||
break;
|
||||
case "|":
|
||||
kind = "i64.or";
|
||||
kind = `${valty}.or`;
|
||||
break;
|
||||
case "<":
|
||||
kind = "i64.lt_u";
|
||||
kind = `${valty}.lt_u`;
|
||||
break;
|
||||
case ">":
|
||||
kind = "i64.gt_u";
|
||||
kind = `${valty}.gt_u`;
|
||||
// errs
|
||||
break;
|
||||
case "==":
|
||||
kind = "i64.eq";
|
||||
kind = `${valty}.eq`;
|
||||
break;
|
||||
case "<=":
|
||||
kind = "i64.le_u";
|
||||
kind = `${valty}.le_u`;
|
||||
break;
|
||||
case ">=":
|
||||
kind = "i64.ge_u";
|
||||
kind = `${valty}.ge_u`;
|
||||
break;
|
||||
case "!=":
|
||||
kind = "i64.ne";
|
||||
kind = `${valty}.ne`;
|
||||
break;
|
||||
}
|
||||
instrs.push({ kind });
|
||||
} else if (expr.lhs.ty!.kind === "bool" && expr.rhs.ty!.kind === "bool") {
|
||||
} else if (lhsTy.kind === "bool" && rhsTy.kind === "bool") {
|
||||
let kind: wasm.Instr["kind"];
|
||||
|
||||
switch (expr.binaryKind) {
|
||||
|
|
@ -404,6 +455,50 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
|
|||
if (expr.lhs.kind !== "ident") {
|
||||
todo("non constant calls");
|
||||
}
|
||||
|
||||
if (expr.lhs.value.res!.kind === "builtin") {
|
||||
switch (expr.lhs.value.res!.name) {
|
||||
case "__i32_load": {
|
||||
lowerExpr(fcx, instrs, expr.args[0]);
|
||||
instrs.push({ kind: "i64.load", imm: {} });
|
||||
break exprKind;
|
||||
}
|
||||
case "__i64_load": {
|
||||
lowerExpr(fcx, instrs, expr.args[0]);
|
||||
instrs.push({ kind: "i64.load", imm: {} });
|
||||
break exprKind;
|
||||
}
|
||||
case "__i32_store": {
|
||||
lowerExpr(fcx, instrs, expr.args[0]);
|
||||
lowerExpr(fcx, instrs, expr.args[1]);
|
||||
instrs.push({ kind: "i32.store", imm: {} });
|
||||
break exprKind;
|
||||
}
|
||||
case "__i64_store": {
|
||||
lowerExpr(fcx, instrs, expr.args[0]);
|
||||
lowerExpr(fcx, instrs, expr.args[1]);
|
||||
instrs.push({ kind: "i64.store", imm: {} });
|
||||
break exprKind;
|
||||
}
|
||||
case "__string_ptr": {
|
||||
lowerExpr(fcx, instrs, expr.args[0]);
|
||||
// ptr, len
|
||||
instrs.push({ kind: "drop" });
|
||||
// ptr
|
||||
break exprKind;
|
||||
}
|
||||
case "__string_len": {
|
||||
lowerExpr(fcx, instrs, expr.args[0]);
|
||||
// ptr, len
|
||||
instrs.push({ kind: "i32.const", imm: 0 });
|
||||
// ptr, len, 0
|
||||
instrs.push({ kind: "select" });
|
||||
// len
|
||||
break exprKind;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const callInstr: wasm.Instr = { kind: "call", func: 9999999999 };
|
||||
fcx.cx.relocations.push({
|
||||
kind: "funccall",
|
||||
|
|
@ -676,9 +771,10 @@ function addRt(cx: Context, ast: Ast) {
|
|||
const printIdx = cx.mod.funcs.length;
|
||||
cx.mod.funcs.push(print);
|
||||
|
||||
cx.funcIndices.set(
|
||||
JSON.stringify({ kind: "builtin", name: "print" }),
|
||||
printIdx
|
||||
setMap(
|
||||
cx.funcIndices,
|
||||
{ kind: "builtin", name: "print" },
|
||||
{ kind: "func", idx: printIdx }
|
||||
);
|
||||
|
||||
mod.exports.push({
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
FunctionArg,
|
||||
FunctionDef,
|
||||
Identifier,
|
||||
ImportDef,
|
||||
Item,
|
||||
LOGICAL_KINDS,
|
||||
Type,
|
||||
|
|
@ -23,7 +24,13 @@ import {
|
|||
superFoldExpr,
|
||||
} from "./ast";
|
||||
import { CompilerError, Span, spanMerge } from "./error";
|
||||
import { BaseToken, Token, TokenIdent } from "./lexer";
|
||||
import {
|
||||
BaseToken,
|
||||
Token,
|
||||
TokenIdent,
|
||||
TokenLit,
|
||||
TokenLitString,
|
||||
} from "./lexer";
|
||||
|
||||
type Parser<T> = (t: Token[]) => [Token[], T];
|
||||
|
||||
|
|
@ -49,28 +56,8 @@ function parseItem(t: Token[]): [Token[], Item] {
|
|||
let tok;
|
||||
[t, tok] = next(t);
|
||||
if (tok.kind === "function") {
|
||||
let name;
|
||||
[t, name] = expectNext<TokenIdent>(t, "identifier");
|
||||
|
||||
[t] = expectNext(t, "(");
|
||||
|
||||
let args: FunctionArg[];
|
||||
[t, args] = parseCommaSeparatedList(t, ")", (t) => {
|
||||
let name;
|
||||
[t, name] = expectNext<TokenIdent & { span: Span }>(t, "identifier");
|
||||
[t] = expectNext(t, ":");
|
||||
let type;
|
||||
[t, type] = parseType(t);
|
||||
|
||||
return [t, { name: name.ident, type, span: name.span }];
|
||||
});
|
||||
|
||||
let colon;
|
||||
let returnType = undefined;
|
||||
[t, colon] = eat(t, ":");
|
||||
if (colon) {
|
||||
[t, returnType] = parseType(t);
|
||||
}
|
||||
let sig;
|
||||
[t, sig] = parseFunctionSig(t);
|
||||
|
||||
[t] = expectNext(t, "=");
|
||||
|
||||
|
|
@ -80,9 +67,7 @@ function parseItem(t: Token[]): [Token[], Item] {
|
|||
[t] = expectNext(t, ";");
|
||||
|
||||
const def: FunctionDef = {
|
||||
name: name.ident,
|
||||
params: args,
|
||||
returnType,
|
||||
...sig,
|
||||
body,
|
||||
};
|
||||
|
||||
|
|
@ -129,11 +114,64 @@ function parseItem(t: Token[]): [Token[], Item] {
|
|||
};
|
||||
|
||||
return [t, { kind: "type", node: def, span: name.span, id: 0 }];
|
||||
} else if (tok.kind === "import") {
|
||||
[t] = expectNext(t, "(");
|
||||
let module;
|
||||
[t, module] = expectNext<TokenLitString>(t, "lit_string");
|
||||
let func;
|
||||
[t, func] = expectNext<TokenLitString>(t, "lit_string");
|
||||
[t] = expectNext(t, ")");
|
||||
|
||||
let sig;
|
||||
[t, sig] = parseFunctionSig(t);
|
||||
|
||||
[t] = expectNext(t, ";");
|
||||
|
||||
const def: ImportDef = {
|
||||
module: { kind: "str", value: module.value, span: module.span },
|
||||
func: { kind: "str", value: func.value, span: func.span },
|
||||
...sig,
|
||||
};
|
||||
|
||||
return [t, { kind: "import", node: def, span: tok.span, id: 0 }];
|
||||
} else {
|
||||
unexpectedToken(tok, "item");
|
||||
}
|
||||
}
|
||||
|
||||
type FunctionSig = {
|
||||
name: string;
|
||||
params: FunctionArg[];
|
||||
returnType?: Type;
|
||||
};
|
||||
|
||||
function parseFunctionSig(t: Token[]): [Token[], FunctionSig] {
|
||||
let name;
|
||||
[t, name] = expectNext<TokenIdent>(t, "identifier");
|
||||
|
||||
[t] = expectNext(t, "(");
|
||||
|
||||
let params: FunctionArg[];
|
||||
[t, params] = parseCommaSeparatedList(t, ")", (t) => {
|
||||
let name;
|
||||
[t, name] = expectNext<TokenIdent & { span: Span }>(t, "identifier");
|
||||
[t] = expectNext(t, ":");
|
||||
let type;
|
||||
[t, type] = parseType(t);
|
||||
|
||||
return [t, { name: name.ident, type, span: name.span }];
|
||||
});
|
||||
|
||||
let colon;
|
||||
let returnType = undefined;
|
||||
[t, colon] = eat(t, ":");
|
||||
if (colon) {
|
||||
[t, returnType] = parseType(t);
|
||||
}
|
||||
|
||||
return [t, { name: name.ident, params, returnType }];
|
||||
}
|
||||
|
||||
function parseExpr(t: Token[]): [Token[], Expr] {
|
||||
/*
|
||||
EXPR = COMPARISON
|
||||
|
|
@ -267,7 +305,7 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] {
|
|||
{
|
||||
kind: "literal",
|
||||
span,
|
||||
value: { kind: "str", value: tok.value },
|
||||
value: { kind: "str", value: tok.value, span: tok.span },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -278,7 +316,7 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] {
|
|||
{
|
||||
kind: "literal",
|
||||
span,
|
||||
value: { kind: "int", value: tok.value },
|
||||
value: { kind: "int", value: tok.value, type: tok.type },
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -340,7 +378,7 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] {
|
|||
if (tok.kind === "if") {
|
||||
let cond;
|
||||
[t, cond] = parseExpr(t);
|
||||
|
||||
|
||||
[t] = expectNext(t, "then");
|
||||
let then;
|
||||
[t, then] = parseExpr(t);
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ import {
|
|||
Expr,
|
||||
FunctionDef,
|
||||
Identifier,
|
||||
ImportDef,
|
||||
Item,
|
||||
Resolution,
|
||||
StringLiteral,
|
||||
Ty,
|
||||
Type,
|
||||
TypeDef,
|
||||
|
|
@ -15,6 +17,10 @@ export function printAst(ast: Ast): string {
|
|||
return ast.items.map(printItem).join("\n");
|
||||
}
|
||||
|
||||
function printStringLiteral(lit: StringLiteral): string {
|
||||
return `"${lit.value}"`;
|
||||
}
|
||||
|
||||
function printItem(item: Item): string {
|
||||
switch (item.kind) {
|
||||
case "function": {
|
||||
|
|
@ -23,6 +29,9 @@ function printItem(item: Item): string {
|
|||
case "type": {
|
||||
return printTypeDef(item.node);
|
||||
}
|
||||
case "import": {
|
||||
return printImportDef(item.node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -45,6 +54,17 @@ function printTypeDef(type: TypeDef): string {
|
|||
return `type ${type.name} = ${fieldPart};`;
|
||||
}
|
||||
|
||||
function printImportDef(def: ImportDef): string {
|
||||
const args = def.params
|
||||
.map(({ name, type }) => `${name}: ${printType(type)}`)
|
||||
.join(", ");
|
||||
const ret = def.returnType ? `: ${printType(def.returnType)}` : "";
|
||||
|
||||
return `import ${printStringLiteral(def.module)} ${printStringLiteral(
|
||||
def.func
|
||||
)}(${args})${ret};`;
|
||||
}
|
||||
|
||||
function printExpr(expr: Expr, indent: number): string {
|
||||
switch (expr.kind) {
|
||||
case "empty": {
|
||||
|
|
@ -84,10 +104,10 @@ function printExpr(expr: Expr, indent: number): string {
|
|||
case "literal": {
|
||||
switch (expr.value.kind) {
|
||||
case "str": {
|
||||
return `"${expr.value.value}"`;
|
||||
return printStringLiteral(expr.value);
|
||||
}
|
||||
case "int": {
|
||||
return `${expr.value.value}`;
|
||||
return `${expr.value.value}_${expr.value.type}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
101
src/typeck.ts
101
src/typeck.ts
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
Ast,
|
||||
binaryExprPrecedenceClass,
|
||||
BuiltinName,
|
||||
COMPARISON_KINDS,
|
||||
DEFAULT_FOLDER,
|
||||
EQUALITY_KINDS,
|
||||
|
|
@ -21,12 +22,17 @@ import {
|
|||
TY_STRING,
|
||||
TY_UNIT,
|
||||
TyFn,
|
||||
tyIsUnit,
|
||||
Type,
|
||||
TyStruct,
|
||||
} from "./ast";
|
||||
import { CompilerError, Span } from "./error";
|
||||
import { printTy } from "./printer";
|
||||
|
||||
function mkTyFn(params: Ty[], returnTy: Ty): Ty {
|
||||
return { kind: "fn", params, returnTy };
|
||||
}
|
||||
|
||||
function builtinAsTy(name: string, span: Span): Ty {
|
||||
switch (name) {
|
||||
case "String": {
|
||||
|
|
@ -47,13 +53,25 @@ function builtinAsTy(name: string, span: Span): Ty {
|
|||
}
|
||||
}
|
||||
|
||||
function typeOfBuiltinValue(name: string, span: Span): Ty {
|
||||
function typeOfBuiltinValue(name: BuiltinName, span: Span): Ty {
|
||||
switch (name) {
|
||||
case "false":
|
||||
case "true":
|
||||
return TY_BOOL;
|
||||
case "print":
|
||||
return { kind: "fn", params: [TY_STRING], returnTy: TY_UNIT };
|
||||
return mkTyFn([TY_STRING], TY_UNIT);
|
||||
case "__i32_store":
|
||||
return mkTyFn([TY_I32, TY_I32], TY_UNIT);
|
||||
case "__i64_store":
|
||||
return mkTyFn([TY_I32, TY_INT], TY_UNIT);
|
||||
case "__i32_load":
|
||||
return mkTyFn([TY_I32], TY_I32);
|
||||
case "__i64_load":
|
||||
return mkTyFn([TY_I32], TY_INT);
|
||||
case "__string_ptr":
|
||||
return mkTyFn([TY_STRING], TY_I32);
|
||||
case "__string_len":
|
||||
return mkTyFn([TY_STRING], TY_I32);
|
||||
default: {
|
||||
throw new CompilerError(`\`${name}\` cannot be used as a value`, span);
|
||||
}
|
||||
|
|
@ -103,7 +121,8 @@ export function typeck(ast: Ast): Ast {
|
|||
}
|
||||
itemTys.set(index, null);
|
||||
switch (item.kind) {
|
||||
case "function": {
|
||||
case "function":
|
||||
case "import": {
|
||||
const args = item.node.params.map((arg) => lowerAstTy(arg.type));
|
||||
const returnTy: Ty = item.node.returnType
|
||||
? lowerAstTy(item.node.returnType)
|
||||
|
|
@ -182,6 +201,58 @@ export function typeck(ast: Ast): Ast {
|
|||
},
|
||||
};
|
||||
}
|
||||
case "import": {
|
||||
const fnTy = typeOfItem(item.id) as TyFn;
|
||||
|
||||
fnTy.params.forEach((param, i) => {
|
||||
switch (param.kind) {
|
||||
case "int":
|
||||
case "i32":
|
||||
break;
|
||||
default: {
|
||||
throw new CompilerError(
|
||||
`import parameters must be I32 or Int`,
|
||||
item.node.params[i].span
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!tyIsUnit(fnTy.returnTy)) {
|
||||
switch (fnTy.returnTy.kind) {
|
||||
case "int":
|
||||
case "i32":
|
||||
break;
|
||||
default: {
|
||||
throw new CompilerError(
|
||||
`import return must be I32 or Int`,
|
||||
item.node.returnType!.span
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const returnType = item.node.returnType && {
|
||||
...item.node.returnType,
|
||||
ty: fnTy.returnTy,
|
||||
};
|
||||
|
||||
return {
|
||||
...item,
|
||||
kind: "import",
|
||||
node: {
|
||||
module: item.node.module,
|
||||
func: item.node.func,
|
||||
name: item.node.name,
|
||||
params: item.node.params.map((arg, i) => ({
|
||||
...arg,
|
||||
type: { ...arg.type, ty: fnTy.params[i] },
|
||||
})),
|
||||
returnType,
|
||||
ty: fnTy,
|
||||
},
|
||||
};
|
||||
}
|
||||
case "type": {
|
||||
const fieldNames = new Set();
|
||||
item.node.fields.forEach(({ name }) => {
|
||||
|
|
@ -436,7 +507,17 @@ export function checkBody(
|
|||
type,
|
||||
(ident) => {
|
||||
const res = ident.res!;
|
||||
return typeOf(res, ident.span);
|
||||
switch (res.kind) {
|
||||
case "local": {
|
||||
const idx = localTys.length - 1 - res.index;
|
||||
return localTys[idx];
|
||||
}
|
||||
case "item": {
|
||||
return typeOfItem(res.index);
|
||||
}
|
||||
case "builtin":
|
||||
return builtinAsTy(res.name, ident.span);
|
||||
}
|
||||
},
|
||||
typeOfItem
|
||||
);
|
||||
|
|
@ -502,7 +583,14 @@ export function checkBody(
|
|||
break;
|
||||
}
|
||||
case "int": {
|
||||
ty = TY_INT;
|
||||
switch (expr.value.type) {
|
||||
case "Int":
|
||||
ty = TY_INT;
|
||||
break;
|
||||
case "I32":
|
||||
ty = TY_I32;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -721,6 +809,9 @@ function checkBinary(expr: Expr & ExprBinary): Expr {
|
|||
if (lhsTy.kind === "int" && rhsTy.kind === "int") {
|
||||
return { ...expr, ty: TY_INT };
|
||||
}
|
||||
if (lhsTy.kind === "i32" && rhsTy.kind === "i32") {
|
||||
return { ...expr, ty: TY_I32 };
|
||||
}
|
||||
|
||||
if (LOGICAL_KINDS.includes(expr.binaryKind)) {
|
||||
if (lhsTy.kind === "bool" && rhsTy.kind === "bool") {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue