mirror of
https://github.com/Noratrieb/riverdelta.git
synced 2026-01-14 16:35:03 +01:00
fix many things
This commit is contained in:
parent
02bbd8ec1e
commit
7cd50ab554
7 changed files with 269 additions and 51 deletions
51
src/ast.ts
51
src/ast.ts
|
|
@ -104,6 +104,20 @@ export type ExprIf = {
|
||||||
else?: Expr;
|
else?: Expr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ExprLoop = {
|
||||||
|
kind: "loop";
|
||||||
|
body: Expr;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ExprBreak = {
|
||||||
|
kind: "break";
|
||||||
|
/**
|
||||||
|
* The break target block.
|
||||||
|
* May be any control flow block, labelled from inside out.
|
||||||
|
*/
|
||||||
|
target?: number,
|
||||||
|
};
|
||||||
|
|
||||||
export type ExprStructLiteral = {
|
export type ExprStructLiteral = {
|
||||||
kind: "structLiteral";
|
kind: "structLiteral";
|
||||||
name: Identifier;
|
name: Identifier;
|
||||||
|
|
@ -120,6 +134,8 @@ export type ExprKind =
|
||||||
| ExprUnary
|
| ExprUnary
|
||||||
| ExprCall
|
| ExprCall
|
||||||
| ExprIf
|
| ExprIf
|
||||||
|
| ExprLoop
|
||||||
|
| ExprBreak
|
||||||
| ExprStructLiteral;
|
| ExprStructLiteral;
|
||||||
|
|
||||||
export type Expr = ExprKind & {
|
export type Expr = ExprKind & {
|
||||||
|
|
@ -202,7 +218,8 @@ export type TypeKind =
|
||||||
| {
|
| {
|
||||||
kind: "tuple";
|
kind: "tuple";
|
||||||
elems: Type[];
|
elems: Type[];
|
||||||
};
|
}
|
||||||
|
| { kind: "never" };
|
||||||
|
|
||||||
export type Type = TypeKind & {
|
export type Type = TypeKind & {
|
||||||
span: Span;
|
span: Span;
|
||||||
|
|
@ -217,9 +234,9 @@ export type Resolution =
|
||||||
/**
|
/**
|
||||||
* The index of the local variable, from inside out.
|
* The index of the local variable, from inside out.
|
||||||
* ```
|
* ```
|
||||||
* let a = 0; let b; (a, b);
|
* let a; let b; (a, b);
|
||||||
* ^ ^
|
* ^ ^
|
||||||
* 1 0
|
* 1 0
|
||||||
* ```
|
* ```
|
||||||
* When traversing resolutions, a stack of locals has to be kept.
|
* When traversing resolutions, a stack of locals has to be kept.
|
||||||
* It's similar to a De Bruijn index.
|
* It's similar to a De Bruijn index.
|
||||||
|
|
@ -304,6 +321,10 @@ export type TyStruct = {
|
||||||
fields: [string, Ty][];
|
fields: [string, Ty][];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TyNever = {
|
||||||
|
kind: "never";
|
||||||
|
};
|
||||||
|
|
||||||
export type Ty =
|
export type Ty =
|
||||||
| TyString
|
| TyString
|
||||||
| TyInt
|
| TyInt
|
||||||
|
|
@ -312,7 +333,8 @@ export type Ty =
|
||||||
| TyTuple
|
| TyTuple
|
||||||
| TyFn
|
| TyFn
|
||||||
| TyVar
|
| TyVar
|
||||||
| TyStruct;
|
| TyStruct
|
||||||
|
| TyNever;
|
||||||
|
|
||||||
export function tyIsUnit(ty: Ty): ty is TyUnit {
|
export function tyIsUnit(ty: Ty): ty is TyUnit {
|
||||||
return ty.kind === "tuple" && ty.elems.length === 0;
|
return ty.kind === "tuple" && ty.elems.length === 0;
|
||||||
|
|
@ -322,9 +344,10 @@ export const TY_UNIT: Ty = { kind: "tuple", elems: [] };
|
||||||
export const TY_STRING: Ty = { kind: "string" };
|
export const TY_STRING: Ty = { kind: "string" };
|
||||||
export const TY_BOOL: Ty = { kind: "bool" };
|
export const TY_BOOL: Ty = { kind: "bool" };
|
||||||
export const TY_INT: Ty = { kind: "int", signed: false };
|
export const TY_INT: Ty = { kind: "int", signed: false };
|
||||||
|
export const TY_NEVER: Ty = { kind: "never" };
|
||||||
|
|
||||||
export type TypeckResults = {
|
export type TypeckResults = {
|
||||||
main: ItemId;
|
main: Resolution;
|
||||||
};
|
};
|
||||||
|
|
||||||
// folders
|
// folders
|
||||||
|
|
@ -459,6 +482,19 @@ export function superFoldExpr(expr: Expr, folder: Folder): Expr {
|
||||||
else: expr.else && folder.expr(expr.else),
|
else: expr.else && folder.expr(expr.else),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case "loop": {
|
||||||
|
return {
|
||||||
|
...expr,
|
||||||
|
kind: "loop",
|
||||||
|
body: folder.expr(expr.body),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "break": {
|
||||||
|
return {
|
||||||
|
...expr,
|
||||||
|
kind: "break",
|
||||||
|
};
|
||||||
|
}
|
||||||
case "structLiteral": {
|
case "structLiteral": {
|
||||||
return {
|
return {
|
||||||
...expr,
|
...expr,
|
||||||
|
|
@ -494,6 +530,9 @@ export function superFoldType(type: Type, folder: Folder): Type {
|
||||||
span,
|
span,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case "never": {
|
||||||
|
return { ...type, kind: "never" };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
12
src/index.ts
12
src/index.ts
|
|
@ -10,8 +10,13 @@ import fs from "fs";
|
||||||
import { exec } from "child_process";
|
import { exec } from "child_process";
|
||||||
|
|
||||||
const input = `
|
const input = `
|
||||||
function main() = (
|
function main() = uwu(10);
|
||||||
1 + 2 * 3;
|
|
||||||
|
function a() = ;
|
||||||
|
|
||||||
|
function uwu(a: Int) = if a != 0 then (
|
||||||
|
print("uwu\n");
|
||||||
|
uwu(a - 1);
|
||||||
);
|
);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
@ -39,7 +44,8 @@ function main() {
|
||||||
|
|
||||||
console.log("-----AST typecked------");
|
console.log("-----AST typecked------");
|
||||||
const typecked = typeck(resolved);
|
const typecked = typeck(resolved);
|
||||||
console.dir(typecked, { depth: 8 });
|
const typeckPrinted = printAst(typecked);
|
||||||
|
console.log(typeckPrinted);
|
||||||
|
|
||||||
console.log("-----wasm--------------");
|
console.log("-----wasm--------------");
|
||||||
const wasmModule = lowerToWasm(typecked);
|
const wasmModule = lowerToWasm(typecked);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ export type DatalessToken =
|
||||||
| "then"
|
| "then"
|
||||||
| "else"
|
| "else"
|
||||||
| "type"
|
| "type"
|
||||||
|
| "loop"
|
||||||
|
| "break"
|
||||||
| "("
|
| "("
|
||||||
| ")"
|
| ")"
|
||||||
| "{"
|
| "{"
|
||||||
|
|
@ -263,6 +265,8 @@ const KEYOWRDS: DatalessToken[] = [
|
||||||
"then",
|
"then",
|
||||||
"else",
|
"else",
|
||||||
"type",
|
"type",
|
||||||
|
"loop",
|
||||||
|
"break",
|
||||||
];
|
];
|
||||||
|
|
||||||
const KEYWORD_SET = new Set<string>(KEYOWRDS);
|
const KEYWORD_SET = new Set<string>(KEYOWRDS);
|
||||||
|
|
|
||||||
161
src/lower.ts
161
src/lower.ts
|
|
@ -4,11 +4,11 @@ import {
|
||||||
ExprBlock,
|
ExprBlock,
|
||||||
FunctionDef,
|
FunctionDef,
|
||||||
Item,
|
Item,
|
||||||
|
Resolution,
|
||||||
Ty,
|
Ty,
|
||||||
TyFn,
|
TyFn,
|
||||||
varUnreachable,
|
varUnreachable,
|
||||||
} from "./ast";
|
} from "./ast";
|
||||||
import { CompilerError } from "./error";
|
|
||||||
import { encodeUtf8 } from "./utils";
|
import { encodeUtf8 } from "./utils";
|
||||||
import * as wasm from "./wasm/defs";
|
import * as wasm from "./wasm/defs";
|
||||||
|
|
||||||
|
|
@ -19,30 +19,46 @@ const USIZE: wasm.ValType = "i32";
|
||||||
const POINTER: wasm.ValType = USIZE;
|
const POINTER: wasm.ValType = USIZE;
|
||||||
|
|
||||||
const STRING_TYPES: wasm.ValType[] = [POINTER, USIZE];
|
const STRING_TYPES: wasm.ValType[] = [POINTER, USIZE];
|
||||||
const STRING_ABI: ArgAbi & RetAbi = {
|
const STRING_ABI: ArgRetAbi = {
|
||||||
kind: "aggregate",
|
kind: "aggregate",
|
||||||
types: STRING_TYPES,
|
types: STRING_TYPES,
|
||||||
};
|
};
|
||||||
|
|
||||||
const WASM_PAGE = 65536;
|
const WASM_PAGE = 65536;
|
||||||
|
|
||||||
|
type Relocation = {
|
||||||
|
kind: "funccall";
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMap<K, V>(
|
||||||
|
map: Map<StringifiedForMap<K>, V>,
|
||||||
|
key: K
|
||||||
|
): V | undefined {
|
||||||
|
return map.get(JSON.stringify(key));
|
||||||
|
}
|
||||||
|
|
||||||
export type Context = {
|
export type Context = {
|
||||||
mod: wasm.Module;
|
mod: wasm.Module;
|
||||||
funcTypes: Map<StringifiedForMap<wasm.FuncType>, wasm.TypeIdx>;
|
funcTypes: Map<StringifiedForMap<wasm.FuncType>, wasm.TypeIdx>;
|
||||||
reservedHeapMemoryStart: number;
|
reservedHeapMemoryStart: number;
|
||||||
funcIndices: Map<number, wasm.FuncIdx>;
|
funcIndices: Map<StringifiedForMap<Resolution>, wasm.FuncIdx>;
|
||||||
ast: Ast;
|
ast: Ast;
|
||||||
|
relocations: Relocation[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function internFuncType(cx: Context, type: wasm.FuncType): wasm.TypeIdx {
|
function internFuncType(cx: Context, type: wasm.FuncType): wasm.TypeIdx {
|
||||||
const s = JSON.stringify(type);
|
const existing = getMap(cx.funcTypes, type);
|
||||||
const existing = cx.funcTypes.get(s);
|
|
||||||
if (existing !== undefined) {
|
if (existing !== undefined) {
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
const idx = cx.mod.types.length;
|
const idx = cx.mod.types.length;
|
||||||
cx.mod.types.push(type);
|
cx.mod.types.push(type);
|
||||||
cx.funcTypes.set(s, idx);
|
setMap(cx.funcTypes, type, idx);
|
||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,6 +118,7 @@ export function lower(ast: Ast): wasm.Module {
|
||||||
funcIndices: new Map(),
|
funcIndices: new Map(),
|
||||||
reservedHeapMemoryStart: 0,
|
reservedHeapMemoryStart: 0,
|
||||||
ast,
|
ast,
|
||||||
|
relocations: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
ast.items.forEach((item) => {
|
ast.items.forEach((item) => {
|
||||||
|
|
@ -120,6 +137,23 @@ export function lower(ast: Ast): wasm.Module {
|
||||||
|
|
||||||
addRt(cx, ast);
|
addRt(cx, ast);
|
||||||
|
|
||||||
|
// THE LINKER
|
||||||
|
const offset = cx.mod.imports.length;
|
||||||
|
cx.relocations.forEach((rel) => {
|
||||||
|
switch (rel.kind) {
|
||||||
|
case "funccall": {
|
||||||
|
const idx = getMap<Resolution, number>(cx.funcIndices, rel.res);
|
||||||
|
if (idx === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`no function found for relocation '${JSON.stringify(rel.res)}'`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
rel.instr.func = offset + idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// END OF THE LINKER
|
||||||
|
|
||||||
return mod;
|
return mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,13 +165,9 @@ type FuncContext = {
|
||||||
varLocations: VarLocation[];
|
varLocations: VarLocation[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type Abi = { params: ArgAbi[]; ret: RetAbi };
|
type FnAbi = { params: ArgRetAbi[]; ret: ArgRetAbi };
|
||||||
|
|
||||||
type ArgAbi =
|
type ArgRetAbi =
|
||||||
| { kind: "scalar"; type: wasm.ValType }
|
|
||||||
| { kind: "zst" }
|
|
||||||
| { kind: "aggregate"; types: wasm.ValType[] };
|
|
||||||
type RetAbi =
|
|
||||||
| { kind: "scalar"; type: wasm.ValType }
|
| { kind: "scalar"; type: wasm.ValType }
|
||||||
| { kind: "zst" }
|
| { kind: "zst" }
|
||||||
| { kind: "aggregate"; types: wasm.ValType[] };
|
| { kind: "aggregate"; types: wasm.ValType[] };
|
||||||
|
|
@ -168,7 +198,11 @@ function lowerFunc(cx: Context, item: Item, func: FunctionDef) {
|
||||||
|
|
||||||
const idx = fcx.cx.mod.funcs.length;
|
const idx = fcx.cx.mod.funcs.length;
|
||||||
fcx.cx.mod.funcs.push(wasmFunc);
|
fcx.cx.mod.funcs.push(wasmFunc);
|
||||||
fcx.cx.funcIndices.set(fcx.item.id, idx);
|
setMap<Resolution, number>(
|
||||||
|
fcx.cx.funcIndices,
|
||||||
|
{ kind: "item", index: fcx.item.id },
|
||||||
|
idx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -372,18 +406,17 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
|
||||||
if (expr.lhs.kind !== "ident") {
|
if (expr.lhs.kind !== "ident") {
|
||||||
todo("non constant calls");
|
todo("non constant calls");
|
||||||
}
|
}
|
||||||
if (expr.lhs.value.res!.kind !== "builtin") {
|
const callInstr: wasm.Instr = { kind: "call", func: 9999999999 };
|
||||||
todo("youre not a builtin, fuck you");
|
fcx.cx.relocations.push({
|
||||||
}
|
kind: "funccall",
|
||||||
const printIdx =
|
instr: callInstr,
|
||||||
fcx.cx.ast.items.filter((item) => item.kind === "function").length +
|
res: expr.lhs.value.res!,
|
||||||
/*import*/ 1 +
|
});
|
||||||
/*_start*/ 1;
|
|
||||||
expr.args.forEach((arg) => {
|
expr.args.forEach((arg) => {
|
||||||
lowerExpr(fcx, instrs, arg);
|
lowerExpr(fcx, instrs, arg);
|
||||||
});
|
});
|
||||||
instrs.push({ kind: "call", func: printIdx });
|
instrs.push(callInstr);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "if": {
|
case "if": {
|
||||||
|
|
@ -407,6 +440,43 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "loop": {
|
||||||
|
const outerBlockInstrs: wasm.Instr[] = [];
|
||||||
|
|
||||||
|
const bodyInstrs: wasm.Instr[] = [];
|
||||||
|
lowerExpr(fcx, bodyInstrs, expr.body);
|
||||||
|
bodyInstrs.push({
|
||||||
|
kind: "br",
|
||||||
|
label: /*innermost control structure, the loop*/ 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
outerBlockInstrs.push({
|
||||||
|
kind: "loop",
|
||||||
|
instrs: bodyInstrs,
|
||||||
|
type: blockTypeForBody(fcx.cx, expr.ty!),
|
||||||
|
});
|
||||||
|
|
||||||
|
instrs.push({
|
||||||
|
kind: "block",
|
||||||
|
instrs: outerBlockInstrs,
|
||||||
|
type: blockTypeForBody(fcx.cx, expr.ty!),
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "break": {
|
||||||
|
instrs.push({
|
||||||
|
kind: "br",
|
||||||
|
label: expr.target! + /* the block outside the loop */ 1,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "structLiteral": {
|
||||||
|
todo("struct literal");
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
const _: never = expr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -444,12 +514,12 @@ function loadVariable(instrs: wasm.Instr[], loc: VarLocation) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeAbi(ty: TyFn): Abi {
|
function computeAbi(ty: TyFn): FnAbi {
|
||||||
const scalar = (type: wasm.ValType): ArgAbi & RetAbi =>
|
const scalar = (type: wasm.ValType): ArgRetAbi =>
|
||||||
({ kind: "scalar", type } as const);
|
({ kind: "scalar", type } as const);
|
||||||
const zst: ArgAbi & RetAbi = { kind: "zst" };
|
const zst: ArgRetAbi = { kind: "zst" };
|
||||||
|
|
||||||
function paramAbi(param: Ty): ArgAbi {
|
function paramAbi(param: Ty): ArgRetAbi {
|
||||||
switch (param.kind) {
|
switch (param.kind) {
|
||||||
case "string":
|
case "string":
|
||||||
return STRING_ABI;
|
return STRING_ABI;
|
||||||
|
|
@ -470,6 +540,8 @@ function computeAbi(ty: TyFn): Abi {
|
||||||
todo("complex tuple abi");
|
todo("complex tuple abi");
|
||||||
case "struct":
|
case "struct":
|
||||||
todo("struct ABI");
|
todo("struct ABI");
|
||||||
|
case "never":
|
||||||
|
return zst;
|
||||||
case "var":
|
case "var":
|
||||||
varUnreachable();
|
varUnreachable();
|
||||||
}
|
}
|
||||||
|
|
@ -477,7 +549,7 @@ function computeAbi(ty: TyFn): Abi {
|
||||||
|
|
||||||
const params = ty.params.map(paramAbi);
|
const params = ty.params.map(paramAbi);
|
||||||
|
|
||||||
let ret: RetAbi;
|
let ret: ArgRetAbi;
|
||||||
switch (ty.returnTy.kind) {
|
switch (ty.returnTy.kind) {
|
||||||
case "string":
|
case "string":
|
||||||
ret = STRING_ABI;
|
ret = STRING_ABI;
|
||||||
|
|
@ -503,6 +575,9 @@ function computeAbi(ty: TyFn): Abi {
|
||||||
todo("complex tuple abi");
|
todo("complex tuple abi");
|
||||||
case "struct":
|
case "struct":
|
||||||
todo("struct ABI");
|
todo("struct ABI");
|
||||||
|
case "never":
|
||||||
|
ret = zst;
|
||||||
|
break;
|
||||||
case "var":
|
case "var":
|
||||||
varUnreachable();
|
varUnreachable();
|
||||||
}
|
}
|
||||||
|
|
@ -510,7 +585,7 @@ function computeAbi(ty: TyFn): Abi {
|
||||||
return { params, ret };
|
return { params, ret };
|
||||||
}
|
}
|
||||||
|
|
||||||
function wasmTypeForAbi(abi: Abi): {
|
function wasmTypeForAbi(abi: FnAbi): {
|
||||||
type: wasm.FuncType;
|
type: wasm.FuncType;
|
||||||
paramLocations: VarLocation[];
|
paramLocations: VarLocation[];
|
||||||
} {
|
} {
|
||||||
|
|
@ -568,6 +643,8 @@ function wasmTypeForBody(ty: Ty): wasm.ValType[] {
|
||||||
todo("fn types");
|
todo("fn types");
|
||||||
case "struct":
|
case "struct":
|
||||||
todo("struct types");
|
todo("struct types");
|
||||||
|
case "never":
|
||||||
|
return [];
|
||||||
case "var":
|
case "var":
|
||||||
varUnreachable();
|
varUnreachable();
|
||||||
}
|
}
|
||||||
|
|
@ -588,20 +665,26 @@ function todo(msg: string): never {
|
||||||
// Make the program runnable using wasi-preview-1
|
// Make the program runnable using wasi-preview-1
|
||||||
function addRt(cx: Context, ast: Ast) {
|
function addRt(cx: Context, ast: Ast) {
|
||||||
const { mod } = cx;
|
const { mod } = cx;
|
||||||
const main = cx.funcIndices.get(ast.typeckResults!.main);
|
|
||||||
if (main === undefined) {
|
const mainCall: wasm.Instr = { kind: "call", func: 9999999 };
|
||||||
throw new Error(`main function (${main}) was not compiled.`);
|
cx.relocations.push({
|
||||||
}
|
kind: "funccall",
|
||||||
|
instr: mainCall,
|
||||||
|
res: ast.typeckResults!.main,
|
||||||
|
});
|
||||||
|
|
||||||
const start: wasm.Func = {
|
const start: wasm.Func = {
|
||||||
_name: "_start",
|
_name: "_start",
|
||||||
type: internFuncType(cx, { params: [], returns: [] }),
|
type: internFuncType(cx, { params: [], returns: [] }),
|
||||||
locals: [],
|
locals: [],
|
||||||
body: [{ kind: "call", func: main + 1 }],
|
body: [mainCall],
|
||||||
};
|
};
|
||||||
|
|
||||||
const startIdx = mod.funcs.length;
|
const startIdx = mod.funcs.length;
|
||||||
mod.funcs.push(start);
|
mod.funcs.push(start);
|
||||||
|
console.log(mod.funcs.map(({ _name }) => _name));
|
||||||
|
|
||||||
|
console.log(startIdx);
|
||||||
|
|
||||||
const reserveMemory = (amount: number) => {
|
const reserveMemory = (amount: number) => {
|
||||||
const start = cx.reservedHeapMemoryStart;
|
const start = cx.reservedHeapMemoryStart;
|
||||||
|
|
@ -645,8 +728,16 @@ function addRt(cx: Context, ast: Ast) {
|
||||||
{ kind: "drop" },
|
{ kind: "drop" },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
const printIdx = cx.mod.funcs.length;
|
||||||
cx.mod.funcs.push(print);
|
cx.mod.funcs.push(print);
|
||||||
|
|
||||||
mod.exports.push({ name: "_start", desc: { kind: "func", idx: startIdx } });
|
cx.funcIndices.set(
|
||||||
|
JSON.stringify({ kind: "builtin", name: "print" }),
|
||||||
|
printIdx
|
||||||
|
);
|
||||||
|
|
||||||
|
mod.exports.push({
|
||||||
|
name: "_start",
|
||||||
|
desc: { kind: "func", idx: startIdx + mod.imports.length },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ function parseItem(t: Token[]): [Token[], Item] {
|
||||||
|
|
||||||
return [t, { kind: "type", node: def, span: name.span, id: 0 }];
|
return [t, { kind: "type", node: def, span: name.span, id: 0 }];
|
||||||
} else {
|
} else {
|
||||||
unexpectedToken(tok);
|
unexpectedToken(tok, "item");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,6 +140,8 @@ function parseExpr(t: Token[]): [Token[], Expr] {
|
||||||
|
|
||||||
LET = "let" NAME { ":" TYPE } "=" EXPR "in" EXPR
|
LET = "let" NAME { ":" TYPE } "=" EXPR "in" EXPR
|
||||||
IF = "if" EXPR "then" EXPR { "else" EXPR }
|
IF = "if" EXPR "then" EXPR { "else" EXPR }
|
||||||
|
LOOP = "loop" EXPR
|
||||||
|
BREAK = "break"
|
||||||
|
|
||||||
// The precende here is pretty arbitrary since we forbid mixing of operators
|
// The precende here is pretty arbitrary since we forbid mixing of operators
|
||||||
// with different precedence classes anyways.
|
// with different precedence classes anyways.
|
||||||
|
|
@ -154,7 +156,7 @@ function parseExpr(t: Token[]): [Token[], Expr] {
|
||||||
|
|
||||||
CALL = ATOM { "(" EXPR_LIST ")" }
|
CALL = ATOM { "(" EXPR_LIST ")" }
|
||||||
|
|
||||||
ATOM = "(" { EXPR ";" } EXPR ")" | IDENT { STRUCT_INIT } | LITERAL | EMPTY | LET | IF
|
ATOM = "(" { EXPR ";" } EXPR ")" | IDENT { STRUCT_INIT } | LITERAL | EMPTY | LET | IF | LOOP | BREAK
|
||||||
EMPTY =
|
EMPTY =
|
||||||
STRUCT_INIT = "{" { NAME ":" EXPR } { "," NAME ":" EXPR } { "," } "}"
|
STRUCT_INIT = "{" { NAME ":" EXPR } { "," NAME ":" EXPR } { "," } "}"
|
||||||
EXPR_LIST = { EXPR { "," EXPR } { "," } }
|
EXPR_LIST = { EXPR { "," EXPR } { "," } }
|
||||||
|
|
@ -338,6 +340,7 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] {
|
||||||
if (tok.kind === "if") {
|
if (tok.kind === "if") {
|
||||||
let cond;
|
let cond;
|
||||||
[t, cond] = parseExpr(t);
|
[t, cond] = parseExpr(t);
|
||||||
|
|
||||||
[t] = expectNext(t, "then");
|
[t] = expectNext(t, "then");
|
||||||
let then;
|
let then;
|
||||||
[t, then] = parseExpr(t);
|
[t, then] = parseExpr(t);
|
||||||
|
|
@ -352,6 +355,16 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] {
|
||||||
return [t, { kind: "if", cond, then, else: elsePart, span: tok.span }];
|
return [t, { kind: "if", cond, then, else: elsePart, span: tok.span }];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tok.kind === "loop") {
|
||||||
|
let body;
|
||||||
|
[t, body] = parseExpr(t);
|
||||||
|
return [t, { kind: "loop", body, span: tok.span }];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tok.kind === "break") {
|
||||||
|
return [t, { kind: "break", span: tok.span }];
|
||||||
|
}
|
||||||
|
|
||||||
// Parse nothing at all.
|
// Parse nothing at all.
|
||||||
return [startT, { kind: "empty", span }];
|
return [startT, { kind: "empty", span }];
|
||||||
}
|
}
|
||||||
|
|
@ -379,6 +392,9 @@ function parseType(t: Token[]): [Token[], Type] {
|
||||||
const span = tok.span;
|
const span = tok.span;
|
||||||
|
|
||||||
switch (tok.kind) {
|
switch (tok.kind) {
|
||||||
|
case "!": {
|
||||||
|
return [t, { kind: "never", span }];
|
||||||
|
}
|
||||||
case "identifier": {
|
case "identifier": {
|
||||||
return [
|
return [
|
||||||
t,
|
t,
|
||||||
|
|
@ -450,7 +466,7 @@ function parseCommaSeparatedList<R>(
|
||||||
// No comma? Fine, you don't like trailing commas.
|
// No comma? Fine, you don't like trailing commas.
|
||||||
// But this better be the end.
|
// But this better be the end.
|
||||||
if (next(t)[1]?.kind !== terminator) {
|
if (next(t)[1]?.kind !== terminator) {
|
||||||
unexpectedToken(next(t)[1]);
|
unexpectedToken(next(t)[1], `, or ${terminator}`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -501,8 +517,8 @@ function maybeNextT(t: Token[]): [Token[], Token | undefined] {
|
||||||
return [rest, next];
|
return [rest, next];
|
||||||
}
|
}
|
||||||
|
|
||||||
function unexpectedToken(token: Token): never {
|
function unexpectedToken(token: Token, expected: string): never {
|
||||||
throw new CompilerError("unexpected token", token.span);
|
throw new CompilerError(`unexpected token, expected ${expected}`, token.span);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateAst(ast: Ast) {
|
function validateAst(ast: Ast) {
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,13 @@ function printExpr(expr: Expr, indent: number): string {
|
||||||
indent + 1
|
indent + 1
|
||||||
)}${elsePart}`;
|
)}${elsePart}`;
|
||||||
}
|
}
|
||||||
|
case "loop": {
|
||||||
|
return `loop ${printExpr(expr.body, indent + 1)}`;
|
||||||
|
}
|
||||||
|
case "break": {
|
||||||
|
const target = expr.target !== undefined ? `#${expr.target}` : "";
|
||||||
|
return `break${target}`;
|
||||||
|
}
|
||||||
case "structLiteral": {
|
case "structLiteral": {
|
||||||
return `${printIdent(expr.name)} { ${expr.fields
|
return `${printIdent(expr.name)} { ${expr.fields
|
||||||
.map(([name, expr]) => `${name.name}: ${printExpr(expr, indent + 1)}`)
|
.map(([name, expr]) => `${name.name}: ${printExpr(expr, indent + 1)}`)
|
||||||
|
|
@ -142,6 +149,8 @@ function printType(type: Type): string {
|
||||||
return `[${printType(type.elem)}]`;
|
return `[${printType(type.elem)}]`;
|
||||||
case "tuple":
|
case "tuple":
|
||||||
return `(${type.elems.map(printType).join(", ")})`;
|
return `(${type.elems.map(printType).join(", ")})`;
|
||||||
|
case "never":
|
||||||
|
return "!";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,6 +197,9 @@ export function printTy(ty: Ty): string {
|
||||||
case "struct": {
|
case "struct": {
|
||||||
return ty.name;
|
return ty.name;
|
||||||
}
|
}
|
||||||
|
case "never": {
|
||||||
|
return "!";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
Ty,
|
Ty,
|
||||||
TY_BOOL,
|
TY_BOOL,
|
||||||
TY_INT,
|
TY_INT,
|
||||||
|
TY_NEVER,
|
||||||
TY_STRING,
|
TY_STRING,
|
||||||
TY_UNIT,
|
TY_UNIT,
|
||||||
TyFn,
|
TyFn,
|
||||||
|
|
@ -78,6 +79,9 @@ function lowerAstTyBase(
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case "never": {
|
||||||
|
return TY_NEVER;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,7 +238,7 @@ export function typeck(ast: Ast): Ast {
|
||||||
}
|
}
|
||||||
|
|
||||||
typecked.typeckResults = {
|
typecked.typeckResults = {
|
||||||
main: main.id,
|
main: { kind: "item", index: main.id },
|
||||||
};
|
};
|
||||||
|
|
||||||
return typecked;
|
return typecked;
|
||||||
|
|
@ -337,7 +341,10 @@ export class InferContext {
|
||||||
this.constrainVar(rhs.index, lhs);
|
this.constrainVar(rhs.index, lhs);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// type variable handling here
|
|
||||||
|
if (rhs.kind === "never") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (lhs.kind) {
|
switch (lhs.kind) {
|
||||||
case "string": {
|
case "string": {
|
||||||
|
|
@ -397,6 +404,8 @@ export function checkBody(
|
||||||
typeOfItem: (index: number) => Ty
|
typeOfItem: (index: number) => Ty
|
||||||
): Expr {
|
): Expr {
|
||||||
const localTys = [...fnTy.params];
|
const localTys = [...fnTy.params];
|
||||||
|
const loopState: { hasBreak: boolean; nestingDepth: number }[] = [];
|
||||||
|
let currentNestingDepth = 0;
|
||||||
|
|
||||||
const infcx = new InferContext();
|
const infcx = new InferContext();
|
||||||
|
|
||||||
|
|
@ -460,6 +469,7 @@ export function checkBody(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "block": {
|
case "block": {
|
||||||
|
currentNestingDepth++;
|
||||||
const prevLocalTysLen = localTys.length;
|
const prevLocalTysLen = localTys.length;
|
||||||
|
|
||||||
const exprs = expr.exprs.map((expr) => this.expr(expr));
|
const exprs = expr.exprs.map((expr) => this.expr(expr));
|
||||||
|
|
@ -468,6 +478,8 @@ export function checkBody(
|
||||||
|
|
||||||
localTys.length = prevLocalTysLen;
|
localTys.length = prevLocalTysLen;
|
||||||
|
|
||||||
|
currentNestingDepth--;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...expr,
|
...expr,
|
||||||
exprs,
|
exprs,
|
||||||
|
|
@ -544,8 +556,10 @@ export function checkBody(
|
||||||
}
|
}
|
||||||
case "if": {
|
case "if": {
|
||||||
const cond = this.expr(expr.cond);
|
const cond = this.expr(expr.cond);
|
||||||
|
currentNestingDepth++;
|
||||||
const then = this.expr(expr.then);
|
const then = this.expr(expr.then);
|
||||||
const elsePart = expr.else && this.expr(expr.else);
|
const elsePart = expr.else && this.expr(expr.else);
|
||||||
|
currentNestingDepth--;
|
||||||
|
|
||||||
infcx.assign(TY_BOOL, cond.ty!, cond.span);
|
infcx.assign(TY_BOOL, cond.ty!, cond.span);
|
||||||
|
|
||||||
|
|
@ -560,6 +574,42 @@ export function checkBody(
|
||||||
|
|
||||||
return { ...expr, cond, then, else: elsePart, ty };
|
return { ...expr, cond, then, else: elsePart, ty };
|
||||||
}
|
}
|
||||||
|
case "loop": {
|
||||||
|
currentNestingDepth++;
|
||||||
|
loopState.push({
|
||||||
|
hasBreak: false,
|
||||||
|
nestingDepth: currentNestingDepth,
|
||||||
|
});
|
||||||
|
|
||||||
|
const body = this.expr(expr.body);
|
||||||
|
infcx.assign(TY_UNIT, body.ty!, body.span);
|
||||||
|
|
||||||
|
const hadBreak = loopState.pop();
|
||||||
|
const ty = hadBreak ? TY_UNIT : TY_NEVER;
|
||||||
|
|
||||||
|
currentNestingDepth--;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...expr,
|
||||||
|
body,
|
||||||
|
ty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "break": {
|
||||||
|
if (loopState.length === 0) {
|
||||||
|
throw new CompilerError("break outside loop", expr.span);
|
||||||
|
}
|
||||||
|
const loopDepth = loopState[loopState.length - 1].nestingDepth;
|
||||||
|
loopState[loopState.length - 1].hasBreak = true;
|
||||||
|
|
||||||
|
const target = currentNestingDepth - loopDepth;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...expr,
|
||||||
|
ty: TY_NEVER,
|
||||||
|
target,
|
||||||
|
};
|
||||||
|
}
|
||||||
case "structLiteral": {
|
case "structLiteral": {
|
||||||
const fields = expr.fields.map<[Identifier, Expr]>(([name, expr]) => [
|
const fields = expr.fields.map<[Identifier, Expr]>(([name, expr]) => [
|
||||||
name,
|
name,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue