Strongly typed AST

This commit is contained in:
nora 2023-07-31 16:17:56 +02:00
parent 903fe75747
commit 5f191c72cc
7 changed files with 355 additions and 241 deletions

View file

@ -1,88 +1,115 @@
import { Span } from "./error"; import { Span } from "./error";
import { LitIntType } from "./lexer"; import { LitIntType } from "./lexer";
export type Ast = { export type Ast<P extends Phase> = {
rootItems: Item[]; rootItems: Item<P>[];
typeckResults?: TypeckResults; typeckResults?: TypeckResults;
itemsById: Map<ItemId, Item>; itemsById: Map<ItemId, Item<P>>;
packageName: string; packageName: string;
}; };
export type Identifier = { export type Phase = {
res: unknown;
};
type NoRes = object;
type HasRes = { res: Resolution };
export type Parsed = {
res: NoRes;
};
export type Built = {
res: NoRes;
};
export type Resolved = {
res: HasRes;
};
export type Typecked = {
res: HasRes;
};
export type AnyPhase = {
res: NoRes | HasRes;
};
export type Ident = {
name: string; name: string;
span: Span; span: Span;
res?: Resolution;
}; };
export type IdentWithRes<P extends Phase> = {
name: string;
span: Span;
} & P["res"];
export type ItemId = number; export type ItemId = number;
export type ItemKind = export type ItemKind<P extends Phase> =
| { | {
kind: "function"; kind: "function";
node: FunctionDef; node: FunctionDef<P>;
} }
| { | {
kind: "type"; kind: "type";
node: TypeDef; node: TypeDef<P>;
} }
| { | {
kind: "import"; kind: "import";
node: ImportDef; node: ImportDef<P>;
} }
| { | {
kind: "mod"; kind: "mod";
node: ModItem; node: ModItem<P>;
}; };
export type Item = ItemKind & { export type Item<P extends Phase> = ItemKind<P> & {
span: Span; span: Span;
id: ItemId; id: ItemId;
defPath?: string[]; defPath?: string[];
}; };
export type FunctionDef = { export type FunctionDef<P extends Phase> = {
name: string; name: string;
params: FunctionArg[]; params: FunctionArg<P>[];
body: Expr; body: Expr<P>;
returnType?: Type; returnType?: Type<P>;
ty?: TyFn; ty?: TyFn;
}; };
export type FunctionArg = { export type FunctionArg<P extends Phase> = {
name: string; name: string;
type: Type; type: Type<P>;
span: Span; span: Span;
}; };
export type TypeDef = { export type TypeDef<P extends Phase> = {
name: string; name: string;
fields: FieldDef[]; fields: FieldDef<P>[];
ty?: TyStruct; ty?: TyStruct;
}; };
export type FieldDef = { export type FieldDef<P extends Phase> = {
name: Identifier; name: Ident;
type: Type; type: Type<P>;
}; };
export type ImportDef = { export type ImportDef<P extends Phase> = {
module: StringLiteral; module: StringLiteral;
func: StringLiteral; func: StringLiteral;
name: string; name: string;
params: FunctionArg[]; params: FunctionArg<P>[];
returnType?: Type; returnType?: Type<P>;
ty?: TyFn; ty?: TyFn;
}; };
export type ModItem = { export type ModItem<P extends Phase> = {
name: string; name: string;
modKind: ModItemKind; modKind: ModItemKind<P>;
}; };
export type ModItemKind = export type ModItemKind<P extends Phase> =
| { | {
kind: "inline"; kind: "inline";
contents: Item[]; contents: Item<P>[];
} }
| { | {
kind: "extern"; kind: "extern";
@ -90,11 +117,11 @@ export type ModItemKind =
export type ExprEmpty = { kind: "empty" }; export type ExprEmpty = { kind: "empty" };
export type ExprLet = { export type ExprLet<P extends Phase> = {
kind: "let"; kind: "let";
name: Identifier; name: Ident;
type?: Type; type?: Type<P>;
rhs: Expr; rhs: Expr<P>;
// IMPORTANT: This is (sadly) shared with ExprBlock. // IMPORTANT: This is (sadly) shared with ExprBlock.
// TODO: Stop this sharing and just store the stack of blocks in typeck. // TODO: Stop this sharing and just store the stack of blocks in typeck.
local?: LocalInfo; local?: LocalInfo;
@ -102,15 +129,15 @@ export type ExprLet = {
// A bit like ExprBinary except there are restrictions // A bit like ExprBinary except there are restrictions
// on the LHS and precedence is unrestricted. // on the LHS and precedence is unrestricted.
export type ExprAssign = { export type ExprAssign<P extends Phase> = {
kind: "assign"; kind: "assign";
lhs: Expr; lhs: Expr<P>;
rhs: Expr; rhs: Expr<P>;
}; };
export type ExprBlock = { export type ExprBlock<P extends Phase> = {
kind: "block"; kind: "block";
exprs: Expr[]; exprs: Expr<P>[];
// IMPORTANT: This is (sadly) shared with ExprLet. // IMPORTANT: This is (sadly) shared with ExprLet.
locals?: LocalInfo[]; locals?: LocalInfo[];
}; };
@ -120,9 +147,9 @@ export type ExprLiteral = {
value: Literal; value: Literal;
}; };
export type ExprIdent = { export type ExprIdent<P extends Phase> = {
kind: "ident"; kind: "ident";
value: Identifier; value: IdentWithRes<P>;
}; };
/** /**
@ -139,28 +166,28 @@ export type ExprPath = {
res: Resolution; res: Resolution;
}; };
export type ExprBinary = { export type ExprBinary<P extends Phase> = {
kind: "binary"; kind: "binary";
binaryKind: BinaryKind; binaryKind: BinaryKind;
lhs: Expr; lhs: Expr<P>;
rhs: Expr; rhs: Expr<P>;
}; };
export type ExprUnary = { export type ExprUnary<P extends Phase> = {
kind: "unary"; kind: "unary";
unaryKind: UnaryKind; unaryKind: UnaryKind;
rhs: Expr; rhs: Expr<P>;
}; };
export type ExprCall = { export type ExprCall<P extends Phase> = {
kind: "call"; kind: "call";
lhs: Expr; lhs: Expr<P>;
args: Expr[]; args: Expr<P>[];
}; };
export type ExprFieldAccess = { export type ExprFieldAccess<P extends Phase> = {
kind: "fieldAccess"; kind: "fieldAccess";
lhs: Expr; lhs: Expr<P>;
field: { field: {
value: string | number; value: string | number;
span: Span; span: Span;
@ -168,18 +195,18 @@ export type ExprFieldAccess = {
}; };
}; };
export type ExprIf = { export type ExprIf<P extends Phase> = {
kind: "if"; kind: "if";
cond: Expr; cond: Expr<P>;
then: Expr; then: Expr<P>;
else?: Expr; else?: Expr<P>;
}; };
export type LoopId = number; export type LoopId = number;
export type ExprLoop = { export type ExprLoop<P extends Phase> = {
kind: "loop"; kind: "loop";
body: Expr; body: Expr<P>;
loopId: LoopId; loopId: LoopId;
}; };
@ -188,36 +215,36 @@ export type ExprBreak = {
target?: LoopId; target?: LoopId;
}; };
export type ExprStructLiteral = { export type ExprStructLiteral<P extends Phase> = {
kind: "structLiteral"; kind: "structLiteral";
name: Identifier; name: IdentWithRes<P>;
fields: [Identifier, Expr][]; fields: [Ident, Expr<P>][];
}; };
export type TupleLiteral = { export type TupleLiteral<P extends Phase> = {
kind: "tupleLiteral"; kind: "tupleLiteral";
fields: Expr[]; fields: Expr<P>[];
}; };
export type ExprKind = export type ExprKind<P extends Phase> =
| ExprEmpty | ExprEmpty
| ExprLet | ExprLet<P>
| ExprAssign | ExprAssign<P>
| ExprBlock | ExprBlock<P>
| ExprLiteral | ExprLiteral
| ExprIdent | ExprIdent<P>
| ExprPath | ExprPath
| ExprBinary | ExprBinary<P>
| ExprUnary | ExprUnary<P>
| ExprCall | ExprCall<P>
| ExprFieldAccess | ExprFieldAccess<P>
| ExprIf | ExprIf<P>
| ExprLoop | ExprLoop<P>
| ExprBreak | ExprBreak
| ExprStructLiteral | ExprStructLiteral<P>
| TupleLiteral; | TupleLiteral<P>;
export type Expr = ExprKind & { export type Expr<P extends Phase> = ExprKind<P> & {
span: Span; span: Span;
ty?: Ty; ty?: Ty;
}; };
@ -291,22 +318,22 @@ export function binaryExprPrecedenceClass(k: BinaryKind): number {
export type UnaryKind = "!" | "-"; export type UnaryKind = "!" | "-";
export const UNARY_KINDS: UnaryKind[] = ["!", "-"]; export const UNARY_KINDS: UnaryKind[] = ["!", "-"];
export type TypeKind = export type TypeKind<P extends Phase> =
| { | {
kind: "ident"; kind: "ident";
value: Identifier; value: IdentWithRes<P>;
} }
| { | {
kind: "list"; kind: "list";
elem: Type; elem: Type<P>;
} }
| { | {
kind: "tuple"; kind: "tuple";
elems: Type[]; elems: Type<P>[];
} }
| { kind: "never" }; | { kind: "never" };
export type Type = TypeKind & { export type Type<P extends Phase> = TypeKind<P> & {
span: Span; span: Span;
ty?: Ty; ty?: Ty;
}; };
@ -446,60 +473,68 @@ export type TypeckResults = {
// folders // folders
export type FoldFn<T> = (value: T) => T; export type FoldFn<From, To> = (value: From) => To;
export type Folder = { export type Folder<From extends Phase, To extends Phase> = {
ast: () => Ast; newItemsById: Map<ItemId, Item<To>>;
/** /**
* This should not be overridden. * This should not be overridden.
*/ */
item: FoldFn<Item>; item: FoldFn<Item<From>, Item<To>>;
itemInner: FoldFn<Item>; itemInner: FoldFn<Item<From>, Item<To>>;
expr: FoldFn<Expr>; expr: FoldFn<Expr<From>, Expr<To>>;
ident: FoldFn<Identifier>; ident: FoldFn<IdentWithRes<From>, IdentWithRes<To>>;
type: FoldFn<Type>; type: FoldFn<Type<From>, Type<To>>;
};
type ItemFolder<From extends Phase, To extends Phase> = {
newItemsById: Map<ItemId, Item<To>>;
item: FoldFn<Item<From>, Item<To>>;
itemInner: FoldFn<Item<From>, Item<To>>;
}; };
const ITEM_DEFAULT = Symbol("item must not be overriden"); const ITEM_DEFAULT = Symbol("item must not be overriden");
export const DEFAULT_FOLDER: Folder = { export function mkDefaultFolder<
ast() { From extends Phase,
throw new Error("folders need to implement `ast`"); To extends Phase
}, >(): ItemFolder<From, To> {
item(item) { const folder: ItemFolder<From, To> = {
const newItem = this.itemInner(item); newItemsById: new Map(),
this.ast().itemsById.set(item.id, newItem); item(item) {
return newItem; const newItem = this.itemInner(item);
}, this.newItemsById.set(item.id, newItem);
itemInner(item) { return newItem;
return superFoldItem(item, this); },
}, itemInner(_item) {
expr(expr) { throw new Error("unimplemented");
return superFoldExpr(expr, this); },
}, };
ident(ident) { (folder.item as any)[ITEM_DEFAULT] = ITEM_DEFAULT;
return ident;
},
type(type) {
return superFoldType(type, this);
},
};
(DEFAULT_FOLDER.item as any)[ITEM_DEFAULT] = ITEM_DEFAULT;
export function foldAst(ast: Ast, folder: Folder): Ast { return folder;
}
export function foldAst<From extends Phase, To extends Phase>(
ast: Ast<From>,
folder: Folder<From, To>
): Ast<To> {
if ((folder.item as any)[ITEM_DEFAULT] !== ITEM_DEFAULT) { if ((folder.item as any)[ITEM_DEFAULT] !== ITEM_DEFAULT) {
throw new Error("must not override `item` on folders"); throw new Error("must not override `item` on folders");
} }
return { return {
rootItems: ast.rootItems.map((item) => folder.item(item)), rootItems: ast.rootItems.map((item) => folder.item(item)),
itemsById: ast.itemsById, itemsById: folder.newItemsById,
typeckResults: ast.typeckResults, typeckResults: ast.typeckResults,
packageName: ast.packageName, packageName: ast.packageName,
}; };
} }
export function superFoldItem(item: Item, folder: Folder): Item { export function superFoldItem<From extends Phase, To extends Phase>(
item: Item<From>,
folder: Folder<From, To>
): Item<To> {
switch (item.kind) { switch (item.kind) {
case "function": { case "function": {
const args = item.node.params.map(({ name, type, span }) => ({ const args = item.node.params.map(({ name, type, span }) => ({
@ -550,7 +585,7 @@ export function superFoldItem(item: Item, folder: Folder): Item {
}; };
} }
case "mod": { case "mod": {
let kind: ModItemKind; let kind: ModItemKind<To>;
const { modKind: itemKind } = item.node; const { modKind: itemKind } = item.node;
switch (itemKind.kind) { switch (itemKind.kind) {
case "inline": case "inline":
@ -576,7 +611,10 @@ export function superFoldItem(item: Item, folder: Folder): Item {
} }
} }
export function superFoldExpr(expr: Expr, folder: Folder): Expr { export function superFoldExpr<From extends Phase, To extends Phase>(
expr: Expr<From>,
folder: Folder<From, To>
): Expr<To> {
const span = expr.span; const span = expr.span;
switch (expr.kind) { switch (expr.kind) {
case "empty": { case "empty": {
@ -687,7 +725,10 @@ export function superFoldExpr(expr: Expr, folder: Folder): Expr {
} }
} }
export function superFoldType(type: Type, folder: Folder): Type { export function superFoldType<From extends Phase, To extends Phase>(
type: Type<From>,
folder: Folder<From, To>
): Type<To> {
const span = type.span; const span = type.span;
switch (type.kind) { switch (type.kind) {
case "ident": { case "ident": {

View file

@ -9,6 +9,7 @@ import { writeModuleWatToString } from "./wasm/wat";
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import { exec } from "child_process"; import { exec } from "child_process";
import { Ast, Built, Typecked } from "./ast";
const INPUT = ` const INPUT = `
function main() = ( function main() = (
@ -55,7 +56,7 @@ function main() {
console.log("-----TOKENS------------"); console.log("-----TOKENS------------");
console.log(tokens); console.log(tokens);
const ast = parse(packageName, tokens); const ast: Ast<Built> = parse(packageName, tokens);
console.log("-----AST---------------"); console.log("-----AST---------------");
console.dir(ast.rootItems, { depth: 50 }); console.dir(ast.rootItems, { depth: 50 });
@ -70,7 +71,7 @@ function main() {
console.log(resolvedPrinted); console.log(resolvedPrinted);
console.log("-----AST typecked------"); console.log("-----AST typecked------");
const typecked = typeck(resolved); const typecked: Ast<Typecked> = typeck(resolved);
const typeckPrinted = printAst(typecked); const typeckPrinted = printAst(typecked);
console.log(typeckPrinted); console.log(typeckPrinted);

View file

@ -10,6 +10,7 @@ import {
Ty, Ty,
TyFn, TyFn,
TyTuple, TyTuple,
Typecked,
varUnreachable, varUnreachable,
} from "./ast"; } from "./ast";
import { ComplexMap, encodeUtf8, unwrap } from "./utils"; import { ComplexMap, encodeUtf8, unwrap } from "./utils";
@ -38,7 +39,7 @@ export type Context = {
funcTypes: ComplexMap<wasm.FuncType, wasm.TypeIdx>; funcTypes: ComplexMap<wasm.FuncType, wasm.TypeIdx>;
reservedHeapMemoryStart: number; reservedHeapMemoryStart: number;
funcIndices: ComplexMap<Resolution, FuncOrImport>; funcIndices: ComplexMap<Resolution, FuncOrImport>;
ast: Ast; ast: Ast<Typecked>;
relocations: Relocation[]; relocations: Relocation[];
}; };
@ -88,7 +89,7 @@ function appendData(cx: Context, newData: Uint8Array): number {
} }
} }
export function lower(ast: Ast): wasm.Module { export function lower(ast: Ast<Typecked>): wasm.Module {
const mod: wasm.Module = { const mod: wasm.Module = {
types: [], types: [],
funcs: [], funcs: [],
@ -122,7 +123,7 @@ export function lower(ast: Ast): wasm.Module {
relocations: [], relocations: [],
}; };
function lowerMod(items: Item[]) { function lowerMod(items: Item<Typecked>[]) {
items.forEach((item) => { items.forEach((item) => {
switch (item.kind) { switch (item.kind) {
case "function": { case "function": {
@ -172,7 +173,11 @@ export function lower(ast: Ast): wasm.Module {
return mod; return mod;
} }
function lowerImport(cx: Context, item: Item, def: ImportDef) { function lowerImport(
cx: Context,
item: Item<Typecked>,
def: ImportDef<Typecked>
) {
const existing = cx.mod.imports.findIndex( const existing = cx.mod.imports.findIndex(
(imp) => imp.module === def.module.value && imp.name === def.func.value (imp) => imp.module === def.module.value && imp.name === def.func.value
); );
@ -201,8 +206,8 @@ function lowerImport(cx: Context, item: Item, def: ImportDef) {
type FuncContext = { type FuncContext = {
cx: Context; cx: Context;
item: Item; item: Item<Typecked>;
func: FunctionDef; func: FunctionDef<Typecked>;
wasmType: wasm.FuncType; wasmType: wasm.FuncType;
wasm: wasm.Func; wasm: wasm.Func;
varLocations: VarLocation[]; varLocations: VarLocation[];
@ -216,7 +221,11 @@ type ArgRetAbi = wasm.ValType[];
type VarLocation = { localIdx: number; types: wasm.ValType[] }; type VarLocation = { localIdx: number; types: wasm.ValType[] };
function lowerFunc(cx: Context, item: Item, func: FunctionDef) { function lowerFunc(
cx: Context,
item: Item<Typecked>,
func: FunctionDef<Typecked>
) {
const abi = computeAbi(func.ty!); const abi = computeAbi(func.ty!);
const { type: wasmType, paramLocations } = wasmTypeForAbi(abi); const { type: wasmType, paramLocations } = wasmTypeForAbi(abi);
const type = internFuncType(cx, wasmType); const type = internFuncType(cx, wasmType);
@ -255,7 +264,11 @@ Expression lowering.
- the result of an expression evaluation is stored on the top of the stack - the result of an expression evaluation is stored on the top of the stack
*/ */
function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) { function lowerExpr(
fcx: FuncContext,
instrs: wasm.Instr[],
expr: Expr<Typecked>
) {
const ty = expr.ty!; const ty = expr.ty!;
exprKind: switch (expr.kind) { exprKind: switch (expr.kind) {
@ -284,7 +297,7 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
const { lhs } = expr; const { lhs } = expr;
switch (lhs.kind) { switch (lhs.kind) {
case "ident": { case "ident": {
const res = lhs.value.res!; const res = lhs.value.res;
switch (res.kind) { switch (res.kind) {
case "local": { case "local": {
@ -355,7 +368,7 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
} }
case "path": case "path":
case "ident": { case "ident": {
const res = expr.kind === "ident" ? expr.value.res! : expr.res; const res = expr.kind === "ident" ? expr.value.res : expr.res;
switch (res.kind) { switch (res.kind) {
case "local": { case "local": {
@ -502,8 +515,7 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
todo("non constant calls"); todo("non constant calls");
} }
const res = const res = expr.lhs.kind === "ident" ? expr.lhs.value.res : expr.lhs.res;
expr.lhs.kind === "ident" ? expr.lhs.value.res! : expr.lhs.res;
if (res.kind === "builtin") { if (res.kind === "builtin") {
switch (res.name) { switch (res.name) {
@ -575,7 +587,7 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
// TODO: Actually do this instead of being naive. // TODO: Actually do this instead of being naive.
const _isPlace = (expr: Expr) => const _isPlace = (expr: Expr<Typecked>) =>
expr.kind === "ident" || expr.kind === "fieldAccess"; expr.kind === "ident" || expr.kind === "fieldAccess";
lowerExpr(fcx, instrs, expr.lhs); lowerExpr(fcx, instrs, expr.lhs);
@ -711,7 +723,7 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
function lowerExprBlockBody( function lowerExprBlockBody(
fcx: FuncContext, fcx: FuncContext,
expr: ExprBlock & Expr expr: ExprBlock<Typecked> & Expr<Typecked>
): wasm.Instr[] { ): wasm.Instr[] {
fcx.currentBlockDepth++; fcx.currentBlockDepth++;
const innerInstrs: wasm.Instr[] = []; const innerInstrs: wasm.Instr[] = [];
@ -853,7 +865,7 @@ 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<Typecked>) {
const { mod } = cx; const { mod } = cx;
const mainCall: wasm.Instr = { kind: "call", func: 9999999 }; const mainCall: wasm.Instr = { kind: "call", func: 9999999 };

View file

@ -4,7 +4,7 @@ import {
Ast, Ast,
BinaryKind, BinaryKind,
COMPARISON_KINDS, COMPARISON_KINDS,
DEFAULT_FOLDER, mkDefaultFolder,
Expr, Expr,
ExprLoop, ExprLoop,
ExprStructLiteral, ExprStructLiteral,
@ -12,7 +12,7 @@ import {
Folder, Folder,
FunctionArg, FunctionArg,
FunctionDef, FunctionDef,
Identifier, Ident,
ImportDef, ImportDef,
Item, Item,
LOGICAL_KINDS, LOGICAL_KINDS,
@ -25,6 +25,8 @@ import {
foldAst, foldAst,
superFoldExpr, superFoldExpr,
superFoldItem, superFoldItem,
Built,
Parsed,
} from "./ast"; } from "./ast";
import { CompilerError, Span, spanMerge } from "./error"; import { CompilerError, Span, spanMerge } from "./error";
import { BaseToken, Token, TokenIdent, TokenLitString } from "./lexer"; import { BaseToken, Token, TokenIdent, TokenLitString } from "./lexer";
@ -32,8 +34,8 @@ import { Ids } from "./utils";
type Parser<T> = (t: Token[]) => [Token[], T]; type Parser<T> = (t: Token[]) => [Token[], T];
export function parse(packageName: string, t: Token[]): Ast { export function parse(packageName: string, t: Token[]): Ast<Built> {
const items: Item[] = []; const items: Item<Parsed>[] = [];
while (t.length > 0) { while (t.length > 0) {
let item; let item;
@ -48,7 +50,7 @@ export function parse(packageName: string, t: Token[]): Ast {
return ast; return ast;
} }
function parseItem(t: Token[]): [Token[], Item] { function parseItem(t: Token[]): [Token[], Item<Parsed>] {
let tok; let tok;
[t, tok] = next(t); [t, tok] = next(t);
if (tok.kind === "function") { if (tok.kind === "function") {
@ -62,7 +64,7 @@ function parseItem(t: Token[]): [Token[], Item] {
[t] = expectNext(t, ";"); [t] = expectNext(t, ";");
const def: FunctionDef = { const def: FunctionDef<Parsed> = {
...sig, ...sig,
body, body,
}; };
@ -84,7 +86,7 @@ function parseItem(t: Token[]): [Token[], Item] {
[t] = expectNext(t, "{"); [t] = expectNext(t, "{");
let fields; let fields;
[t, fields] = parseCommaSeparatedList<FieldDef>(t, "}", (t) => { [t, fields] = parseCommaSeparatedList<FieldDef<Parsed>>(t, "}", (t) => {
let name; let name;
[t, name] = expectNext<TokenIdent>(t, "identifier"); [t, name] = expectNext<TokenIdent>(t, "identifier");
[t] = expectNext(t, ":"); [t] = expectNext(t, ":");
@ -104,7 +106,7 @@ function parseItem(t: Token[]): [Token[], Item] {
[t] = expectNext(t, ";"); [t] = expectNext(t, ";");
const def: TypeDef = { const def: TypeDef<Parsed> = {
name: name.ident, name: name.ident,
fields, fields,
}; };
@ -123,7 +125,7 @@ function parseItem(t: Token[]): [Token[], Item] {
[t] = expectNext(t, ";"); [t] = expectNext(t, ";");
const def: ImportDef = { const def: ImportDef<Parsed> = {
module: { kind: "str", value: module.value, span: module.span }, module: { kind: "str", value: module.value, span: module.span },
func: { kind: "str", value: func.value, span: func.span }, func: { kind: "str", value: func.value, span: func.span },
...sig, ...sig,
@ -135,7 +137,7 @@ function parseItem(t: Token[]): [Token[], Item] {
let name; let name;
[t, name] = expectNext<TokenIdent>(t, "identifier"); [t, name] = expectNext<TokenIdent>(t, "identifier");
const node: ModItem = { const node: ModItem<Parsed> = {
name: name.ident, name: name.ident,
modKind: { kind: "extern" }, modKind: { kind: "extern" },
}; };
@ -149,7 +151,7 @@ function parseItem(t: Token[]): [Token[], Item] {
[t] = expectNext(t, "("); [t] = expectNext(t, "(");
const contents: Item[] = []; const contents: Item<Parsed>[] = [];
while (next(t)[1].kind !== ")") { while (next(t)[1].kind !== ")") {
let item; let item;
@ -161,7 +163,7 @@ function parseItem(t: Token[]): [Token[], Item] {
[t] = expectNext(t, ")"); [t] = expectNext(t, ")");
[t] = expectNext(t, ";"); [t] = expectNext(t, ";");
const node: ModItem = { const node: ModItem<Parsed> = {
name: name.ident, name: name.ident,
modKind: { kind: "inline", contents }, modKind: { kind: "inline", contents },
}; };
@ -174,8 +176,8 @@ function parseItem(t: Token[]): [Token[], Item] {
type FunctionSig = { type FunctionSig = {
name: string; name: string;
params: FunctionArg[]; params: FunctionArg<Parsed>[];
returnType?: Type; returnType?: Type<Parsed>;
}; };
function parseFunctionSig(t: Token[]): [Token[], FunctionSig] { function parseFunctionSig(t: Token[]): [Token[], FunctionSig] {
@ -184,7 +186,7 @@ function parseFunctionSig(t: Token[]): [Token[], FunctionSig] {
[t] = expectNext(t, "("); [t] = expectNext(t, "(");
let params: FunctionArg[]; let params: FunctionArg<Parsed>[];
[t, params] = parseCommaSeparatedList(t, ")", (t) => { [t, params] = parseCommaSeparatedList(t, ")", (t) => {
let name; let name;
[t, name] = expectNext<TokenIdent & { span: Span }>(t, "identifier"); [t, name] = expectNext<TokenIdent & { span: Span }>(t, "identifier");
@ -205,7 +207,7 @@ function parseFunctionSig(t: Token[]): [Token[], FunctionSig] {
return [t, { name: name.ident, params, returnType }]; return [t, { name: name.ident, params, returnType }];
} }
function parseExpr(t: Token[]): [Token[], Expr] { function parseExpr(t: Token[]): [Token[], Expr<Parsed>] {
/* /*
EXPR = ASSIGNMENT EXPR = ASSIGNMENT
@ -237,16 +239,21 @@ function parseExpr(t: Token[]): [Token[], Expr] {
return parseExprAssignment(t); return parseExprAssignment(t);
} }
function mkBinaryExpr(lhs: Expr, rhs: Expr, span: Span, kind: string): Expr { function mkBinaryExpr(
lhs: Expr<Parsed>,
rhs: Expr<Parsed>,
span: Span,
kind: string
): Expr<Parsed> {
return { kind: "binary", binaryKind: kind as BinaryKind, lhs, rhs, span }; return { kind: "binary", binaryKind: kind as BinaryKind, lhs, rhs, span };
} }
function mkParserExprBinary( function mkParserExprBinary(
lower: Parser<Expr>, lower: Parser<Expr<Parsed>>,
kinds: string[], kinds: string[],
mkExpr = mkBinaryExpr mkExpr = mkBinaryExpr
): Parser<Expr> { ): Parser<Expr<Parsed>> {
function parser(t: Token[]): [Token[], Expr] { function parser(t: Token[]): [Token[], Expr<Parsed>] {
let lhs; let lhs;
[t, lhs] = lower(t); [t, lhs] = lower(t);
@ -288,7 +295,7 @@ const parseExprAssignment = mkParserExprBinary(
(lhs, rhs, span) => ({ kind: "assign", lhs, rhs, span }) (lhs, rhs, span) => ({ kind: "assign", lhs, rhs, span })
); );
function parseExprUnary(t: Token[]): [Token[], Expr] { function parseExprUnary(t: Token[]): [Token[], Expr<Parsed>] {
const [, peak] = next(t); const [, peak] = next(t);
if (peak.kind in UNARY_KINDS) { if (peak.kind in UNARY_KINDS) {
let rhs; let rhs;
@ -306,8 +313,8 @@ function parseExprUnary(t: Token[]): [Token[], Expr] {
return parseExprCall(t); return parseExprCall(t);
} }
function parseExprCall(t: Token[]): [Token[], Expr] { function parseExprCall(t: Token[]): [Token[], Expr<Parsed>] {
let lhs: Expr; let lhs: Expr<Parsed>;
[t, lhs] = parseExprAtom(t); [t, lhs] = parseExprAtom(t);
while (next(t)[1].kind === "(" || next(t)[1].kind === ".") { while (next(t)[1].kind === "(" || next(t)[1].kind === ".") {
@ -343,13 +350,13 @@ function parseExprCall(t: Token[]): [Token[], Expr] {
return [t, lhs]; return [t, lhs];
} }
function parseExprAtom(startT: Token[]): [Token[], Expr] { function parseExprAtom(startT: Token[]): [Token[], Expr<Parsed>] {
// eslint-disable-next-line prefer-const // eslint-disable-next-line prefer-const
let [t, tok] = next(startT); let [t, tok] = next(startT);
const span = tok.span; const span = tok.span;
if (tok.kind === "(") { if (tok.kind === "(") {
let expr: Expr; let expr: Expr<Parsed>;
[t, expr] = parseExpr(t); [t, expr] = parseExpr(t);
// This could be a block or a tuple literal. We can only know after // This could be a block or a tuple literal. We can only know after
@ -447,7 +454,7 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] {
let rhs; let rhs;
[t, rhs] = parseExpr(t); [t, rhs] = parseExpr(t);
const nameIdent: Identifier = { name: name.ident, span: name.span }; const nameIdent: Ident = { name: name.ident, span: name.span };
return [ return [
t, t,
@ -493,11 +500,13 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] {
return [startT, { kind: "empty", span }]; return [startT, { kind: "empty", span }];
} }
function parseStructInit(t: Token[]): [Token[], ExprStructLiteral["fields"]] { function parseStructInit(
t: Token[]
): [Token[], ExprStructLiteral<Parsed>["fields"]] {
[t] = expectNext(t, "{"); [t] = expectNext(t, "{");
let fields; let fields;
[t, fields] = parseCommaSeparatedList<[Identifier, Expr]>(t, "}", (t) => { [t, fields] = parseCommaSeparatedList<[Ident, Expr<Parsed>]>(t, "}", (t) => {
let name; let name;
[t, name] = expectNext<TokenIdent>(t, "identifier"); [t, name] = expectNext<TokenIdent>(t, "identifier");
[t] = expectNext(t, ":"); [t] = expectNext(t, ":");
@ -510,7 +519,7 @@ function parseStructInit(t: Token[]): [Token[], ExprStructLiteral["fields"]] {
return [t, fields]; return [t, fields];
} }
function parseType(t: Token[]): [Token[], Type] { function parseType(t: Token[]): [Token[], Type<Parsed>] {
let tok; let tok;
[t, tok] = next(t); [t, tok] = next(t);
const span = tok.span; const span = tok.span;
@ -649,22 +658,19 @@ function unexpectedToken(token: Token, expected: string): never {
throw new CompilerError(`unexpected token, expected ${expected}`, token.span); throw new CompilerError(`unexpected token, expected ${expected}`, token.span);
} }
function validateAst(ast: Ast) { function validateAst(ast: Ast<Built>) {
const seenItemIds = new Set(); const seenItemIds = new Set();
const validator: Folder = { const validator: Folder<Built, Built> = {
...DEFAULT_FOLDER, ...mkDefaultFolder(),
ast() { itemInner(item: Item<Built>): Item<Built> {
return ast;
},
itemInner(item: Item): Item {
if (seenItemIds.has(item.id)) { if (seenItemIds.has(item.id)) {
throw new Error(`duplicate item id: ${item.id} for ${item.node.name}`); throw new Error(`duplicate item id: ${item.id} for ${item.node.name}`);
} }
seenItemIds.add(item.id); seenItemIds.add(item.id);
return superFoldItem(item, this); return superFoldItem(item, this);
}, },
expr(expr: Expr): Expr { expr(expr: Expr<Built>): Expr<Built> {
if (expr.kind === "block") { if (expr.kind === "block") {
expr.exprs.forEach((inner) => { expr.exprs.forEach((inner) => {
if (inner.kind === "let") { if (inner.kind === "let") {
@ -680,7 +686,7 @@ function validateAst(ast: Ast) {
} else if (expr.kind === "let") { } else if (expr.kind === "let") {
throw new CompilerError("let is only allowed in blocks", expr.span); throw new CompilerError("let is only allowed in blocks", expr.span);
} else if (expr.kind === "binary") { } else if (expr.kind === "binary") {
const checkPrecedence = (inner: Expr, side: string) => { const checkPrecedence = (inner: Expr<Built>, side: string) => {
if (inner.kind === "binary") { if (inner.kind === "binary") {
const ourClass = binaryExprPrecedenceClass(expr.binaryKind); const ourClass = binaryExprPrecedenceClass(expr.binaryKind);
const innerClass = binaryExprPrecedenceClass(inner.binaryKind); const innerClass = binaryExprPrecedenceClass(inner.binaryKind);
@ -702,40 +708,49 @@ function validateAst(ast: Ast) {
return superFoldExpr(expr, this); return superFoldExpr(expr, this);
} }
}, },
ident(ident) {
return ident;
},
type(type) {
return type;
},
}; };
foldAst(ast, validator); foldAst(ast, validator);
} }
function buildAst(packageName: string, rootItems: Item[]): Ast { function buildAst(packageName: string, rootItems: Item<Parsed>[]): Ast<Built> {
const itemId = new Ids(); const itemId = new Ids();
const loopId = new Ids(); const loopId = new Ids();
const ast: Ast = { const ast: Ast<Built> = {
rootItems, rootItems,
itemsById: new Map(), itemsById: new Map(),
packageName, packageName,
}; };
const assigner: Folder = { const assigner: Folder<Parsed, Built> = {
...DEFAULT_FOLDER, ...mkDefaultFolder(),
ast() { itemInner(item: Item<Parsed>): Item<Built> {
return ast;
},
itemInner(item: Item): Item {
const id = itemId.next(); const id = itemId.next();
ast.itemsById.set(id, item); ast.itemsById.set(id, item);
return { ...superFoldItem(item, this), id }; return { ...superFoldItem(item, this), id };
}, },
expr(expr: Expr): Expr { expr(expr: Expr<Parsed>): Expr<Built> {
if (expr.kind === "loop") { if (expr.kind === "loop") {
return { return {
...(superFoldExpr(expr, this) as ExprLoop & Expr), ...(superFoldExpr(expr, this) as ExprLoop<Built> & Expr<Built>),
loopId: loopId.next(), loopId: loopId.next(),
}; };
} }
return superFoldExpr(expr, this); return superFoldExpr(expr, this);
}, },
ident(ident) {
return ident;
},
type(type) {
return type;
},
}; };
return foldAst(ast, assigner); return foldAst(ast, assigner);

View file

@ -1,8 +1,9 @@
import { import {
AnyPhase,
Ast, Ast,
Expr, Expr,
FunctionDef, FunctionDef,
Identifier, IdentWithRes,
ImportDef, ImportDef,
Item, Item,
ModItem, ModItem,
@ -14,7 +15,7 @@ import {
tyIsUnit, tyIsUnit,
} from "./ast"; } from "./ast";
export function printAst(ast: Ast): string { export function printAst(ast: Ast<AnyPhase>): string {
return ast.rootItems.map(printItem).join("\n"); return ast.rootItems.map(printItem).join("\n");
} }
@ -22,7 +23,7 @@ function printStringLiteral(lit: StringLiteral): string {
return `"${lit.value.replace("\n", "\\n")}"`; return `"${lit.value.replace("\n", "\\n")}"`;
} }
function printItem(item: Item): string { function printItem(item: Item<AnyPhase>): string {
const id = `/*${item.id}*/ `; const id = `/*${item.id}*/ `;
switch (item.kind) { switch (item.kind) {
@ -41,7 +42,7 @@ function printItem(item: Item): string {
} }
} }
function printFunction(func: FunctionDef): string { function printFunction(func: FunctionDef<AnyPhase>): string {
const args = func.params const args = func.params
.map(({ name, type }) => `${name}: ${printType(type)}`) .map(({ name, type }) => `${name}: ${printType(type)}`)
.join(", "); .join(", ");
@ -49,7 +50,7 @@ function printFunction(func: FunctionDef): string {
return `function ${func.name}(${args})${ret} = ${printExpr(func.body, 0)};`; return `function ${func.name}(${args})${ret} = ${printExpr(func.body, 0)};`;
} }
function printTypeDef(type: TypeDef): string { function printTypeDef(type: TypeDef<AnyPhase>): string {
const fields = type.fields.map( const fields = type.fields.map(
({ name, type }) => `${ind(1)}${name.name}: ${printType(type)},` ({ name, type }) => `${ind(1)}${name.name}: ${printType(type)},`
); );
@ -60,7 +61,7 @@ function printTypeDef(type: TypeDef): string {
return `type ${type.name} = ${fieldPart};`; return `type ${type.name} = ${fieldPart};`;
} }
function printImportDef(def: ImportDef): string { function printImportDef(def: ImportDef<AnyPhase>): string {
const args = def.params const args = def.params
.map(({ name, type }) => `${name}: ${printType(type)}`) .map(({ name, type }) => `${name}: ${printType(type)}`)
.join(", "); .join(", ");
@ -71,7 +72,7 @@ function printImportDef(def: ImportDef): string {
)}(${args})${ret};`; )}(${args})${ret};`;
} }
function printMod(mod: ModItem): string { function printMod(mod: ModItem<AnyPhase>): string {
switch (mod.modKind.kind) { switch (mod.modKind.kind) {
case "inline": case "inline":
return `mod ${mod.name} (\n${mod.modKind.contents return `mod ${mod.name} (\n${mod.modKind.contents
@ -82,7 +83,7 @@ function printMod(mod: ModItem): string {
} }
} }
function printExpr(expr: Expr, indent: number): string { function printExpr(expr: Expr<AnyPhase>, indent: number): string {
switch (expr.kind) { switch (expr.kind) {
case "empty": { case "empty": {
return ""; return "";
@ -192,7 +193,7 @@ function printExpr(expr: Expr, indent: number): string {
} }
} }
function printType(type: Type): string { function printType(type: Type<AnyPhase>): string {
switch (type.kind) { switch (type.kind) {
case "ident": case "ident":
return printIdent(type.value); return printIdent(type.value);
@ -217,8 +218,8 @@ function printRes(res: Resolution): string {
} }
} }
function printIdent(ident: Identifier): string { function printIdent(ident: IdentWithRes<AnyPhase>): string {
const res = ident.res ? printRes(ident.res) : ""; const res = "res" in ident ? printRes(ident.res) : "";
return `${ident.name}${res}`; return `${ident.name}${res}`;
} }

View file

@ -1,18 +1,21 @@
import { import {
Ast, Ast,
BUILTINS, BUILTINS,
Built,
BuiltinName, BuiltinName,
DEFAULT_FOLDER,
Expr, Expr,
Folder, Folder,
Identifier, Ident,
Item, Item,
ItemId, ItemId,
LocalInfo, LocalInfo,
ModItem, ModItem,
Resolution, Resolution,
Resolved,
mkDefaultFolder,
superFoldExpr, superFoldExpr,
superFoldItem, superFoldItem,
superFoldType,
} from "./ast"; } from "./ast";
import { CompilerError, spanMerge, todo } from "./error"; import { CompilerError, spanMerge, todo } from "./error";
import { unwrap } from "./utils"; import { unwrap } from "./utils";
@ -20,13 +23,14 @@ import { unwrap } from "./utils";
const BUILTIN_SET = new Set<string>(BUILTINS); const BUILTIN_SET = new Set<string>(BUILTINS);
type Context = { type Context = {
ast: Ast; ast: Ast<Built>;
modContentsCache: Map<ItemId, Map<string, ItemId>>; modContentsCache: Map<ItemId, Map<string, ItemId>>;
newItemsById: Map<ItemId, Item<Resolved>>;
}; };
function resolveModItem( function resolveModItem(
cx: Context, cx: Context,
mod: ModItem, mod: ModItem<Built>,
modId: ItemId, modId: ItemId,
name: string name: string
): ItemId | undefined { ): ItemId | undefined {
@ -49,18 +53,26 @@ function resolveModItem(
} }
} }
export function resolve(ast: Ast): Ast { export function resolve(ast: Ast<Built>): Ast<Resolved> {
const cx: Context = { ast, modContentsCache: new Map() }; const cx: Context = {
ast,
modContentsCache: new Map(),
newItemsById: new Map(),
};
const rootItems = resolveModule(cx, [ast.packageName], ast.rootItems); const rootItems = resolveModule(cx, [ast.packageName], ast.rootItems);
return { ...ast, rootItems }; return {
itemsById: cx.newItemsById,
rootItems,
packageName: ast.packageName,
};
} }
function resolveModule( function resolveModule(
cx: Context, cx: Context,
modName: string[], modName: string[],
contents: Item[] contents: Item<Built>[]
): Item[] { ): Item<Resolved>[] {
const items = new Map<string, number>(); const items = new Map<string, number>();
contents.forEach((item) => { contents.forEach((item) => {
@ -85,7 +97,7 @@ function resolveModule(
} }
}; };
const resolveIdent = (ident: Identifier): Resolution => { const resolveIdent = (ident: Ident): Resolution => {
const lastIdx = scopes.length - 1; const lastIdx = scopes.length - 1;
for (let i = lastIdx; i >= 0; i--) { for (let i = lastIdx; i >= 0; i--) {
const candidate = scopes[i]; const candidate = scopes[i];
@ -115,11 +127,8 @@ function resolveModule(
const blockLocals: LocalInfo[][] = []; const blockLocals: LocalInfo[][] = [];
const resolver: Folder = { const resolver: Folder<Built, Resolved> = {
...DEFAULT_FOLDER, ...mkDefaultFolder(),
ast() {
return cx.ast;
},
itemInner(item) { itemInner(item) {
const defPath = [...modName, item.node.name]; const defPath = [...modName, item.node.name];
@ -178,7 +187,9 @@ function resolveModule(
const prevScopeLength = scopes.length; const prevScopeLength = scopes.length;
blockLocals.push([]); blockLocals.push([]);
const exprs = expr.exprs.map<Expr>((inner) => this.expr(inner)); const exprs = expr.exprs.map<Expr<Resolved>>((inner) =>
this.expr(inner)
);
scopes.length = prevScopeLength; scopes.length = prevScopeLength;
const locals = blockLocals.pop(); const locals = blockLocals.pop();
@ -263,6 +274,10 @@ function resolveModule(
const res = resolveIdent(ident); const res = resolveIdent(ident);
return { name: ident.name, span: ident.span, res }; return { name: ident.name, span: ident.span, res };
}, },
type(type) {
return superFoldType(type, this);
},
newItemsById: cx.newItemsById,
}; };
return contents.map((item) => resolver.item(item)); return contents.map((item) => resolver.item(item));

View file

@ -2,19 +2,21 @@ import {
Ast, Ast,
BuiltinName, BuiltinName,
COMPARISON_KINDS, COMPARISON_KINDS,
DEFAULT_FOLDER, mkDefaultFolder,
EQUALITY_KINDS, EQUALITY_KINDS,
Expr, Expr,
ExprBinary, ExprBinary,
ExprUnary, ExprUnary,
foldAst, foldAst,
Folder, Folder,
Identifier, Ident,
IdentWithRes,
ItemId, ItemId,
LOGICAL_KINDS, LOGICAL_KINDS,
LoopId, LoopId,
ModItemKind, ModItemKind,
Resolution, Resolution,
Resolved,
Ty, Ty,
TY_BOOL, TY_BOOL,
TY_I32, TY_I32,
@ -25,7 +27,9 @@ import {
TyFn, TyFn,
tyIsUnit, tyIsUnit,
Type, Type,
Typecked,
TyStruct, TyStruct,
Item,
} from "./ast"; } from "./ast";
import { CompilerError, Span } from "./error"; import { CompilerError, Span } from "./error";
import { printTy } from "./printer"; import { printTy } from "./printer";
@ -84,8 +88,8 @@ function typeOfBuiltinValue(name: BuiltinName, span: Span): Ty {
// TODO: Cleanup, maybe get the ident switch into this function because typeOfItem is unused. // TODO: Cleanup, maybe get the ident switch into this function because typeOfItem is unused.
function lowerAstTyBase( function lowerAstTyBase(
type: Type, type: Type<Resolved>,
lowerIdentTy: (ident: Identifier) => Ty, lowerIdentTy: (ident: IdentWithRes<Resolved>) => Ty,
typeOfItem: (index: number, cause: Span) => Ty typeOfItem: (index: number, cause: Span) => Ty
): Ty { ): Ty {
switch (type.kind) { switch (type.kind) {
@ -112,7 +116,7 @@ function lowerAstTyBase(
} }
} }
export function typeck(ast: Ast): Ast { export function typeck(ast: Ast<Resolved>): Ast<Typecked> {
const itemTys = new Map<number, Ty | null>(); const itemTys = new Map<number, Ty | null>();
function typeOfItem(index: ItemId, cause: Span): Ty { function typeOfItem(index: ItemId, cause: Span): Ty {
const item = unwrap(ast.itemsById.get(index)); const item = unwrap(ast.itemsById.get(index));
@ -165,11 +169,11 @@ export function typeck(ast: Ast): Ast {
} }
} }
function lowerAstTy(type: Type): Ty { function lowerAstTy(type: Type<Resolved>): Ty {
return lowerAstTyBase( return lowerAstTyBase(
type, type,
(ident) => { (ident) => {
const res = ident.res!; const res = ident.res;
switch (res.kind) { switch (res.kind) {
case "local": { case "local": {
throw new Error("Item type cannot refer to local variable"); throw new Error("Item type cannot refer to local variable");
@ -186,12 +190,9 @@ export function typeck(ast: Ast): Ast {
); );
} }
const checker: Folder = { const checker: Folder<Resolved, Typecked> = {
...DEFAULT_FOLDER, ...mkDefaultFolder(),
ast() { itemInner(item: Item<Resolved>): Item<Typecked> {
return ast;
},
itemInner(item) {
switch (item.kind) { switch (item.kind) {
case "function": { case "function": {
const fnTy = typeOfItem(item.id, item.span) as TyFn; const fnTy = typeOfItem(item.id, item.span) as TyFn;
@ -298,7 +299,7 @@ export function typeck(ast: Ast): Ast {
case "mod": { case "mod": {
switch (item.node.modKind.kind) { switch (item.node.modKind.kind) {
case "inline": { case "inline": {
const modKind: ModItemKind = { const modKind: ModItemKind<Typecked> = {
kind: "inline", kind: "inline",
contents: item.node.modKind.contents.map((item) => contents: item.node.modKind.contents.map((item) =>
this.item(item) this.item(item)
@ -323,6 +324,15 @@ export function typeck(ast: Ast): Ast {
} }
} }
}, },
expr(_expr) {
throw new Error("expressions need to be handled in checkBody");
},
ident(ident) {
return ident;
},
type(_type) {
throw new Error("all types should be typechecked manually");
},
}; };
const typecked = foldAst(ast, checker); const typecked = foldAst(ast, checker);
@ -518,10 +528,10 @@ export class InferContext {
} }
export function checkBody( export function checkBody(
body: Expr, body: Expr<Resolved>,
fnTy: TyFn, fnTy: TyFn,
typeOfItem: (index: number, cause: Span) => Ty typeOfItem: (index: number, cause: Span) => Ty
): Expr { ): Expr<Typecked> {
const localTys = [...fnTy.params]; const localTys = [...fnTy.params];
const loopState: { hasBreak: boolean; loopId: LoopId }[] = []; const loopState: { hasBreak: boolean; loopId: LoopId }[] = [];
@ -541,11 +551,11 @@ export function checkBody(
} }
} }
function lowerAstTy(type: Type): Ty { function lowerAstTy(type: Type<Resolved>): Ty {
return lowerAstTyBase( return lowerAstTyBase(
type, type,
(ident) => { (ident) => {
const res = ident.res!; const res = ident.res;
switch (res.kind) { switch (res.kind) {
case "local": { case "local": {
const idx = localTys.length - 1 - res.index; const idx = localTys.length - 1 - res.index;
@ -562,8 +572,8 @@ export function checkBody(
); );
} }
const checker: Folder = { const checker: Folder<Resolved, Typecked> = {
...DEFAULT_FOLDER, ...mkDefaultFolder(),
expr(expr) { expr(expr) {
switch (expr.kind) { switch (expr.kind) {
case "empty": { case "empty": {
@ -584,7 +594,7 @@ export function checkBody(
expr.local!.ty = bindingTy; expr.local!.ty = bindingTy;
const type: Type | undefined = loweredBindingTy && { const type: Type<Typecked> | undefined = loweredBindingTy && {
...expr.type!, ...expr.type!,
ty: loweredBindingTy, ty: loweredBindingTy,
}; };
@ -606,7 +616,7 @@ export function checkBody(
switch (lhs.kind) { switch (lhs.kind) {
case "ident": case "ident":
if (lhs.value.res!.kind !== "local") { if (lhs.value.res.kind !== "local") {
throw new CompilerError("cannot assign to items", expr.span); throw new CompilerError("cannot assign to items", expr.span);
} }
break; break;
@ -664,7 +674,7 @@ export function checkBody(
return { ...expr, ty }; return { ...expr, ty };
} }
case "ident": { case "ident": {
const ty = typeOf(expr.value.res!, expr.value.span); const ty = typeOf(expr.value.res, expr.value.span);
return { ...expr, ty }; return { ...expr, ty };
} }
@ -842,12 +852,11 @@ export function checkBody(
}; };
} }
case "structLiteral": { case "structLiteral": {
const fields = expr.fields.map<[Identifier, Expr]>(([name, expr]) => [ const fields = expr.fields.map<[Ident, Expr<Typecked>]>(
name, ([name, expr]) => [name, this.expr(expr)]
this.expr(expr), );
]);
const structTy = typeOf(expr.name.res!, expr.name.span); const structTy = typeOf(expr.name.res, expr.name.span);
if (structTy.kind !== "struct") { if (structTy.kind !== "struct") {
throw new CompilerError( throw new CompilerError(
@ -897,6 +906,15 @@ export function checkBody(
} }
} }
}, },
itemInner(_item) {
throw new Error("cannot deal with items inside body");
},
ident(ident) {
return ident;
},
type(_type) {
throw new Error("all types in the body should be handled elsewhere");
},
}; };
const checked = checker.expr(body); const checked = checker.expr(body);
@ -912,8 +930,8 @@ export function checkBody(
return resTy; return resTy;
}; };
const resolver: Folder = { const resolver: Folder<Typecked, Typecked> = {
...DEFAULT_FOLDER, ...mkDefaultFolder(),
expr(expr) { expr(expr) {
const ty = resolveTy(expr.ty!, expr.span); const ty = resolveTy(expr.ty!, expr.span);
@ -925,6 +943,13 @@ export function checkBody(
return { ...expr, ty }; return { ...expr, ty };
}, },
type(type) {
const ty = resolveTy(type.ty!, type.span);
return { ...type, ty };
},
ident(ident) {
return ident;
},
}; };
const resolved = resolver.expr(checked); const resolved = resolver.expr(checked);
@ -932,7 +957,9 @@ export function checkBody(
return resolved; return resolved;
} }
function checkBinary(expr: Expr & ExprBinary): Expr { function checkBinary(
expr: Expr<Typecked> & ExprBinary<Typecked>
): Expr<Typecked> {
const lhsTy = expr.lhs.ty!; const lhsTy = expr.lhs.ty!;
const rhsTy = expr.rhs.ty!; const rhsTy = expr.rhs.ty!;
@ -973,7 +1000,9 @@ function checkBinary(expr: Expr & ExprBinary): Expr {
); );
} }
function checkUnary(expr: Expr & ExprUnary): Expr { function checkUnary(
expr: Expr<Typecked> & ExprUnary<Typecked>
): Expr<Typecked> {
const rhsTy = expr.rhs.ty!; const rhsTy = expr.rhs.ty!;
if ( if (