Compare commits
	
		
			No commits in common. "main" and "teknikfagseksamen" have entirely different histories.
		
	
	
		
			main
			...
			teknikfags
		
	
		
@ -40,8 +40,7 @@ export const Ops = {
 | 
			
		||||
 | 
			
		||||
export type Builtins = typeof Builtins;
 | 
			
		||||
export const Builtins = {
 | 
			
		||||
    Exit: 0x00,
 | 
			
		||||
    IntToString: 0x01,
 | 
			
		||||
    IntToString: 0x00,
 | 
			
		||||
    StringConcat: 0x10,
 | 
			
		||||
    StringEqual: 0x11,
 | 
			
		||||
    StringCharAt: 0x12,
 | 
			
		||||
@ -53,9 +52,7 @@ export const Builtins = {
 | 
			
		||||
    ArrayPush: 0x22,
 | 
			
		||||
    ArrayAt: 0x23,
 | 
			
		||||
    ArrayLength: 0x24,
 | 
			
		||||
    StructNew: 0x30,
 | 
			
		||||
    StructSet: 0x31,
 | 
			
		||||
    StructAt: 0x32,
 | 
			
		||||
    StructSet: 0x30,
 | 
			
		||||
    Print: 0x40,
 | 
			
		||||
    FileOpen: 0x41,
 | 
			
		||||
    FileClose: 0x42,
 | 
			
		||||
							
								
								
									
										34
									
								
								compiler/architecture.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								compiler/architecture.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Mnemonic        Arg     Arg description
 | 
			
		||||
----------------------------------------
 | 
			
		||||
Nop
 | 
			
		||||
PushNull
 | 
			
		||||
PushInt         int     initial value
 | 
			
		||||
PushString      string  initial value
 | 
			
		||||
PushArray
 | 
			
		||||
PushStruct
 | 
			
		||||
PushPtr         ptr     pointer value
 | 
			
		||||
Pop
 | 
			
		||||
LoadLocal       int     stack position
 | 
			
		||||
StoreLocal      int     stack position
 | 
			
		||||
Call            int     arg count
 | 
			
		||||
Return
 | 
			
		||||
Jump
 | 
			
		||||
JumpIfNotZero
 | 
			
		||||
Add
 | 
			
		||||
Subtract
 | 
			
		||||
Multiply
 | 
			
		||||
Divide
 | 
			
		||||
Remainder
 | 
			
		||||
Equal
 | 
			
		||||
LessThan
 | 
			
		||||
And
 | 
			
		||||
Or
 | 
			
		||||
Xor
 | 
			
		||||
Not
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										147
									
								
								compiler/ast.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								compiler/ast.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,147 @@
 | 
			
		||||
import { Pos } from "./token.ts";
 | 
			
		||||
import { VType } from "./vtype.ts";
 | 
			
		||||
 | 
			
		||||
export type Stmt = {
 | 
			
		||||
    kind: StmtKind;
 | 
			
		||||
    pos: Pos;
 | 
			
		||||
    id: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type StmtKind =
 | 
			
		||||
    | { type: "error" }
 | 
			
		||||
    | { type: "import"; path: Expr }
 | 
			
		||||
    | { type: "break"; expr?: Expr }
 | 
			
		||||
    | { type: "return"; expr?: Expr }
 | 
			
		||||
    | {
 | 
			
		||||
        type: "fn";
 | 
			
		||||
        ident: string;
 | 
			
		||||
        params: Param[];
 | 
			
		||||
        returnType?: EType;
 | 
			
		||||
        body: Expr;
 | 
			
		||||
        anno?: Anno;
 | 
			
		||||
        vtype?: VType;
 | 
			
		||||
    }
 | 
			
		||||
    | { type: "let"; param: Param; value: Expr }
 | 
			
		||||
    | { type: "assign"; assignType: AssignType; subject: Expr; value: Expr }
 | 
			
		||||
    | { type: "expr"; expr: Expr };
 | 
			
		||||
 | 
			
		||||
export type AssignType = "=" | "+=" | "-=";
 | 
			
		||||
 | 
			
		||||
export type Expr = {
 | 
			
		||||
    kind: ExprKind;
 | 
			
		||||
    pos: Pos;
 | 
			
		||||
    vtype?: VType;
 | 
			
		||||
    id: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ExprKind =
 | 
			
		||||
    | { type: "error" }
 | 
			
		||||
    | { type: "int"; value: number }
 | 
			
		||||
    | { type: "string"; value: string }
 | 
			
		||||
    | { type: "ident"; value: string }
 | 
			
		||||
    | { type: "group"; expr: Expr }
 | 
			
		||||
    | { type: "field"; subject: Expr; value: string }
 | 
			
		||||
    | { type: "index"; subject: Expr; value: Expr }
 | 
			
		||||
    | { type: "call"; subject: Expr; args: Expr[] }
 | 
			
		||||
    | { type: "unary"; unaryType: UnaryType; subject: Expr }
 | 
			
		||||
    | { type: "binary"; binaryType: BinaryType; left: Expr; right: Expr }
 | 
			
		||||
    | { type: "if"; cond: Expr; truthy: Expr; falsy?: Expr; elsePos?: Pos }
 | 
			
		||||
    | { type: "bool"; value: boolean }
 | 
			
		||||
    | { type: "null" }
 | 
			
		||||
    | { type: "loop"; body: Expr }
 | 
			
		||||
    | { type: "block"; stmts: Stmt[]; expr?: Expr }
 | 
			
		||||
    | {
 | 
			
		||||
        type: "sym";
 | 
			
		||||
        ident: string;
 | 
			
		||||
        sym: Sym;
 | 
			
		||||
    }
 | 
			
		||||
    | { type: "while"; cond: Expr; body: Expr }
 | 
			
		||||
    | { type: "for_in"; param: Param; value: Expr; body: Expr }
 | 
			
		||||
    | {
 | 
			
		||||
        type: "for";
 | 
			
		||||
        decl?: Stmt;
 | 
			
		||||
        cond?: Expr;
 | 
			
		||||
        incr?: Stmt;
 | 
			
		||||
        body: Expr;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
export type UnaryType = "not" | "-";
 | 
			
		||||
export type BinaryType =
 | 
			
		||||
    | "+"
 | 
			
		||||
    | "*"
 | 
			
		||||
    | "=="
 | 
			
		||||
    | "-"
 | 
			
		||||
    | "/"
 | 
			
		||||
    | "!="
 | 
			
		||||
    | "<"
 | 
			
		||||
    | ">"
 | 
			
		||||
    | "<="
 | 
			
		||||
    | ">="
 | 
			
		||||
    | "or"
 | 
			
		||||
    | "and";
 | 
			
		||||
 | 
			
		||||
export type Param = {
 | 
			
		||||
    ident: string;
 | 
			
		||||
    etype?: EType;
 | 
			
		||||
    pos: Pos;
 | 
			
		||||
    vtype?: VType;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Sym = {
 | 
			
		||||
    ident: string;
 | 
			
		||||
    pos?: Pos;
 | 
			
		||||
} & SymKind;
 | 
			
		||||
 | 
			
		||||
export type SymKind =
 | 
			
		||||
    | { type: "let"; stmt: Stmt; param: Param }
 | 
			
		||||
    | { type: "let_static"; stmt: Stmt; param: Param }
 | 
			
		||||
    | { type: "fn"; stmt: Stmt }
 | 
			
		||||
    | { type: "fn_param"; param: Param }
 | 
			
		||||
    | { type: "closure"; inner: Sym }
 | 
			
		||||
    | { type: "builtin"; builtinId: number };
 | 
			
		||||
 | 
			
		||||
export type EType = {
 | 
			
		||||
    kind: ETypeKind;
 | 
			
		||||
    pos: Pos;
 | 
			
		||||
    id: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ETypeKind =
 | 
			
		||||
    | { type: "error" }
 | 
			
		||||
    | { type: "ident"; value: string }
 | 
			
		||||
    | { type: "array"; inner: EType }
 | 
			
		||||
    | { type: "struct"; fields: Param[] };
 | 
			
		||||
 | 
			
		||||
export type ETypeParam = {
 | 
			
		||||
    ident: string;
 | 
			
		||||
    pos: Pos;
 | 
			
		||||
    vtype?: VType;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Anno = {
 | 
			
		||||
    ident: string;
 | 
			
		||||
    values: Expr[];
 | 
			
		||||
    pos: Pos;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export class AstCreator {
 | 
			
		||||
    private nextNodeId = 0;
 | 
			
		||||
 | 
			
		||||
    public stmt(kind: StmtKind, pos: Pos): Stmt {
 | 
			
		||||
        const id = this.nextNodeId;
 | 
			
		||||
        this.nextNodeId += 1;
 | 
			
		||||
        return { kind, pos, id };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public expr(kind: ExprKind, pos: Pos): Expr {
 | 
			
		||||
        const id = this.nextNodeId;
 | 
			
		||||
        this.nextNodeId += 1;
 | 
			
		||||
        return { kind, pos, id };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public etype(kind: ETypeKind, pos: Pos): EType {
 | 
			
		||||
        const id = this.nextNodeId;
 | 
			
		||||
        this.nextNodeId += 1;
 | 
			
		||||
        return { kind, pos, id };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,153 +0,0 @@
 | 
			
		||||
import { Span } from "../diagnostics.ts";
 | 
			
		||||
 | 
			
		||||
export type File = {
 | 
			
		||||
    stmts: Stmt[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Stmt = {
 | 
			
		||||
    kind: StmtKind;
 | 
			
		||||
    span: Span;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type StmtKind =
 | 
			
		||||
    | { tag: "error" }
 | 
			
		||||
    | { tag: "item" } & ItemStmt
 | 
			
		||||
    | { tag: "let" } & LetStmt
 | 
			
		||||
    | { tag: "return" } & ReturnStmt
 | 
			
		||||
    | { tag: "break" } & BreakStmt
 | 
			
		||||
    | { tag: "assign" } & AssignStmt
 | 
			
		||||
    | { tag: "expr" } & ExprStmt;
 | 
			
		||||
 | 
			
		||||
export type ItemStmt = { item: Item };
 | 
			
		||||
 | 
			
		||||
export type LetStmt = {
 | 
			
		||||
    subject: Pat;
 | 
			
		||||
    expr?: Expr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ReturnStmt = { expr?: Expr };
 | 
			
		||||
export type BreakStmt = { expr?: Expr };
 | 
			
		||||
 | 
			
		||||
export type AssignStmt = {
 | 
			
		||||
    assignType: AssignType;
 | 
			
		||||
    subject: Expr;
 | 
			
		||||
    value: Expr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type AssignType = "=" | "+=" | "-=";
 | 
			
		||||
 | 
			
		||||
export type ExprStmt = { expr: Expr };
 | 
			
		||||
 | 
			
		||||
export type Item = {
 | 
			
		||||
    kind: ItemKind;
 | 
			
		||||
    span: Span;
 | 
			
		||||
    ident: Ident;
 | 
			
		||||
    pub: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ItemKind =
 | 
			
		||||
    | { tag: "error" }
 | 
			
		||||
    | { tag: "mod_block" } & ModBlockItem
 | 
			
		||||
    | { tag: "mod_file" } & ModFileItem
 | 
			
		||||
    | { tag: "enum" } & EnumItem
 | 
			
		||||
    | { tag: "struct" } & StructItem
 | 
			
		||||
    | { tag: "fn" } & FnItem
 | 
			
		||||
    | { tag: "use" } & UseItem
 | 
			
		||||
    | { tag: "static" } & StaticItem
 | 
			
		||||
    | { tag: "type_alias" } & TypeAliasItem;
 | 
			
		||||
 | 
			
		||||
export type ModBlockItem = {
 | 
			
		||||
    ident: Ident;
 | 
			
		||||
    stmts: Stmt[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ModFileItem = {
 | 
			
		||||
    ident: Ident;
 | 
			
		||||
    filePath: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type EnumItem = { variants: Variant[] };
 | 
			
		||||
export type StructItem = { data: VariantData };
 | 
			
		||||
export type FnItem = { _: 0 };
 | 
			
		||||
export type UseItem = { _: 0 };
 | 
			
		||||
export type StaticItem = { _: 0 };
 | 
			
		||||
export type TypeAliasItem = { _: 0 };
 | 
			
		||||
 | 
			
		||||
export type Variant = {
 | 
			
		||||
    ident: Ident;
 | 
			
		||||
    data: VariantData;
 | 
			
		||||
    pub: boolean;
 | 
			
		||||
    span: Span;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type VariantData = {
 | 
			
		||||
    kind: VariantDataKind;
 | 
			
		||||
    span: Span;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type VariantDataKind =
 | 
			
		||||
    | { tag: "error" }
 | 
			
		||||
    | { tag: "unit" }
 | 
			
		||||
    | { tag: "tuple" } & TupleVariantData
 | 
			
		||||
    | { tag: "struct" } & StructVariantData;
 | 
			
		||||
 | 
			
		||||
export type TupleVariantData = { elems: VariantData[] };
 | 
			
		||||
export type StructVariantData = { fields: FieldDef[] };
 | 
			
		||||
 | 
			
		||||
export type FieldDef = {
 | 
			
		||||
    ident: Ident;
 | 
			
		||||
    ty: Ty;
 | 
			
		||||
    pub: boolean;
 | 
			
		||||
    span: Span;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Expr = {
 | 
			
		||||
    kind: ExprKind;
 | 
			
		||||
    span: Span;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ExprKind =
 | 
			
		||||
    | { tag: "error" }
 | 
			
		||||
    | { tag: "ident" } & Ident;
 | 
			
		||||
 | 
			
		||||
export type Pat = {
 | 
			
		||||
    kind: PatKind;
 | 
			
		||||
    span: Span;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type PatKind =
 | 
			
		||||
    | { tag: "error" }
 | 
			
		||||
    | { tag: "bind" } & BindPat;
 | 
			
		||||
 | 
			
		||||
export type BindPat = {
 | 
			
		||||
    ident: Ident;
 | 
			
		||||
    mut: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Ty = {
 | 
			
		||||
    kind: TyKind;
 | 
			
		||||
    span: Span;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type TyKind =
 | 
			
		||||
    | { tag: "error" }
 | 
			
		||||
    | { tag: "ident" } & Ident
 | 
			
		||||
    | { tag: "ref" } & RefTy
 | 
			
		||||
    | { tag: "ptr" } & PtrTy
 | 
			
		||||
    | { tag: "slice" } & SliceTy
 | 
			
		||||
    | { tag: "array" } & ArrayTy;
 | 
			
		||||
 | 
			
		||||
export type RefTy = { ty: Ty; mut: boolean };
 | 
			
		||||
export type PtrTy = { ty: Ty; mut: boolean };
 | 
			
		||||
export type SliceTy = { ty: Ty };
 | 
			
		||||
export type ArrayTy = { ty: Ty; length: Expr };
 | 
			
		||||
export type TupleTy = { elems: Ty[] };
 | 
			
		||||
export type StructTy = { fields: FieldDef[] };
 | 
			
		||||
 | 
			
		||||
export type AnonFieldDef = {
 | 
			
		||||
    ident: Ident;
 | 
			
		||||
    ty: Ty;
 | 
			
		||||
    span: Span;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Ident = { text: string; span: Span };
 | 
			
		||||
@ -1,2 +0,0 @@
 | 
			
		||||
export * from "./ast.ts";
 | 
			
		||||
export * from "./visitor.ts";
 | 
			
		||||
@ -1,236 +0,0 @@
 | 
			
		||||
import { exhausted } from "../util.ts";
 | 
			
		||||
import {
 | 
			
		||||
    ArrayTy,
 | 
			
		||||
    AssignStmt,
 | 
			
		||||
    BindPat,
 | 
			
		||||
    BreakStmt,
 | 
			
		||||
    EnumItem,
 | 
			
		||||
    Expr,
 | 
			
		||||
    ExprStmt,
 | 
			
		||||
    File,
 | 
			
		||||
    FnItem,
 | 
			
		||||
    Ident,
 | 
			
		||||
    Item,
 | 
			
		||||
    ItemStmt,
 | 
			
		||||
    LetStmt,
 | 
			
		||||
    ModBlockItem,
 | 
			
		||||
    ModFileItem,
 | 
			
		||||
    Pat,
 | 
			
		||||
    PtrTy,
 | 
			
		||||
    RefTy,
 | 
			
		||||
    ReturnStmt,
 | 
			
		||||
    SliceTy,
 | 
			
		||||
    StaticItem,
 | 
			
		||||
    Stmt,
 | 
			
		||||
    StructItem,
 | 
			
		||||
    Ty,
 | 
			
		||||
    TypeAliasItem,
 | 
			
		||||
    UseItem,
 | 
			
		||||
} from "./ast.ts";
 | 
			
		||||
 | 
			
		||||
export type VisitRes = "stop" | void;
 | 
			
		||||
 | 
			
		||||
type R = VisitRes;
 | 
			
		||||
type PM = unknown[];
 | 
			
		||||
 | 
			
		||||
export interface Visitor<
 | 
			
		||||
    P extends PM = [],
 | 
			
		||||
> {
 | 
			
		||||
    visitFile?(file: File, ...p: P): R;
 | 
			
		||||
    visitStmt?(stmt: Stmt, ...p: P): R;
 | 
			
		||||
    visitErrorStmt?(stmt: Stmt, ...p: P): R;
 | 
			
		||||
    visitItemStmt?(stmt: Stmt, kind: ItemStmt, ...p: P): R;
 | 
			
		||||
    visitLetStmt?(stmt: Stmt, kind: LetStmt, ...p: P): R;
 | 
			
		||||
    visitReturnStmt?(stmt: Stmt, kind: ReturnStmt, ...p: P): R;
 | 
			
		||||
    visitBreakStmt?(stmt: Stmt, kind: BreakStmt, ...p: P): R;
 | 
			
		||||
    visitAssignStmt?(stmt: Stmt, kind: AssignStmt, ...p: P): R;
 | 
			
		||||
    visitExprStmt?(stmt: Stmt, kind: ExprStmt, ...p: P): R;
 | 
			
		||||
    visitItem?(item: Item, ...p: P): R;
 | 
			
		||||
    visitErrorItem?(item: Item, ...p: P): R;
 | 
			
		||||
    visitModBlockItem?(item: Item, kind: ModBlockItem, ...p: P): R;
 | 
			
		||||
    visitModFileItem?(item: Item, kind: ModFileItem, ...p: P): R;
 | 
			
		||||
    visitEnumItem?(item: Item, kind: EnumItem, ...p: P): R;
 | 
			
		||||
    visitStructItem?(item: Item, kind: StructItem, ...p: P): R;
 | 
			
		||||
    visitFnItem?(item: Item, kind: FnItem, ...p: P): R;
 | 
			
		||||
    visitUseItem?(item: Item, kind: UseItem, ...p: P): R;
 | 
			
		||||
    visitStaticItem?(item: Item, kind: StaticItem, ...p: P): R;
 | 
			
		||||
    visitTypeAliasItem?(item: Item, kind: TypeAliasItem, ...p: P): R;
 | 
			
		||||
    visitExpr?(expr: Expr, ...p: P): R;
 | 
			
		||||
    visitErrorExpr?(expr: Expr, ...p: P): R;
 | 
			
		||||
    visitIdentExpr?(expr: Expr, kind: Ident, ...p: P): R;
 | 
			
		||||
    visitPat?(pat: Pat, ...p: P): R;
 | 
			
		||||
    visitErrorPat?(pat: Pat, ...p: P): R;
 | 
			
		||||
    visitBindPat?(pat: Pat, kind: BindPat, ...p: P): R;
 | 
			
		||||
    visitTy?(ty: Ty, ...p: P): R;
 | 
			
		||||
    visitErrorTy?(ty: Ty, ...p: P): R;
 | 
			
		||||
    visitIdentTy?(ty: Ty, kind: Ident, ...p: P): R;
 | 
			
		||||
    visitRefTy?(ty: Ty, kind: RefTy, ...p: P): R;
 | 
			
		||||
    visitPtrTy?(ty: Ty, kind: PtrTy, ...p: P): R;
 | 
			
		||||
    visitSliceTy?(ty: Ty, kind: SliceTy, ...p: P): R;
 | 
			
		||||
    visitArrayTy?(ty: Ty, kind: ArrayTy, ...p: P): R;
 | 
			
		||||
    visitIdent?(ident: Ident, ...p: P): R;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function visitFile<
 | 
			
		||||
    P extends PM = [],
 | 
			
		||||
>(
 | 
			
		||||
    v: Visitor<P>,
 | 
			
		||||
    file: File,
 | 
			
		||||
    ...p: P
 | 
			
		||||
) {
 | 
			
		||||
    if (v.visitFile?.(file, ...p) === "stop") return;
 | 
			
		||||
    visitStmts(v, file.stmts, ...p);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function visitStmts<
 | 
			
		||||
    P extends PM = [],
 | 
			
		||||
>(
 | 
			
		||||
    v: Visitor<P>,
 | 
			
		||||
    stmts: Stmt[],
 | 
			
		||||
    ...p: P
 | 
			
		||||
) {
 | 
			
		||||
    for (const stmt of stmts) {
 | 
			
		||||
        visitStmt(v, stmt, ...p);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function visitStmt<
 | 
			
		||||
    P extends PM = [],
 | 
			
		||||
>(
 | 
			
		||||
    v: Visitor<P>,
 | 
			
		||||
    stmt: Stmt,
 | 
			
		||||
    ...p: P
 | 
			
		||||
) {
 | 
			
		||||
    const kind = stmt.kind;
 | 
			
		||||
    switch (kind.tag) {
 | 
			
		||||
        case "error":
 | 
			
		||||
            if (v.visitErrorStmt?.(stmt, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
        case "item":
 | 
			
		||||
            if (v.visitItemStmt?.(stmt, kind, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
        case "let":
 | 
			
		||||
            if (v.visitLetStmt?.(stmt, kind, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
        case "return":
 | 
			
		||||
            if (v.visitReturnStmt?.(stmt, kind, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
        case "break":
 | 
			
		||||
            if (v.visitBreakStmt?.(stmt, kind, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
        case "assign":
 | 
			
		||||
            if (v.visitAssignStmt?.(stmt, kind, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
        case "expr":
 | 
			
		||||
            if (v.visitExprStmt?.(stmt, kind, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
    }
 | 
			
		||||
    exhausted(kind);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function visitItem<
 | 
			
		||||
    P extends PM = [],
 | 
			
		||||
>(
 | 
			
		||||
    v: Visitor<P>,
 | 
			
		||||
    item: Item,
 | 
			
		||||
    ...p: P
 | 
			
		||||
) {
 | 
			
		||||
    const kind = item.kind;
 | 
			
		||||
    switch (kind.tag) {
 | 
			
		||||
        case "error":
 | 
			
		||||
            if (v.visitErrorItem?.(item, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
        case "mod_block":
 | 
			
		||||
            if (v.visitModBlockItem?.(item, kind, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
        case "mod_file":
 | 
			
		||||
            if (v.visitModFileItem?.(item, kind, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
        case "enum":
 | 
			
		||||
            if (v.visitEnumItem?.(item, kind, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
        case "struct":
 | 
			
		||||
            if (v.visitStructItem?.(item, kind, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
        case "fn":
 | 
			
		||||
            if (v.visitFnItem?.(item, kind, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
        case "use":
 | 
			
		||||
            if (v.visitUseItem?.(item, kind, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
        case "static":
 | 
			
		||||
            if (v.visitStaticItem?.(item, kind, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
        case "type_alias":
 | 
			
		||||
            if (v.visitTypeAliasItem?.(item, kind, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
    }
 | 
			
		||||
    exhausted(kind);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function visitExpr<
 | 
			
		||||
    P extends PM = [],
 | 
			
		||||
>(
 | 
			
		||||
    v: Visitor<P>,
 | 
			
		||||
    expr: Expr,
 | 
			
		||||
    ...p: P
 | 
			
		||||
) {
 | 
			
		||||
    const kind = expr.kind;
 | 
			
		||||
    switch (kind.tag) {
 | 
			
		||||
        case "error":
 | 
			
		||||
            if (v.visitErrorExpr?.(expr, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
        case "ident":
 | 
			
		||||
            if (v.visitIdentExpr?.(expr, kind, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
    }
 | 
			
		||||
    exhausted(kind);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function visitPat<
 | 
			
		||||
    P extends PM = [],
 | 
			
		||||
>(
 | 
			
		||||
    v: Visitor<P>,
 | 
			
		||||
    pat: Pat,
 | 
			
		||||
    ...p: P
 | 
			
		||||
) {
 | 
			
		||||
    const kind = pat.kind;
 | 
			
		||||
    switch (kind.tag) {
 | 
			
		||||
        case "error":
 | 
			
		||||
            if (v.visitErrorPat?.(pat, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
        case "bind":
 | 
			
		||||
            if (v.visitBindPat?.(pat, kind, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
    }
 | 
			
		||||
    exhausted(kind);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function visitTy<
 | 
			
		||||
    P extends PM = [],
 | 
			
		||||
>(
 | 
			
		||||
    v: Visitor<P>,
 | 
			
		||||
    ty: Ty,
 | 
			
		||||
    ...p: P
 | 
			
		||||
) {
 | 
			
		||||
    const kind = ty.kind;
 | 
			
		||||
    switch (kind.tag) {
 | 
			
		||||
        case "error":
 | 
			
		||||
            if (v.visitErrorTy?.(ty, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
        case "ident":
 | 
			
		||||
            if (v.visitIdentTy?.(ty, kind, ...p) === "stop") return;
 | 
			
		||||
            return;
 | 
			
		||||
    }
 | 
			
		||||
    exhausted(kind);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function visitIdent<
 | 
			
		||||
    P extends PM = [],
 | 
			
		||||
>(
 | 
			
		||||
    v: Visitor<P>,
 | 
			
		||||
    ident: Ident,
 | 
			
		||||
    ...p: P
 | 
			
		||||
) {
 | 
			
		||||
    v.visitIdent?.(ident, ...p);
 | 
			
		||||
}
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { EType, Expr, Field, Param, Stmt } from "./ast.ts";
 | 
			
		||||
import { EType, Expr, Param, Stmt } from "./ast.ts";
 | 
			
		||||
 | 
			
		||||
export type VisitRes = "stop" | void;
 | 
			
		||||
 | 
			
		||||
@ -6,14 +6,11 @@ export interface AstVisitor<Args extends unknown[] = []> {
 | 
			
		||||
    visitStmts?(stmts: Stmt[], ...args: Args): VisitRes;
 | 
			
		||||
    visitStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
			
		||||
    visitErrorStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
			
		||||
    visitModFileStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
			
		||||
    visitModBlockStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
			
		||||
    visitModStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
			
		||||
    visitImportStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
			
		||||
    visitBreakStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
			
		||||
    visitReturnStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
			
		||||
    visitFnStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
			
		||||
    visitLetStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
			
		||||
    visitTypeAliasStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
			
		||||
    visitAssignStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
			
		||||
    visitExprStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
			
		||||
    visitExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
@ -22,16 +19,9 @@ export interface AstVisitor<Args extends unknown[] = []> {
 | 
			
		||||
    visitStringExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
    visitIdentExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
    visitGroupExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
    visitRefExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
    visitRefMutExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
    visitDerefExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
    visitArrayExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
    visitStructExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
    visitFieldExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
    visitIndexExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
    visitCallExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
    visitPathExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
    visitETypeArgsExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
    visitUnaryExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
    visitBinaryExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
    visitIfExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
@ -44,22 +34,11 @@ export interface AstVisitor<Args extends unknown[] = []> {
 | 
			
		||||
    visitBlockExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
    visitSymExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
			
		||||
    visitParam?(param: Param, ...args: Args): VisitRes;
 | 
			
		||||
    visitField?(field: Field, ...args: Args): VisitRes;
 | 
			
		||||
    visitEType?(etype: EType, ...args: Args): VisitRes;
 | 
			
		||||
    visitErrorEType?(etype: EType, ...args: Args): VisitRes;
 | 
			
		||||
    visitNullEType?(etype: EType, ...args: Args): VisitRes;
 | 
			
		||||
    visitIntEType?(etype: EType, ...args: Args): VisitRes;
 | 
			
		||||
    visitBoolEType?(etype: EType, ...args: Args): VisitRes;
 | 
			
		||||
    visitStringEType?(etype: EType, ...args: Args): VisitRes;
 | 
			
		||||
    visitIdentEType?(etype: EType, ...args: Args): VisitRes;
 | 
			
		||||
    visitSymEType?(etype: EType, ...args: Args): VisitRes;
 | 
			
		||||
    visitRefEType?(etype: EType, ...args: Args): VisitRes;
 | 
			
		||||
    visitRefMutEType?(etype: EType, ...args: Args): VisitRes;
 | 
			
		||||
    visitPtrEType?(etype: EType, ...args: Args): VisitRes;
 | 
			
		||||
    visitPtrMutEType?(etype: EType, ...args: Args): VisitRes;
 | 
			
		||||
    visitArrayEType?(etype: EType, ...args: Args): VisitRes;
 | 
			
		||||
    visitStructEType?(etype: EType, ...args: Args): VisitRes;
 | 
			
		||||
    visitTypeOfEType?(etype: EType, ...args: Args): VisitRes;
 | 
			
		||||
    visitAnno?(etype: EType, ...args: Args): VisitRes;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -82,16 +61,9 @@ export function visitStmt<Args extends unknown[] = []>(
 | 
			
		||||
        case "error":
 | 
			
		||||
            if (v.visitErrorStmt?.(stmt, ...args) == "stop") return;
 | 
			
		||||
            break;
 | 
			
		||||
        case "mod_file":
 | 
			
		||||
            if (v.visitModFileStmt?.(stmt, ...args) == "stop") return;
 | 
			
		||||
            break;
 | 
			
		||||
        case "mod_block":
 | 
			
		||||
            if (v.visitModBlockStmt?.(stmt, ...args) == "stop") return;
 | 
			
		||||
            visitStmts(stmt.kind.stmts, v, ...args);
 | 
			
		||||
            break;
 | 
			
		||||
        case "mod":
 | 
			
		||||
            if (v.visitModStmt?.(stmt, ...args) == "stop") return;
 | 
			
		||||
            visitStmts(stmt.kind.mod.ast, v, ...args);
 | 
			
		||||
        case "import":
 | 
			
		||||
            if (v.visitImportStmt?.(stmt, ...args) == "stop") return;
 | 
			
		||||
            visitExpr(stmt.kind.path, v, ...args);
 | 
			
		||||
            break;
 | 
			
		||||
        case "break":
 | 
			
		||||
            if (v.visitBreakStmt?.(stmt, ...args) == "stop") return;
 | 
			
		||||
@ -114,10 +86,6 @@ export function visitStmt<Args extends unknown[] = []>(
 | 
			
		||||
            visitParam(stmt.kind.param, v, ...args);
 | 
			
		||||
            visitExpr(stmt.kind.value, v, ...args);
 | 
			
		||||
            break;
 | 
			
		||||
        case "type_alias":
 | 
			
		||||
            if (v.visitTypeAliasStmt?.(stmt, ...args) == "stop") return;
 | 
			
		||||
            visitParam(stmt.kind.param, v, ...args);
 | 
			
		||||
            break;
 | 
			
		||||
        case "assign":
 | 
			
		||||
            if (v.visitAssignStmt?.(stmt, ...args) == "stop") return;
 | 
			
		||||
            visitExpr(stmt.kind.subject, v, ...args);
 | 
			
		||||
@ -127,12 +95,6 @@ export function visitStmt<Args extends unknown[] = []>(
 | 
			
		||||
            if (v.visitExprStmt?.(stmt, ...args) == "stop") return;
 | 
			
		||||
            visitExpr(stmt.kind.expr, v, ...args);
 | 
			
		||||
            break;
 | 
			
		||||
        default:
 | 
			
		||||
            throw new Error(
 | 
			
		||||
                `statement '${
 | 
			
		||||
                    (stmt.kind as { type: string }).type
 | 
			
		||||
                }' not implemented`,
 | 
			
		||||
            );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -159,18 +121,6 @@ export function visitExpr<Args extends unknown[] = []>(
 | 
			
		||||
            if (v.visitGroupExpr?.(expr, ...args) == "stop") return;
 | 
			
		||||
            visitExpr(expr.kind.expr, v, ...args);
 | 
			
		||||
            break;
 | 
			
		||||
        case "ref":
 | 
			
		||||
            if (v.visitRefExpr?.(expr, ...args) == "stop") return;
 | 
			
		||||
            visitExpr(expr.kind.subject, v, ...args);
 | 
			
		||||
            break;
 | 
			
		||||
        case "ref_mut":
 | 
			
		||||
            if (v.visitRefMutExpr?.(expr, ...args) == "stop") return;
 | 
			
		||||
            visitExpr(expr.kind.subject, v, ...args);
 | 
			
		||||
            break;
 | 
			
		||||
        case "deref":
 | 
			
		||||
            if (v.visitDerefExpr?.(expr, ...args) == "stop") return;
 | 
			
		||||
            visitExpr(expr.kind.subject, v, ...args);
 | 
			
		||||
            break;
 | 
			
		||||
        case "field":
 | 
			
		||||
            if (v.visitFieldExpr?.(expr, ...args) == "stop") return;
 | 
			
		||||
            visitExpr(expr.kind.subject, v, ...args);
 | 
			
		||||
@ -185,15 +135,6 @@ export function visitExpr<Args extends unknown[] = []>(
 | 
			
		||||
            visitExpr(expr.kind.subject, v, ...args);
 | 
			
		||||
            expr.kind.args.map((arg) => visitExpr(arg, v, ...args));
 | 
			
		||||
            break;
 | 
			
		||||
        case "path":
 | 
			
		||||
            if (v.visitPathExpr?.(expr, ...args) == "stop") return;
 | 
			
		||||
            visitExpr(expr.kind.subject, v, ...args);
 | 
			
		||||
            break;
 | 
			
		||||
        case "etype_args":
 | 
			
		||||
            if (v.visitETypeArgsExpr?.(expr, ...args) == "stop") return;
 | 
			
		||||
            visitExpr(expr.kind.subject, v, ...args);
 | 
			
		||||
            expr.kind.etypeArgs.map((arg) => visitEType(arg, v, ...args));
 | 
			
		||||
            break;
 | 
			
		||||
        case "unary":
 | 
			
		||||
            if (v.visitUnaryExpr?.(expr, ...args) == "stop") return;
 | 
			
		||||
            visitExpr(expr.kind.subject, v, ...args);
 | 
			
		||||
@ -203,14 +144,6 @@ export function visitExpr<Args extends unknown[] = []>(
 | 
			
		||||
            visitExpr(expr.kind.left, v, ...args);
 | 
			
		||||
            visitExpr(expr.kind.right, v, ...args);
 | 
			
		||||
            break;
 | 
			
		||||
        case "array":
 | 
			
		||||
            if (v.visitArrayExpr?.(expr, ...args) == "stop") return;
 | 
			
		||||
            expr.kind.exprs.map((expr) => visitExpr(expr, v, ...args));
 | 
			
		||||
            break;
 | 
			
		||||
        case "struct":
 | 
			
		||||
            if (v.visitStructExpr?.(expr, ...args) == "stop") return;
 | 
			
		||||
            expr.kind.fields.map((field) => visitField(field, v, ...args));
 | 
			
		||||
            break;
 | 
			
		||||
        case "if":
 | 
			
		||||
            if (v.visitIfExpr?.(expr, ...args) == "stop") return;
 | 
			
		||||
            visitExpr(expr.kind.cond, v, ...args);
 | 
			
		||||
@ -253,12 +186,6 @@ export function visitExpr<Args extends unknown[] = []>(
 | 
			
		||||
        case "sym":
 | 
			
		||||
            if (v.visitSymExpr?.(expr, ...args) == "stop") return;
 | 
			
		||||
            break;
 | 
			
		||||
        default:
 | 
			
		||||
            throw new Error(
 | 
			
		||||
                `expression '${
 | 
			
		||||
                    (expr.kind as { type: string }).type
 | 
			
		||||
                }' not implemented`,
 | 
			
		||||
            );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -271,15 +198,6 @@ export function visitParam<Args extends unknown[] = []>(
 | 
			
		||||
    if (param.etype) visitEType(param.etype, v, ...args);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function visitField<Args extends unknown[] = []>(
 | 
			
		||||
    field: Field,
 | 
			
		||||
    v: AstVisitor<Args>,
 | 
			
		||||
    ...args: Args
 | 
			
		||||
) {
 | 
			
		||||
    if (v.visitField?.(field, ...args) == "stop") return;
 | 
			
		||||
    visitExpr(field.expr, v, ...args);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function visitEType<Args extends unknown[] = []>(
 | 
			
		||||
    etype: EType,
 | 
			
		||||
    v: AstVisitor<Args>,
 | 
			
		||||
@ -290,58 +208,17 @@ export function visitEType<Args extends unknown[] = []>(
 | 
			
		||||
        case "error":
 | 
			
		||||
            if (v.visitErrorEType?.(etype, ...args) == "stop") return;
 | 
			
		||||
            break;
 | 
			
		||||
        case "string":
 | 
			
		||||
            if (v.visitStringEType?.(etype, ...args) == "stop") return;
 | 
			
		||||
            break;
 | 
			
		||||
        case "null":
 | 
			
		||||
            if (v.visitNullEType?.(etype, ...args) == "stop") return;
 | 
			
		||||
            break;
 | 
			
		||||
        case "int":
 | 
			
		||||
            if (v.visitIntEType?.(etype, ...args) == "stop") return;
 | 
			
		||||
            break;
 | 
			
		||||
        case "bool":
 | 
			
		||||
            if (v.visitBoolEType?.(etype, ...args) == "stop") return;
 | 
			
		||||
            break;
 | 
			
		||||
        case "ident":
 | 
			
		||||
            if (v.visitIdentEType?.(etype, ...args) == "stop") return;
 | 
			
		||||
            break;
 | 
			
		||||
        case "sym":
 | 
			
		||||
            if (v.visitSymEType?.(etype, ...args) == "stop") return;
 | 
			
		||||
            break;
 | 
			
		||||
        case "ref":
 | 
			
		||||
            if (v.visitRefEType?.(etype, ...args) == "stop") return;
 | 
			
		||||
            visitEType(etype.kind.subject, v, ...args);
 | 
			
		||||
            break;
 | 
			
		||||
        case "ref_mut":
 | 
			
		||||
            if (v.visitRefMutEType?.(etype, ...args) == "stop") return;
 | 
			
		||||
            visitEType(etype.kind.subject, v, ...args);
 | 
			
		||||
            break;
 | 
			
		||||
        case "ptr":
 | 
			
		||||
            if (v.visitPtrEType?.(etype, ...args) == "stop") return;
 | 
			
		||||
            visitEType(etype.kind.subject, v, ...args);
 | 
			
		||||
            break;
 | 
			
		||||
        case "ptr_mut":
 | 
			
		||||
            if (v.visitPtrMutEType?.(etype, ...args) == "stop") return;
 | 
			
		||||
            visitEType(etype.kind.subject, v, ...args);
 | 
			
		||||
            break;
 | 
			
		||||
        case "array":
 | 
			
		||||
            if (v.visitArrayEType?.(etype, ...args) == "stop") return;
 | 
			
		||||
            visitEType(etype.kind.subject, v, ...args);
 | 
			
		||||
            if (etype.kind.inner) visitEType(etype.kind.inner, v, ...args);
 | 
			
		||||
            break;
 | 
			
		||||
        case "struct":
 | 
			
		||||
            if (v.visitStructEType?.(etype, ...args) == "stop") return;
 | 
			
		||||
            etype.kind.fields.map((field) => visitParam(field, v, ...args));
 | 
			
		||||
            break;
 | 
			
		||||
        case "type_of":
 | 
			
		||||
            if (v.visitTypeOfEType?.(etype, ...args) == "stop") return;
 | 
			
		||||
            visitExpr(etype.kind.expr, v, ...args);
 | 
			
		||||
            break;
 | 
			
		||||
        default:
 | 
			
		||||
            throw new Error(
 | 
			
		||||
                `etype '${
 | 
			
		||||
                    (etype.kind as { type: string }).type
 | 
			
		||||
                }' not implemented`,
 | 
			
		||||
            );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										663
									
								
								compiler/checker.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										663
									
								
								compiler/checker.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,663 @@
 | 
			
		||||
import { EType, Expr, Stmt } from "./ast.ts";
 | 
			
		||||
import { printStackTrace, Reporter } from "./info.ts";
 | 
			
		||||
import { Pos } from "./token.ts";
 | 
			
		||||
import { VType, VTypeParam, vtypesEqual, vtypeToString } from "./vtype.ts";
 | 
			
		||||
 | 
			
		||||
export class Checker {
 | 
			
		||||
    private fnReturnStack: VType[] = [];
 | 
			
		||||
    private loopBreakStack: VType[][] = [];
 | 
			
		||||
 | 
			
		||||
    public constructor(private reporter: Reporter) {}
 | 
			
		||||
 | 
			
		||||
    public check(stmts: Stmt[]) {
 | 
			
		||||
        this.checkFnHeaders(stmts);
 | 
			
		||||
        for (const stmt of stmts) {
 | 
			
		||||
            this.checkStmt(stmt);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private checkFnHeaders(stmts: Stmt[]) {
 | 
			
		||||
        for (const stmt of stmts) {
 | 
			
		||||
            if (stmt.kind.type !== "fn") {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            const returnType: VType = stmt.kind.returnType
 | 
			
		||||
                ? this.checkEType(stmt.kind.returnType)
 | 
			
		||||
                : { type: "null" };
 | 
			
		||||
            const params: VTypeParam[] = [];
 | 
			
		||||
            for (const param of stmt.kind.params) {
 | 
			
		||||
                if (param.etype === undefined) {
 | 
			
		||||
                    this.report("parameter types must be defined", param.pos);
 | 
			
		||||
                    stmt.kind.vtype = { type: "error" };
 | 
			
		||||
                }
 | 
			
		||||
                const vtype = this.checkEType(param.etype!);
 | 
			
		||||
                param.vtype = vtype;
 | 
			
		||||
                params.push({ ident: param.ident, vtype });
 | 
			
		||||
            }
 | 
			
		||||
            stmt.kind.vtype = { type: "fn", params, returnType };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public checkStmt(stmt: Stmt) {
 | 
			
		||||
        switch (stmt.kind.type) {
 | 
			
		||||
            case "error":
 | 
			
		||||
                return { type: "error" };
 | 
			
		||||
            case "break":
 | 
			
		||||
                return this.checkBreakStmt(stmt);
 | 
			
		||||
            case "return":
 | 
			
		||||
                return this.checkReturnStmt(stmt);
 | 
			
		||||
            case "fn":
 | 
			
		||||
                return this.checkFnStmt(stmt);
 | 
			
		||||
            case "let":
 | 
			
		||||
                return this.checkLetStmt(stmt);
 | 
			
		||||
            case "assign":
 | 
			
		||||
                return this.checkAssignStmt(stmt);
 | 
			
		||||
            case "expr":
 | 
			
		||||
                return this.checkExpr(stmt.kind.expr);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public checkBreakStmt(stmt: Stmt) {
 | 
			
		||||
        if (stmt.kind.type !== "break") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const pos = stmt.pos;
 | 
			
		||||
        if (this.loopBreakStack.length === 0) {
 | 
			
		||||
            this.report("cannot break outside loop context", pos);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const exprType: VType = stmt.kind.expr
 | 
			
		||||
            ? this.checkExpr(stmt.kind.expr)
 | 
			
		||||
            : { type: "null" };
 | 
			
		||||
        const breakTypes = this.loopBreakStack.at(-1)!;
 | 
			
		||||
        if (breakTypes.length === 0) {
 | 
			
		||||
            breakTypes.push(exprType);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const prevBreakType = breakTypes.at(-1)!;
 | 
			
		||||
        if (!vtypesEqual(prevBreakType, exprType)) {
 | 
			
		||||
            this.report(
 | 
			
		||||
                `incompatible types for break` +
 | 
			
		||||
                    `, got ${exprType}` +
 | 
			
		||||
                    ` incompatible with ${prevBreakType}`,
 | 
			
		||||
                pos,
 | 
			
		||||
            );
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        breakTypes.push(exprType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public checkReturnStmt(stmt: Stmt) {
 | 
			
		||||
        if (stmt.kind.type !== "return") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const pos = stmt.pos;
 | 
			
		||||
        if (this.fnReturnStack.length === 0) {
 | 
			
		||||
            this.report("cannot return outside fn context", pos);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const exprType: VType = stmt.kind.expr
 | 
			
		||||
            ? this.checkExpr(stmt.kind.expr)
 | 
			
		||||
            : { type: "null" };
 | 
			
		||||
        const returnType = this.fnReturnStack.at(-1)!;
 | 
			
		||||
        if (!vtypesEqual(exprType, returnType)) {
 | 
			
		||||
            this.report(
 | 
			
		||||
                `incompatible return type` +
 | 
			
		||||
                    `, got ${exprType}` +
 | 
			
		||||
                    `, expected ${returnType}`,
 | 
			
		||||
                pos,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public checkFnStmt(stmt: Stmt) {
 | 
			
		||||
        if (stmt.kind.type !== "fn") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const pos = stmt.pos;
 | 
			
		||||
        if (stmt.kind.vtype!.type !== "fn") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (
 | 
			
		||||
            stmt.kind.anno?.ident === "remainder" ||
 | 
			
		||||
            stmt.kind.anno?.ident === "builtin"
 | 
			
		||||
        ) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const { returnType } = stmt.kind.vtype!;
 | 
			
		||||
        this.fnReturnStack.push(returnType);
 | 
			
		||||
        const body = this.checkExpr(stmt.kind.body);
 | 
			
		||||
        this.fnReturnStack.pop();
 | 
			
		||||
 | 
			
		||||
        if (!vtypesEqual(returnType, body)) {
 | 
			
		||||
            this.report(
 | 
			
		||||
                `incompatible return type` +
 | 
			
		||||
                    `, expected '${vtypeToString(returnType)}'` +
 | 
			
		||||
                    `, got '${vtypeToString(body)}'`,
 | 
			
		||||
                pos,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public checkLetStmt(stmt: Stmt) {
 | 
			
		||||
        if (stmt.kind.type !== "let") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const pos = stmt.pos;
 | 
			
		||||
        const value = this.checkExpr(stmt.kind.value);
 | 
			
		||||
        if (stmt.kind.param.etype) {
 | 
			
		||||
            const paramVtype = this.checkEType(stmt.kind.param.etype);
 | 
			
		||||
            if (!vtypesEqual(value, paramVtype)) {
 | 
			
		||||
                this.report(
 | 
			
		||||
                    `incompatible value type` +
 | 
			
		||||
                        `, got '${vtypeToString(value)}'` +
 | 
			
		||||
                        `, expected '${vtypeToString(paramVtype)}'`,
 | 
			
		||||
                    pos,
 | 
			
		||||
                );
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        stmt.kind.param.vtype = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public checkAssignStmt(stmt: Stmt) {
 | 
			
		||||
        if (stmt.kind.type !== "assign") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const pos = stmt.pos;
 | 
			
		||||
        if (stmt.kind.assignType !== "=") {
 | 
			
		||||
            throw new Error("invalid ast: compound assign should be desugered");
 | 
			
		||||
        }
 | 
			
		||||
        const value = this.checkExpr(stmt.kind.value);
 | 
			
		||||
        switch (stmt.kind.subject.kind.type) {
 | 
			
		||||
            case "field": {
 | 
			
		||||
                const subject = this.checkExpr(stmt.kind.subject.kind.subject);
 | 
			
		||||
                if (subject.type !== "struct") {
 | 
			
		||||
                    this.report("cannot use field on non-struct", pos);
 | 
			
		||||
                    return { type: "error" };
 | 
			
		||||
                }
 | 
			
		||||
                const fieldValue = stmt.kind.subject.kind.value;
 | 
			
		||||
                const found = subject.fields.find((param) =>
 | 
			
		||||
                    param.ident === fieldValue
 | 
			
		||||
                );
 | 
			
		||||
                if (!found) {
 | 
			
		||||
                    this.report(
 | 
			
		||||
                        `no field named '${stmt.kind.subject.kind.value}' on struct`,
 | 
			
		||||
                        pos,
 | 
			
		||||
                    );
 | 
			
		||||
                    return { type: "error" };
 | 
			
		||||
                }
 | 
			
		||||
                if (!vtypesEqual(found.vtype, value)) {
 | 
			
		||||
                    this.report(
 | 
			
		||||
                        `cannot assign incompatible type to field '${found.ident}'` +
 | 
			
		||||
                            `, got '${vtypeToString(value)}'` +
 | 
			
		||||
                            `, expected '${vtypeToString(found.vtype)}'`,
 | 
			
		||||
                        pos,
 | 
			
		||||
                    );
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            case "index": {
 | 
			
		||||
                const subject = this.checkExpr(stmt.kind.subject.kind.subject);
 | 
			
		||||
                if (subject.type !== "array" && subject.type !== "string") {
 | 
			
		||||
                    this.report(
 | 
			
		||||
                        `cannot index on non-array, got: ${subject.type}`,
 | 
			
		||||
                        pos,
 | 
			
		||||
                    );
 | 
			
		||||
                    return { type: "error" };
 | 
			
		||||
                }
 | 
			
		||||
                const indexValue = this.checkExpr(stmt.kind.subject.kind.value);
 | 
			
		||||
                if (indexValue.type !== "int") {
 | 
			
		||||
                    this.report("cannot index on array with non-int", pos);
 | 
			
		||||
                    return { type: "error" };
 | 
			
		||||
                }
 | 
			
		||||
                if (
 | 
			
		||||
                    subject.type == "array" &&
 | 
			
		||||
                    !vtypesEqual(subject.inner, value)
 | 
			
		||||
                ) {
 | 
			
		||||
                    this.report(
 | 
			
		||||
                        `cannot assign incompatible type to array ` +
 | 
			
		||||
                            `'${vtypeToString(subject)}'` +
 | 
			
		||||
                            `, got '${vtypeToString(value)}'`,
 | 
			
		||||
                        pos,
 | 
			
		||||
                    );
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            case "sym": {
 | 
			
		||||
                if (stmt.kind.subject.kind.sym.type !== "let") {
 | 
			
		||||
                    this.report("cannot only assign to let-symbol", pos);
 | 
			
		||||
                    return { type: "error" };
 | 
			
		||||
                }
 | 
			
		||||
                if (
 | 
			
		||||
                    !vtypesEqual(stmt.kind.subject.kind.sym.param.vtype!, value)
 | 
			
		||||
                ) {
 | 
			
		||||
                    this.report(
 | 
			
		||||
                        `cannot assign to incompatible type` +
 | 
			
		||||
                            `, got '${vtypeToString(value)}'` +
 | 
			
		||||
                            `, expected '${
 | 
			
		||||
                                vtypeToString(
 | 
			
		||||
                                    stmt.kind.subject.kind.sym.param.vtype!,
 | 
			
		||||
                                )
 | 
			
		||||
                            }'`,
 | 
			
		||||
                        pos,
 | 
			
		||||
                    );
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            default:
 | 
			
		||||
                this.report("unassignable expression", pos);
 | 
			
		||||
                return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public checkExpr(expr: Expr): VType {
 | 
			
		||||
        const vtype = ((): VType => {
 | 
			
		||||
            switch (expr.kind.type) {
 | 
			
		||||
                case "error":
 | 
			
		||||
                    throw new Error("error in AST");
 | 
			
		||||
                case "ident":
 | 
			
		||||
                    throw new Error("ident expr in AST");
 | 
			
		||||
                case "sym":
 | 
			
		||||
                    return this.checkSymExpr(expr);
 | 
			
		||||
                case "null":
 | 
			
		||||
                    return { type: "null" };
 | 
			
		||||
                case "int":
 | 
			
		||||
                    return { type: "int" };
 | 
			
		||||
                case "bool":
 | 
			
		||||
                    return { type: "bool" };
 | 
			
		||||
                case "string":
 | 
			
		||||
                    return { type: "string" };
 | 
			
		||||
                case "group":
 | 
			
		||||
                    return this.checkExpr(expr.kind.expr);
 | 
			
		||||
                case "field":
 | 
			
		||||
                    return this.checkFieldExpr(expr);
 | 
			
		||||
                case "index":
 | 
			
		||||
                    return this.checkIndexExpr(expr);
 | 
			
		||||
                case "call":
 | 
			
		||||
                    return this.checkCallExpr(expr);
 | 
			
		||||
                case "unary":
 | 
			
		||||
                    return this.checkUnaryExpr(expr);
 | 
			
		||||
                case "binary":
 | 
			
		||||
                    return this.checkBinaryExpr(expr);
 | 
			
		||||
                case "if":
 | 
			
		||||
                    return this.checkIfExpr(expr);
 | 
			
		||||
                case "loop":
 | 
			
		||||
                    return this.checkLoopExpr(expr);
 | 
			
		||||
                case "while":
 | 
			
		||||
                case "for_in":
 | 
			
		||||
                case "for":
 | 
			
		||||
                    throw new Error(
 | 
			
		||||
                        "invalid ast: special loops should be desugered",
 | 
			
		||||
                    );
 | 
			
		||||
                case "block":
 | 
			
		||||
                    return this.checkBlockExpr(expr);
 | 
			
		||||
            }
 | 
			
		||||
            // throw new Error(`unhandled type ${expr.kind.type}`);
 | 
			
		||||
        })();
 | 
			
		||||
        return expr.vtype = vtype;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public checkSymExpr(expr: Expr): VType {
 | 
			
		||||
        if (expr.kind.type !== "sym") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        switch (expr.kind.sym.type) {
 | 
			
		||||
            case "let":
 | 
			
		||||
                return expr.kind.sym.param.vtype!;
 | 
			
		||||
            case "fn": {
 | 
			
		||||
                const fnStmt = expr.kind.sym.stmt!;
 | 
			
		||||
                if (fnStmt.kind.type !== "fn") {
 | 
			
		||||
                    throw new Error();
 | 
			
		||||
                }
 | 
			
		||||
                const vtype = fnStmt.kind.vtype!;
 | 
			
		||||
                if (vtype.type !== "fn") {
 | 
			
		||||
                    throw new Error();
 | 
			
		||||
                }
 | 
			
		||||
                const { params, returnType } = vtype;
 | 
			
		||||
                return { type: "fn", params, returnType };
 | 
			
		||||
            }
 | 
			
		||||
            case "fn_param":
 | 
			
		||||
                return expr.kind.sym.param.vtype!;
 | 
			
		||||
            case "builtin":
 | 
			
		||||
            case "let_static":
 | 
			
		||||
            case "closure":
 | 
			
		||||
                throw new Error(
 | 
			
		||||
                    `not implemented, sym type '${expr.kind.sym.type}'`,
 | 
			
		||||
                );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public checkFieldExpr(expr: Expr): VType {
 | 
			
		||||
        if (expr.kind.type !== "field") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const pos = expr.pos;
 | 
			
		||||
        const subject = this.checkExpr(expr.kind.subject);
 | 
			
		||||
        if (subject.type !== "struct") {
 | 
			
		||||
            this.report("cannot use field on non-struct", pos);
 | 
			
		||||
            return { type: "error" };
 | 
			
		||||
        }
 | 
			
		||||
        const value = expr.kind.value;
 | 
			
		||||
        const found = subject.fields.find((param) => param.ident === value);
 | 
			
		||||
        if (!found) {
 | 
			
		||||
            this.report(
 | 
			
		||||
                `no field named '${expr.kind.value}' on struct`,
 | 
			
		||||
                pos,
 | 
			
		||||
            );
 | 
			
		||||
            return { type: "error" };
 | 
			
		||||
        }
 | 
			
		||||
        return found.vtype;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public checkIndexExpr(expr: Expr): VType {
 | 
			
		||||
        if (expr.kind.type !== "index") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const pos = expr.pos;
 | 
			
		||||
        const subject = this.checkExpr(expr.kind.subject);
 | 
			
		||||
        if (subject.type !== "array" && subject.type !== "string") {
 | 
			
		||||
            this.report(`cannot index on non-array, got: ${subject.type}`, pos);
 | 
			
		||||
            return { type: "error" };
 | 
			
		||||
        }
 | 
			
		||||
        const value = this.checkExpr(expr.kind.value);
 | 
			
		||||
        if (value.type !== "int") {
 | 
			
		||||
            this.report("cannot index on array with non-int", pos);
 | 
			
		||||
            return { type: "error" };
 | 
			
		||||
        }
 | 
			
		||||
        if (subject.type === "array") {
 | 
			
		||||
            return subject.inner;
 | 
			
		||||
        }
 | 
			
		||||
        return { type: "int" };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public checkCallExpr(expr: Expr): VType {
 | 
			
		||||
        if (expr.kind.type !== "call") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const pos = expr.pos;
 | 
			
		||||
        const subject = this.checkExpr(expr.kind.subject);
 | 
			
		||||
        if (subject.type !== "fn") {
 | 
			
		||||
            this.report("cannot call non-fn", pos);
 | 
			
		||||
            return { type: "error" };
 | 
			
		||||
        }
 | 
			
		||||
        const args = expr.kind.args.map((arg) => this.checkExpr(arg));
 | 
			
		||||
        if (args.length !== subject.params.length) {
 | 
			
		||||
            this.report(
 | 
			
		||||
                `incorrect number of arguments` +
 | 
			
		||||
                    `, expected ${subject.params.length}`,
 | 
			
		||||
                pos,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        for (let i = 0; i < args.length; ++i) {
 | 
			
		||||
            if (!vtypesEqual(args[i], subject.params[i].vtype)) {
 | 
			
		||||
                this.report(
 | 
			
		||||
                    `incorrect argument ${i} '${subject.params[i].ident}'` +
 | 
			
		||||
                        `, expected ${vtypeToString(subject.params[i].vtype)}` +
 | 
			
		||||
                        `, got ${vtypeToString(args[i])}`,
 | 
			
		||||
                    pos,
 | 
			
		||||
                );
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return subject.returnType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public checkUnaryExpr(expr: Expr): VType {
 | 
			
		||||
        if (expr.kind.type !== "unary") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const pos = expr.pos;
 | 
			
		||||
        const subject = this.checkExpr(expr.kind.subject);
 | 
			
		||||
        for (const operation of simpleUnaryOperations) {
 | 
			
		||||
            if (operation.unaryType !== expr.kind.unaryType) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (!vtypesEqual(operation.operand, subject)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            return operation.result ?? operation.operand;
 | 
			
		||||
        }
 | 
			
		||||
        this.report(
 | 
			
		||||
            `cannot apply unary operation '${expr.kind.unaryType}' ` +
 | 
			
		||||
                `on type '${vtypeToString(subject)}'`,
 | 
			
		||||
            pos,
 | 
			
		||||
        );
 | 
			
		||||
        return { type: "error" };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public checkBinaryExpr(expr: Expr): VType {
 | 
			
		||||
        if (expr.kind.type !== "binary") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const pos = expr.pos;
 | 
			
		||||
        const left = this.checkExpr(expr.kind.left);
 | 
			
		||||
        const right = this.checkExpr(expr.kind.right);
 | 
			
		||||
        for (const operation of simpleBinaryOperations) {
 | 
			
		||||
            if (operation.binaryType !== expr.kind.binaryType) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (!vtypesEqual(operation.operand, left)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (!vtypesEqual(left, right)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            return operation.result ?? operation.operand;
 | 
			
		||||
        }
 | 
			
		||||
        this.report(
 | 
			
		||||
            `cannot apply binary operation '${expr.kind.binaryType}' ` +
 | 
			
		||||
                `on types '${vtypeToString(left)}' and '${
 | 
			
		||||
                    vtypeToString(right)
 | 
			
		||||
                }'`,
 | 
			
		||||
            pos,
 | 
			
		||||
        );
 | 
			
		||||
        return { type: "error" };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public checkIfExpr(expr: Expr): VType {
 | 
			
		||||
        if (expr.kind.type !== "if") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const pos = expr.pos;
 | 
			
		||||
        const cond = this.checkExpr(expr.kind.cond);
 | 
			
		||||
        const truthy = this.checkExpr(expr.kind.truthy);
 | 
			
		||||
        const falsy = expr.kind.falsy
 | 
			
		||||
            ? this.checkExpr(expr.kind.falsy)
 | 
			
		||||
            : undefined;
 | 
			
		||||
        if (cond.type !== "bool") {
 | 
			
		||||
            this.report(
 | 
			
		||||
                `if condition should be 'bool', got '${vtypeToString(cond)}'`,
 | 
			
		||||
                pos,
 | 
			
		||||
            );
 | 
			
		||||
            return { type: "error" };
 | 
			
		||||
        }
 | 
			
		||||
        if (falsy === undefined && truthy.type !== "null") {
 | 
			
		||||
            this.report(
 | 
			
		||||
                `if expressions without false-case must result in type 'null'` +
 | 
			
		||||
                    `, got '${vtypeToString(truthy)}'`,
 | 
			
		||||
                pos,
 | 
			
		||||
            );
 | 
			
		||||
            return { type: "error" };
 | 
			
		||||
        }
 | 
			
		||||
        if (falsy !== undefined && !vtypesEqual(truthy, falsy)) {
 | 
			
		||||
            this.report(
 | 
			
		||||
                `if cases must be compatible, got incompatible types` +
 | 
			
		||||
                    ` '${vtypeToString(truthy)}'` +
 | 
			
		||||
                    ` and '${vtypeToString(falsy)}'`,
 | 
			
		||||
                pos,
 | 
			
		||||
            );
 | 
			
		||||
            return { type: "error" };
 | 
			
		||||
        }
 | 
			
		||||
        return truthy;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public checkLoopExpr(expr: Expr): VType {
 | 
			
		||||
        if (expr.kind.type !== "loop") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const pos = expr.pos;
 | 
			
		||||
        this.loopBreakStack.push([]);
 | 
			
		||||
        const body = this.checkExpr(expr.kind.body);
 | 
			
		||||
        if (body.type !== "null") {
 | 
			
		||||
            this.report(
 | 
			
		||||
                `loop body must result in type 'null'` +
 | 
			
		||||
                    `, got '${vtypeToString(body)}'`,
 | 
			
		||||
                pos,
 | 
			
		||||
            );
 | 
			
		||||
            return { type: "error" };
 | 
			
		||||
        }
 | 
			
		||||
        const loopBreakTypes = this.loopBreakStack.pop()!;
 | 
			
		||||
        if (loopBreakTypes.length === 0) {
 | 
			
		||||
            return { type: "null" };
 | 
			
		||||
        }
 | 
			
		||||
        const breakType = loopBreakTypes.reduce<[VType, boolean, VType]>(
 | 
			
		||||
            (acc, curr) => {
 | 
			
		||||
                const [resulting, isIncompatible, outlier] = acc;
 | 
			
		||||
                if (isIncompatible) {
 | 
			
		||||
                    return acc;
 | 
			
		||||
                }
 | 
			
		||||
                if (!vtypesEqual(resulting, curr)) {
 | 
			
		||||
                    return [resulting, true, curr];
 | 
			
		||||
                }
 | 
			
		||||
                return [resulting, false, outlier];
 | 
			
		||||
            },
 | 
			
		||||
            [{ type: "null" }, false, { type: "null" }],
 | 
			
		||||
        );
 | 
			
		||||
        if (breakType[1]) {
 | 
			
		||||
            this.report(
 | 
			
		||||
                `incompatible types in break statements` +
 | 
			
		||||
                    `, got '${vtypeToString(breakType[2])}'` +
 | 
			
		||||
                    ` incompatible with ${vtypeToString(breakType[0])}`,
 | 
			
		||||
                pos,
 | 
			
		||||
            );
 | 
			
		||||
            return { type: "error" };
 | 
			
		||||
        }
 | 
			
		||||
        return breakType[0];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public checkBlockExpr(expr: Expr): VType {
 | 
			
		||||
        if (expr.kind.type !== "block") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        this.checkFnHeaders(expr.kind.stmts);
 | 
			
		||||
        for (const stmt of expr.kind.stmts) {
 | 
			
		||||
            this.checkStmt(stmt);
 | 
			
		||||
        }
 | 
			
		||||
        return expr.kind.expr
 | 
			
		||||
            ? this.checkExpr(expr.kind.expr)
 | 
			
		||||
            : { type: "null" };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public checkEType(etype: EType): VType {
 | 
			
		||||
        const pos = etype.pos;
 | 
			
		||||
        if (etype.kind.type === "ident") {
 | 
			
		||||
            if (etype.kind.value === "null") {
 | 
			
		||||
                return { type: "null" };
 | 
			
		||||
            }
 | 
			
		||||
            if (etype.kind.value === "int") {
 | 
			
		||||
                return { type: "int" };
 | 
			
		||||
            }
 | 
			
		||||
            if (etype.kind.value === "bool") {
 | 
			
		||||
                return { type: "bool" };
 | 
			
		||||
            }
 | 
			
		||||
            if (etype.kind.value === "string") {
 | 
			
		||||
                return { type: "string" };
 | 
			
		||||
            }
 | 
			
		||||
            this.report(`undefined type '${etype.kind.value}'`, pos);
 | 
			
		||||
            return { type: "error" };
 | 
			
		||||
        }
 | 
			
		||||
        if (etype.kind.type === "array") {
 | 
			
		||||
            const inner = this.checkEType(etype.kind.inner);
 | 
			
		||||
            return { type: "array", inner };
 | 
			
		||||
        }
 | 
			
		||||
        if (etype.kind.type === "struct") {
 | 
			
		||||
            const noTypeTest = etype.kind.fields.reduce(
 | 
			
		||||
                (acc, param) => [acc[0] || !param.etype, param.ident],
 | 
			
		||||
                [false, ""],
 | 
			
		||||
            );
 | 
			
		||||
            if (noTypeTest[0]) {
 | 
			
		||||
                this.report(
 | 
			
		||||
                    `field '${noTypeTest[1]}' declared without type`,
 | 
			
		||||
                    pos,
 | 
			
		||||
                );
 | 
			
		||||
                return { type: "error" };
 | 
			
		||||
            }
 | 
			
		||||
            const declaredTwiceTest = etype.kind.fields.reduce<
 | 
			
		||||
                [boolean, string[], string]
 | 
			
		||||
            >(
 | 
			
		||||
                (acc, curr) => {
 | 
			
		||||
                    if (acc[0]) {
 | 
			
		||||
                        return acc;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (acc[1].includes(curr.ident)) {
 | 
			
		||||
                        return [true, acc[1], curr.ident];
 | 
			
		||||
                    }
 | 
			
		||||
                    return [false, [...acc[1], curr.ident], ""];
 | 
			
		||||
                },
 | 
			
		||||
                [false, [], ""],
 | 
			
		||||
            );
 | 
			
		||||
            if (
 | 
			
		||||
                declaredTwiceTest[0]
 | 
			
		||||
            ) {
 | 
			
		||||
                this.report(`field ${declaredTwiceTest[2]} defined twice`, pos);
 | 
			
		||||
                return { type: "error" };
 | 
			
		||||
            }
 | 
			
		||||
            const fields = etype.kind.fields.map((param): VTypeParam => ({
 | 
			
		||||
                ident: param.ident,
 | 
			
		||||
                vtype: this.checkEType(param.etype!),
 | 
			
		||||
            }));
 | 
			
		||||
            return { type: "struct", fields };
 | 
			
		||||
        }
 | 
			
		||||
        throw new Error(`unknown explicit type ${etype.kind.type}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private report(msg: string, pos: Pos) {
 | 
			
		||||
        this.reporter.reportError({ reporter: "Checker", msg, pos });
 | 
			
		||||
        printStackTrace();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const simpleUnaryOperations: {
 | 
			
		||||
    unaryType: string;
 | 
			
		||||
    operand: VType;
 | 
			
		||||
    result?: VType;
 | 
			
		||||
}[] = [
 | 
			
		||||
    { unaryType: "not", operand: { type: "bool" } },
 | 
			
		||||
    { unaryType: "-", operand: { type: "int" } },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const simpleBinaryOperations: {
 | 
			
		||||
    binaryType: string;
 | 
			
		||||
    operand: VType;
 | 
			
		||||
    result?: VType;
 | 
			
		||||
}[] = [
 | 
			
		||||
    // arithmetic
 | 
			
		||||
    { binaryType: "+", operand: { type: "int" } },
 | 
			
		||||
    { binaryType: "+", operand: { type: "string" } },
 | 
			
		||||
    { binaryType: "-", operand: { type: "int" } },
 | 
			
		||||
    { binaryType: "*", operand: { type: "int" } },
 | 
			
		||||
    { binaryType: "/", operand: { type: "int" } },
 | 
			
		||||
    // logical
 | 
			
		||||
    { binaryType: "and", operand: { type: "bool" } },
 | 
			
		||||
    { binaryType: "or", operand: { type: "bool" } },
 | 
			
		||||
    // equality
 | 
			
		||||
    { binaryType: "==", operand: { type: "null" }, result: { type: "bool" } },
 | 
			
		||||
    { binaryType: "==", operand: { type: "int" }, result: { type: "bool" } },
 | 
			
		||||
    { binaryType: "==", operand: { type: "string" }, result: { type: "bool" } },
 | 
			
		||||
    { binaryType: "==", operand: { type: "bool" }, result: { type: "bool" } },
 | 
			
		||||
    { binaryType: "!=", operand: { type: "null" }, result: { type: "bool" } },
 | 
			
		||||
    { binaryType: "!=", operand: { type: "int" }, result: { type: "bool" } },
 | 
			
		||||
    { binaryType: "!=", operand: { type: "string" }, result: { type: "bool" } },
 | 
			
		||||
    { binaryType: "!=", operand: { type: "bool" }, result: { type: "bool" } },
 | 
			
		||||
    // comparison
 | 
			
		||||
    { binaryType: "<", operand: { type: "int" }, result: { type: "bool" } },
 | 
			
		||||
    { binaryType: ">", operand: { type: "int" }, result: { type: "bool" } },
 | 
			
		||||
    { binaryType: "<=", operand: { type: "int" }, result: { type: "bool" } },
 | 
			
		||||
    { binaryType: ">=", operand: { type: "int" }, result: { type: "bool" } },
 | 
			
		||||
];
 | 
			
		||||
							
								
								
									
										55
									
								
								compiler/compiler.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								compiler/compiler.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,55 @@
 | 
			
		||||
import { AstCreator } from "./ast.ts";
 | 
			
		||||
import { Checker } from "./checker.ts";
 | 
			
		||||
import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts";
 | 
			
		||||
import { SpecialLoopDesugarer } from "./desugar/special_loop.ts";
 | 
			
		||||
import { Reporter } from "./info.ts";
 | 
			
		||||
import { Lexer } from "./lexer.ts";
 | 
			
		||||
import { FnNamesMap, Lowerer } from "./lowerer.ts";
 | 
			
		||||
import { Parser } from "./parser.ts";
 | 
			
		||||
import { Resolver } from "./resolver.ts";
 | 
			
		||||
 | 
			
		||||
export type CompiledFile = {
 | 
			
		||||
    filepath: string;
 | 
			
		||||
    program: number[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type CompileResult = {
 | 
			
		||||
    program: number[];
 | 
			
		||||
    fnNames: FnNamesMap;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export class Compiler {
 | 
			
		||||
    private astCreator = new AstCreator();
 | 
			
		||||
    private reporter = new Reporter();
 | 
			
		||||
 | 
			
		||||
    public constructor(private startFilePath: string) {}
 | 
			
		||||
 | 
			
		||||
    public async compile(): Promise<CompileResult> {
 | 
			
		||||
        const text = await Deno.readTextFile(this.startFilePath);
 | 
			
		||||
 | 
			
		||||
        const lexer = new Lexer(text, this.reporter);
 | 
			
		||||
 | 
			
		||||
        const parser = new Parser(lexer, this.astCreator, this.reporter);
 | 
			
		||||
        const ast = parser.parse();
 | 
			
		||||
 | 
			
		||||
        new SpecialLoopDesugarer(this.astCreator).desugar(ast);
 | 
			
		||||
 | 
			
		||||
        new Resolver(this.reporter).resolve(ast);
 | 
			
		||||
 | 
			
		||||
        new CompoundAssignDesugarer(this.astCreator).desugar(ast);
 | 
			
		||||
 | 
			
		||||
        new Checker(this.reporter).check(ast);
 | 
			
		||||
 | 
			
		||||
        if (this.reporter.errorOccured()) {
 | 
			
		||||
            console.error("Errors occurred, stopping compilation.");
 | 
			
		||||
            Deno.exit(1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const lowerer = new Lowerer(lexer.currentPos());
 | 
			
		||||
        lowerer.lower(ast);
 | 
			
		||||
        // lowerer.printProgram();
 | 
			
		||||
        const { program, fnNames } = lowerer.finish();
 | 
			
		||||
 | 
			
		||||
        return { program, fnNames };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										107
									
								
								compiler/ctx.ts
									
									
									
									
									
								
							
							
						
						
									
										107
									
								
								compiler/ctx.ts
									
									
									
									
									
								
							@ -1,107 +0,0 @@
 | 
			
		||||
import * as ast from "./ast/mod.ts";
 | 
			
		||||
import { Pos, prettyPrintReport, printStackTrace, Report, Span } from "./diagnostics.ts";
 | 
			
		||||
 | 
			
		||||
export class Ctx {
 | 
			
		||||
    private fileIds = new Ids();
 | 
			
		||||
    private files = new Map<Id<File>, FileInfo>();
 | 
			
		||||
 | 
			
		||||
    private reports: Report[] = [];
 | 
			
		||||
 | 
			
		||||
    public fileHasChildWithIdent(file: File, childIdent: string): boolean {
 | 
			
		||||
        return this.files.get(id(file))!
 | 
			
		||||
            .subFiles.has(childIdent);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public addFile(
 | 
			
		||||
        ident: string,
 | 
			
		||||
        absPath: string,
 | 
			
		||||
        relPath: string,
 | 
			
		||||
        superFile: File | undefined,
 | 
			
		||||
        text: string,
 | 
			
		||||
    ): File {
 | 
			
		||||
        const file = this.fileIds.nextThenStep();
 | 
			
		||||
        this.files.set(id(file), {
 | 
			
		||||
            ident,
 | 
			
		||||
            absPath,
 | 
			
		||||
            relPath,
 | 
			
		||||
            superFile,
 | 
			
		||||
            subFiles: new Map(),
 | 
			
		||||
            text,
 | 
			
		||||
        });
 | 
			
		||||
        if (superFile) {
 | 
			
		||||
            this.files.get(id(superFile))!
 | 
			
		||||
                .subFiles.set(ident, file);
 | 
			
		||||
        }
 | 
			
		||||
        return file;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public addFileAst(file: File, ast: ast.File) {
 | 
			
		||||
        this.files.get(id(file))!.ast = ast;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public fileInfo(file: File): FileInfo {
 | 
			
		||||
        return this.files.get(id(file))!;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public filePosLineText(file: File, pos: Pos): string {
 | 
			
		||||
        const fileTextLines = this.fileInfo(file).text.split("\n")
 | 
			
		||||
        return fileTextLines[pos.line-1]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public fileSpanText(file: File, span: Span): string {
 | 
			
		||||
        let result = ""
 | 
			
		||||
        const fileTextLines = this.fileInfo(file).text.split("\n")
 | 
			
		||||
 | 
			
		||||
        for(let i = 0; i < fileTextLines.length; i++) {
 | 
			
		||||
            if (i > span.end.line-1) {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            if (i >= span.begin.line-1) {
 | 
			
		||||
                result += fileTextLines[i] + "\n";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return result
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public report(rep: Report) {
 | 
			
		||||
        this.reports.push(rep);
 | 
			
		||||
        this.reportImmediately(rep);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public enableReportImmediately = false;
 | 
			
		||||
    public enableStacktrace = false;
 | 
			
		||||
    private reportImmediately(rep: Report) {
 | 
			
		||||
        if (this.enableReportImmediately) {
 | 
			
		||||
            prettyPrintReport(this, rep);
 | 
			
		||||
            if (this.enableStacktrace) {
 | 
			
		||||
                printStackTrace();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type File = IdBase;
 | 
			
		||||
 | 
			
		||||
export type FileInfo = {
 | 
			
		||||
    ident: string;
 | 
			
		||||
    absPath: string;
 | 
			
		||||
    relPath: string;
 | 
			
		||||
    superFile?: File;
 | 
			
		||||
    subFiles: Map<string, File>;
 | 
			
		||||
    text: string;
 | 
			
		||||
    ast?: ast.File;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type IdBase = { id: number };
 | 
			
		||||
 | 
			
		||||
export type Id<IdType extends IdBase> = IdType["id"];
 | 
			
		||||
export const id = <IdType extends IdBase>(id: IdType): Id<IdType> => id.id;
 | 
			
		||||
 | 
			
		||||
export class Ids<IdType extends IdBase> {
 | 
			
		||||
    private next = 0;
 | 
			
		||||
    public nextThenStep(): IdType {
 | 
			
		||||
        const id = this.next;
 | 
			
		||||
        this.next += 1;
 | 
			
		||||
        return { id } as IdType;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								compiler/deno.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								compiler/deno.lock
									
									
									
										generated
									
									
									
								
							@ -1,14 +1,8 @@
 | 
			
		||||
{
 | 
			
		||||
  "version": "4",
 | 
			
		||||
  "specifiers": {
 | 
			
		||||
    "jsr:@std/path@*": "1.0.8",
 | 
			
		||||
    "npm:@types/node@*": "22.5.4"
 | 
			
		||||
  },
 | 
			
		||||
  "jsr": {
 | 
			
		||||
    "@std/path@1.0.8": {
 | 
			
		||||
      "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "npm": {
 | 
			
		||||
    "@types/node@22.5.4": {
 | 
			
		||||
      "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
 | 
			
		||||
 | 
			
		||||
@ -70,45 +70,29 @@ export class SpecialLoopDesugarer implements AstVisitor {
 | 
			
		||||
            stmts: [
 | 
			
		||||
                Stmt({
 | 
			
		||||
                    type: "let",
 | 
			
		||||
                    param: this.astCreator.param({
 | 
			
		||||
                        ident: "::values",
 | 
			
		||||
                        mut: true,
 | 
			
		||||
                        pos: npos,
 | 
			
		||||
                    }),
 | 
			
		||||
                    param: { ident: "::values", pos: npos },
 | 
			
		||||
                    value: expr.kind.value,
 | 
			
		||||
                }),
 | 
			
		||||
                Stmt({
 | 
			
		||||
                    type: "let",
 | 
			
		||||
                    param: this.astCreator.param({
 | 
			
		||||
                        ident: "::length",
 | 
			
		||||
                        mut: false,
 | 
			
		||||
                        pos: npos,
 | 
			
		||||
                    }),
 | 
			
		||||
                    param: { ident: "::length", pos: npos },
 | 
			
		||||
                    value: Expr({
 | 
			
		||||
                        type: "call",
 | 
			
		||||
                        subject: Expr({
 | 
			
		||||
                            type: "path",
 | 
			
		||||
                        subject: Expr({
 | 
			
		||||
                            type: "ident",
 | 
			
		||||
                                ident: "std",
 | 
			
		||||
                            }),
 | 
			
		||||
                            ident: "array_length",
 | 
			
		||||
                            value: "int_array_length",
 | 
			
		||||
                        }),
 | 
			
		||||
                        args: [
 | 
			
		||||
                            Expr({
 | 
			
		||||
                                type: "ident",
 | 
			
		||||
                                ident: "::values",
 | 
			
		||||
                                value: "::values",
 | 
			
		||||
                            }),
 | 
			
		||||
                        ],
 | 
			
		||||
                    }),
 | 
			
		||||
                }),
 | 
			
		||||
                Stmt({
 | 
			
		||||
                    type: "let",
 | 
			
		||||
                    param: this.astCreator.param({
 | 
			
		||||
                        ident: "::index",
 | 
			
		||||
                        mut: true,
 | 
			
		||||
                        pos: npos,
 | 
			
		||||
                    }),
 | 
			
		||||
                    param: { ident: "::index", pos: npos },
 | 
			
		||||
                    value: Expr({ type: "int", value: 0 }),
 | 
			
		||||
                }, expr.pos),
 | 
			
		||||
                Stmt({
 | 
			
		||||
@ -130,11 +114,11 @@ export class SpecialLoopDesugarer implements AstVisitor {
 | 
			
		||||
                                                binaryType: "<",
 | 
			
		||||
                                                left: Expr({
 | 
			
		||||
                                                    type: "ident",
 | 
			
		||||
                                                    ident: "::index",
 | 
			
		||||
                                                    value: "::index",
 | 
			
		||||
                                                }),
 | 
			
		||||
                                                right: Expr({
 | 
			
		||||
                                                    type: "ident",
 | 
			
		||||
                                                    ident: "::length",
 | 
			
		||||
                                                    value: "::length",
 | 
			
		||||
                                                }),
 | 
			
		||||
                                            }),
 | 
			
		||||
                                        }),
 | 
			
		||||
@ -155,11 +139,11 @@ export class SpecialLoopDesugarer implements AstVisitor {
 | 
			
		||||
                                        type: "index",
 | 
			
		||||
                                        subject: Expr({
 | 
			
		||||
                                            type: "ident",
 | 
			
		||||
                                            ident: "::values",
 | 
			
		||||
                                            value: "::values",
 | 
			
		||||
                                        }),
 | 
			
		||||
                                        value: Expr({
 | 
			
		||||
                                            type: "ident",
 | 
			
		||||
                                            ident: "::index",
 | 
			
		||||
                                            value: "::index",
 | 
			
		||||
                                        }),
 | 
			
		||||
                                    }),
 | 
			
		||||
                                }, expr.pos),
 | 
			
		||||
@ -172,7 +156,7 @@ export class SpecialLoopDesugarer implements AstVisitor {
 | 
			
		||||
                                    assignType: "+=",
 | 
			
		||||
                                    subject: Expr({
 | 
			
		||||
                                        type: "ident",
 | 
			
		||||
                                        ident: "::index",
 | 
			
		||||
                                        value: "::index",
 | 
			
		||||
                                    }),
 | 
			
		||||
                                    value: Expr({
 | 
			
		||||
                                        type: "int",
 | 
			
		||||
@ -1,95 +0,0 @@
 | 
			
		||||
import { Ctx, File } from "./ctx.ts";
 | 
			
		||||
import { exhausted } from "./util.ts";
 | 
			
		||||
 | 
			
		||||
export type Span = {
 | 
			
		||||
    begin: Pos;
 | 
			
		||||
    end: Pos;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Pos = {
 | 
			
		||||
    idx: number;
 | 
			
		||||
    line: number;
 | 
			
		||||
    col: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Report = {
 | 
			
		||||
    severity: "fatal" | "error" | "warning" | "info";
 | 
			
		||||
    origin?: string;
 | 
			
		||||
    msg: string;
 | 
			
		||||
    file?: File;
 | 
			
		||||
    span?: Span;
 | 
			
		||||
    pos?: Pos;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function severityColor(severity: "fatal" | "error" | "warning" | "info") {
 | 
			
		||||
    switch (severity) {
 | 
			
		||||
        case "fatal":
 | 
			
		||||
            return "\x1b[1m\x1b[31m";
 | 
			
		||||
        case "error":
 | 
			
		||||
            return "\x1b[1m\x1b[31m";
 | 
			
		||||
        case "warning":
 | 
			
		||||
            return "\x1b[1m\x1b[33m";
 | 
			
		||||
        case "info":
 | 
			
		||||
            return "\x1b[1m\x1b[34m";
 | 
			
		||||
        }
 | 
			
		||||
    exhausted(severity)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function prettyPrintReport(ctx: Ctx, rep: Report) {
 | 
			
		||||
    const { severity, msg } = rep;
 | 
			
		||||
    const origin = rep.origin ? `\x1b[1m${rep.origin}:\x1b[0m ` : "";
 | 
			
		||||
    console.error(`${origin}${severityColor(severity)}${severity}:\x1b[0m \x1b[37m${msg}\x1b[0m`);
 | 
			
		||||
    if (rep.file && (rep.span || rep.pos)) {
 | 
			
		||||
        const errorLineOffset = 2
 | 
			
		||||
        const { absPath: path } = ctx.fileInfo(rep.file);
 | 
			
		||||
        const { line, col } = rep.span?.begin ?? rep.pos!;
 | 
			
		||||
        console.error(`    --> ./${path}:${line}:${col}`);
 | 
			
		||||
        if (rep.span) {
 | 
			
		||||
            const spanLines = ctx.fileSpanText(rep.file, rep.span).split("\n");
 | 
			
		||||
            spanLines.pop()
 | 
			
		||||
            if (spanLines.length == 1) {
 | 
			
		||||
                console.error(`${rep.span.begin.line.toString().padStart(4, ' ')}| ${spanLines[0]}`);
 | 
			
		||||
                console.error(`    | ${severityColor(severity)}${" ".repeat(rep.span.begin.col)}${"~".repeat(rep.span.end.col-rep.span.begin.col)}\x1b[0m`)
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
            for (let i = 0; i < spanLines.length; i++) {
 | 
			
		||||
                console.error(`${(rep.span.begin.line+i).toString().padStart(4, ' ')}| ${spanLines[i]}`);
 | 
			
		||||
                if (i == 0) {
 | 
			
		||||
                    console.error(`    | ${" ".repeat(rep.span.begin.col-1)}${severityColor(severity)}${"~".repeat(spanLines[i].length-(rep.span.begin.col-1))}\x1b[0m`)
 | 
			
		||||
                }
 | 
			
		||||
                else if (i == spanLines.length-1) {
 | 
			
		||||
                    console.error(`    | ${severityColor(severity)}${"~".repeat(rep.span.end.col)}\x1b[0m`)
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    console.error(`    | ${severityColor(severity)}${"~".repeat(spanLines[i].length)}\x1b[0m`)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if (rep.pos) {
 | 
			
		||||
            console.error(`${rep.pos.line.toString().padStart(4, ' ')}| ${ctx.filePosLineText(rep.file, rep.pos)}`);
 | 
			
		||||
            console.error(`    | ${severityColor(severity)}${" ".repeat(rep.pos.col)}^\x1b[0m`)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function printStackTrace() {
 | 
			
		||||
    class StackTracer extends Error {
 | 
			
		||||
        constructor() {
 | 
			
		||||
            super("StackTracer");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
        throw new StackTracer();
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        if (!(error instanceof StackTracer)) {
 | 
			
		||||
            throw error;
 | 
			
		||||
        }
 | 
			
		||||
        console.log(
 | 
			
		||||
            error.stack?.replace(
 | 
			
		||||
                "Error: StackTracer",
 | 
			
		||||
                "Stack trace:",
 | 
			
		||||
            ) ??
 | 
			
		||||
                error,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import { Pos } from "./token.ts";
 | 
			
		||||
 | 
			
		||||
export type Report = {
 | 
			
		||||
    type: "error" | "warning" | "note";
 | 
			
		||||
    type: "error" | "note";
 | 
			
		||||
    reporter: string;
 | 
			
		||||
    pos?: Pos;
 | 
			
		||||
    msg: string;
 | 
			
		||||
@ -11,27 +11,16 @@ export class Reporter {
 | 
			
		||||
    private reports: Report[] = [];
 | 
			
		||||
    private errorSet = false;
 | 
			
		||||
 | 
			
		||||
    public constructor(private filePath: string) {}
 | 
			
		||||
 | 
			
		||||
    public setFilePath(filePath: string) {
 | 
			
		||||
        this.filePath = filePath;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public reportError(report: Omit<Report, "type">) {
 | 
			
		||||
        this.reports.push({ ...report, type: "error" });
 | 
			
		||||
        this.printReport({ ...report, type: "error" });
 | 
			
		||||
        this.errorSet = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public reportWarning(report: Omit<Report, "type">) {
 | 
			
		||||
        this.reports.push({ ...report, type: "warning" });
 | 
			
		||||
        this.printReport({ ...report, type: "warning" });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private printReport({ reporter, type, pos, msg }: Report) {
 | 
			
		||||
        console.error(
 | 
			
		||||
            `${reporter} ${type}: ${msg}${
 | 
			
		||||
                pos ? `\n    at ${this.filePath}:${pos.line}:${pos.col}` : ""
 | 
			
		||||
                pos ? ` at ${pos.line}:${pos.col}` : ""
 | 
			
		||||
            }`,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
@ -27,31 +27,24 @@ export class Lexer {
 | 
			
		||||
                this.step();
 | 
			
		||||
            }
 | 
			
		||||
            const keywords = [
 | 
			
		||||
                "false",
 | 
			
		||||
                "true",
 | 
			
		||||
                "null",
 | 
			
		||||
                "int",
 | 
			
		||||
                "bool",
 | 
			
		||||
                "string",
 | 
			
		||||
                "break",
 | 
			
		||||
                "return",
 | 
			
		||||
                "let",
 | 
			
		||||
                "mut",
 | 
			
		||||
                "fn",
 | 
			
		||||
                "loop",
 | 
			
		||||
                "if",
 | 
			
		||||
                "else",
 | 
			
		||||
                "struct",
 | 
			
		||||
                "import",
 | 
			
		||||
                "false",
 | 
			
		||||
                "true",
 | 
			
		||||
                "null",
 | 
			
		||||
                "or",
 | 
			
		||||
                "and",
 | 
			
		||||
                "not",
 | 
			
		||||
                "while",
 | 
			
		||||
                "for",
 | 
			
		||||
                "in",
 | 
			
		||||
                "mod",
 | 
			
		||||
                "pub",
 | 
			
		||||
                "use",
 | 
			
		||||
                "type_alias",
 | 
			
		||||
            ];
 | 
			
		||||
            if (keywords.includes(value)) {
 | 
			
		||||
                return this.token(value, pos);
 | 
			
		||||
@ -77,32 +70,6 @@ export class Lexer {
 | 
			
		||||
            return { ...this.token("int", pos), intValue: 0 };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.test("'")) {
 | 
			
		||||
            this.step();
 | 
			
		||||
            let value: string;
 | 
			
		||||
            if (this.test("\\")) {
 | 
			
		||||
                this.step();
 | 
			
		||||
                if (this.done()) {
 | 
			
		||||
                    this.report("malformed character literal", pos);
 | 
			
		||||
                    return this.token("error", pos);
 | 
			
		||||
                }
 | 
			
		||||
                value = {
 | 
			
		||||
                    n: "\n",
 | 
			
		||||
                    t: "\t",
 | 
			
		||||
                    "0": "\0",
 | 
			
		||||
                }[this.current()] ?? this.current();
 | 
			
		||||
            } else {
 | 
			
		||||
                value = this.current();
 | 
			
		||||
            }
 | 
			
		||||
            this.step();
 | 
			
		||||
            if (this.done() || !this.test("'") || value.length === 0) {
 | 
			
		||||
                this.report("malformed character literal", pos);
 | 
			
		||||
                return this.token("error", pos);
 | 
			
		||||
            }
 | 
			
		||||
            this.step();
 | 
			
		||||
            return { ...this.token("int", pos), intValue: value.charCodeAt(0) };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.test('"')) {
 | 
			
		||||
            this.step();
 | 
			
		||||
            let value = "";
 | 
			
		||||
@ -129,7 +96,7 @@ export class Lexer {
 | 
			
		||||
            this.step();
 | 
			
		||||
            return { ...this.token("string", pos), stringValue: value };
 | 
			
		||||
        }
 | 
			
		||||
        if (this.test(/[\+\{\};=\-\*\(\)\.,:;\[\]><!0#&]/)) {
 | 
			
		||||
        if (this.test(/[\+\{\};=\-\*\(\)\.,:;\[\]><!0#]/)) {
 | 
			
		||||
            const first = this.current();
 | 
			
		||||
            this.step();
 | 
			
		||||
            if (first === "=" && !this.done() && this.test("=")) {
 | 
			
		||||
@ -1,65 +1,48 @@
 | 
			
		||||
import { Builtins, Ops } from "./arch.ts";
 | 
			
		||||
import { Assembler, Label } from "./assembler.ts";
 | 
			
		||||
import { AnnoView, Expr, Stmt } from "./ast.ts";
 | 
			
		||||
import { Expr, Stmt } from "./ast.ts";
 | 
			
		||||
import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts";
 | 
			
		||||
import { MonoCallNameGenMap, MonoFn, MonoFnsMap } from "./mono.ts";
 | 
			
		||||
import { Pos } from "./token.ts";
 | 
			
		||||
import { Assembler, Label } from "./assembler.ts";
 | 
			
		||||
import { vtypeToString } from "./vtype.ts";
 | 
			
		||||
import { Pos } from "./token.ts";
 | 
			
		||||
 | 
			
		||||
export type FnNamesMap = { [pc: number]: string };
 | 
			
		||||
 | 
			
		||||
export class Lowerer {
 | 
			
		||||
    private program = Assembler.newRoot();
 | 
			
		||||
    private locals: Locals = new LocalsFnRoot();
 | 
			
		||||
    private fnStmtIdLabelMap: { [stmtId: number]: string } = {};
 | 
			
		||||
    private fnLabelNameMap: { [name: string]: string } = {};
 | 
			
		||||
    private returnStack: Label[] = [];
 | 
			
		||||
    private breakStack: Label[] = [];
 | 
			
		||||
 | 
			
		||||
    public constructor(
 | 
			
		||||
        private monoFns: MonoFnsMap,
 | 
			
		||||
        private callMap: MonoCallNameGenMap,
 | 
			
		||||
        private lastPos: Pos,
 | 
			
		||||
    ) {}
 | 
			
		||||
    public constructor(private lastPos: Pos) {}
 | 
			
		||||
 | 
			
		||||
    public lower(): { program: number[]; fnNames: FnNamesMap } {
 | 
			
		||||
        const fnLabelNameMap: FnLabelMap = {};
 | 
			
		||||
        for (const nameGen in this.monoFns) {
 | 
			
		||||
            fnLabelNameMap[nameGen] = nameGen;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.addPrelimiary();
 | 
			
		||||
 | 
			
		||||
        for (const fn of Object.values(this.monoFns)) {
 | 
			
		||||
            const fnProgram = new MonoFnLowerer(
 | 
			
		||||
                fn,
 | 
			
		||||
                this.program.fork(),
 | 
			
		||||
                this.callMap,
 | 
			
		||||
            ).lower();
 | 
			
		||||
            this.program.join(fnProgram);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.addConcluding();
 | 
			
		||||
 | 
			
		||||
        const { program, locs } = this.program.assemble();
 | 
			
		||||
        const fnNames: FnNamesMap = {};
 | 
			
		||||
        for (const label in locs) {
 | 
			
		||||
            if (label in fnLabelNameMap) {
 | 
			
		||||
                fnNames[locs[label]] = fnLabelNameMap[label];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return { program, fnNames };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private addPrelimiary() {
 | 
			
		||||
    public lower(stmts: Stmt[]) {
 | 
			
		||||
        this.addClearingSourceMap();
 | 
			
		||||
        this.program.add(Ops.PushPtr, { label: "main" });
 | 
			
		||||
        this.program.add(Ops.Call, 0);
 | 
			
		||||
        this.program.add(Ops.PushPtr, { label: "_exit" });
 | 
			
		||||
        this.program.add(Ops.Jump);
 | 
			
		||||
        this.scoutFnHeaders(stmts);
 | 
			
		||||
        for (const stmt of stmts) {
 | 
			
		||||
            this.lowerStaticStmt(stmt);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private addConcluding() {
 | 
			
		||||
        this.program.setLabel({ label: "_exit" });
 | 
			
		||||
        this.addSourceMap(this.lastPos);
 | 
			
		||||
        this.program.add(Ops.Pop);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public finish(): { program: number[]; fnNames: FnNamesMap } {
 | 
			
		||||
        const { program, locs } = this.program.assemble();
 | 
			
		||||
        const fnNames: FnNamesMap = {};
 | 
			
		||||
        for (const label in locs) {
 | 
			
		||||
            if (label in this.fnLabelNameMap) {
 | 
			
		||||
                fnNames[locs[label]] = this.fnLabelNameMap[label];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return { program, fnNames };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private addSourceMap({ index, line, col }: Pos) {
 | 
			
		||||
        this.program.add(Ops.SourceMap, index, line, col);
 | 
			
		||||
    }
 | 
			
		||||
@ -68,84 +51,30 @@ export class Lowerer {
 | 
			
		||||
        this.program.add(Ops.SourceMap, 0, 1, 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public printProgram() {
 | 
			
		||||
        this.program.printProgram();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FnLabelMap = { [nameGen: string]: string };
 | 
			
		||||
 | 
			
		||||
class MonoFnLowerer {
 | 
			
		||||
    private locals: Locals = new LocalsFnRoot();
 | 
			
		||||
    private returnStack: Label[] = [];
 | 
			
		||||
    private breakStack: Label[] = [];
 | 
			
		||||
 | 
			
		||||
    public constructor(
 | 
			
		||||
        private fn: MonoFn,
 | 
			
		||||
        private program: Assembler,
 | 
			
		||||
        private callMap: MonoCallNameGenMap,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public lower(): Assembler {
 | 
			
		||||
        this.lowerFnStmt(this.fn.stmt);
 | 
			
		||||
        return this.program;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerFnStmt(stmt: Stmt) {
 | 
			
		||||
    private scoutFnHeaders(stmts: Stmt[]) {
 | 
			
		||||
        for (const stmt of stmts) {
 | 
			
		||||
            if (stmt.kind.type !== "fn") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            const label = stmt.kind.ident === "main"
 | 
			
		||||
                ? "main"
 | 
			
		||||
                : `${stmt.kind.ident}_${stmt.id}`;
 | 
			
		||||
            this.fnStmtIdLabelMap[stmt.id] = label;
 | 
			
		||||
        }
 | 
			
		||||
        const label = this.fn.nameGen;
 | 
			
		||||
        this.program.setLabel({ label });
 | 
			
		||||
        this.addSourceMap(stmt.pos);
 | 
			
		||||
 | 
			
		||||
        const outerLocals = this.locals;
 | 
			
		||||
        const fnRoot = new LocalsFnRoot(outerLocals);
 | 
			
		||||
        const outerProgram = this.program;
 | 
			
		||||
 | 
			
		||||
        const returnLabel = this.program.makeLabel();
 | 
			
		||||
        this.returnStack.push(returnLabel);
 | 
			
		||||
 | 
			
		||||
        this.program = outerProgram.fork();
 | 
			
		||||
        this.locals = fnRoot;
 | 
			
		||||
        for (const { ident } of stmt.kind.params) {
 | 
			
		||||
            this.locals.allocSym(ident);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        const annos = new AnnoView(stmt.details);
 | 
			
		||||
        if (annos.has("builtin")) {
 | 
			
		||||
            const anno = annos.get("builtin");
 | 
			
		||||
            if (!anno) {
 | 
			
		||||
                throw new Error();
 | 
			
		||||
    private lowerStaticStmt(stmt: Stmt) {
 | 
			
		||||
        switch (stmt.kind.type) {
 | 
			
		||||
            case "fn":
 | 
			
		||||
                return this.lowerFnStmt(stmt);
 | 
			
		||||
            case "error":
 | 
			
		||||
            case "break":
 | 
			
		||||
            case "return":
 | 
			
		||||
            case "let":
 | 
			
		||||
            case "assign":
 | 
			
		||||
            case "expr":
 | 
			
		||||
        }
 | 
			
		||||
            this.lowerFnBuiltinBody(anno.args);
 | 
			
		||||
        } else if (annos.has("remainder")) {
 | 
			
		||||
            this.program.add(Ops.Remainder);
 | 
			
		||||
        } else {
 | 
			
		||||
            this.lowerExpr(stmt.kind.body);
 | 
			
		||||
        }
 | 
			
		||||
        this.locals = outerLocals;
 | 
			
		||||
 | 
			
		||||
        const localAmount = fnRoot.stackReserved() -
 | 
			
		||||
            stmt.kind.params.length;
 | 
			
		||||
        for (let i = 0; i < localAmount; ++i) {
 | 
			
		||||
            outerProgram.add(Ops.PushNull);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.returnStack.pop();
 | 
			
		||||
        this.program.setLabel(returnLabel);
 | 
			
		||||
        this.program.add(Ops.Return);
 | 
			
		||||
 | 
			
		||||
        outerProgram.join(this.program);
 | 
			
		||||
        this.program = outerProgram;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private addSourceMap({ index, line, col }: Pos) {
 | 
			
		||||
        this.program.add(Ops.SourceMap, index, line, col);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private addClearingSourceMap() {
 | 
			
		||||
        this.program.add(Ops.SourceMap, 0, 1, 1);
 | 
			
		||||
        throw new Error(`unhandled static statement '${stmt.kind.type}'`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerStmt(stmt: Stmt) {
 | 
			
		||||
@ -178,7 +107,7 @@ class MonoFnLowerer {
 | 
			
		||||
        switch (stmt.kind.subject.kind.type) {
 | 
			
		||||
            case "field": {
 | 
			
		||||
                this.lowerExpr(stmt.kind.subject.kind.subject);
 | 
			
		||||
                this.program.add(Ops.PushString, stmt.kind.subject.kind.ident);
 | 
			
		||||
                this.program.add(Ops.PushString, stmt.kind.subject.kind.value);
 | 
			
		||||
                this.program.add(Ops.Builtin, Builtins.StructSet);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
@ -224,6 +153,52 @@ class MonoFnLowerer {
 | 
			
		||||
        this.program.add(Ops.Jump);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerFnStmt(stmt: Stmt) {
 | 
			
		||||
        if (stmt.kind.type !== "fn") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const label = stmt.kind.ident === "main"
 | 
			
		||||
            ? "main"
 | 
			
		||||
            : `${stmt.kind.ident}_${stmt.id}`;
 | 
			
		||||
        this.program.setLabel({ label });
 | 
			
		||||
        this.fnLabelNameMap[label] = stmt.kind.ident;
 | 
			
		||||
        this.addSourceMap(stmt.pos);
 | 
			
		||||
 | 
			
		||||
        const outerLocals = this.locals;
 | 
			
		||||
        const fnRoot = new LocalsFnRoot(outerLocals);
 | 
			
		||||
        const outerProgram = this.program;
 | 
			
		||||
 | 
			
		||||
        const returnLabel = this.program.makeLabel();
 | 
			
		||||
        this.returnStack.push(returnLabel);
 | 
			
		||||
 | 
			
		||||
        this.program = outerProgram.fork();
 | 
			
		||||
        this.locals = fnRoot;
 | 
			
		||||
        for (const { ident } of stmt.kind.params) {
 | 
			
		||||
            this.locals.allocSym(ident);
 | 
			
		||||
        }
 | 
			
		||||
        if (stmt.kind.anno?.ident === "builtin") {
 | 
			
		||||
            this.lowerFnBuiltinBody(stmt.kind.anno.values);
 | 
			
		||||
        } else if (stmt.kind.anno?.ident === "remainder") {
 | 
			
		||||
            this.program.add(Ops.Remainder);
 | 
			
		||||
        } else {
 | 
			
		||||
            this.lowerExpr(stmt.kind.body);
 | 
			
		||||
        }
 | 
			
		||||
        this.locals = outerLocals;
 | 
			
		||||
 | 
			
		||||
        const localAmount = fnRoot.stackReserved() -
 | 
			
		||||
            stmt.kind.params.length;
 | 
			
		||||
        for (let i = 0; i < localAmount; ++i) {
 | 
			
		||||
            outerProgram.add(Ops.PushNull);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.returnStack.pop();
 | 
			
		||||
        this.program.setLabel(returnLabel);
 | 
			
		||||
        this.program.add(Ops.Return);
 | 
			
		||||
 | 
			
		||||
        outerProgram.join(this.program);
 | 
			
		||||
        this.program = outerProgram;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerFnBuiltinBody(annoArgs: Expr[]) {
 | 
			
		||||
        if (annoArgs.length !== 1) {
 | 
			
		||||
            throw new Error("invalid # of arguments to builtin annotation");
 | 
			
		||||
@ -234,7 +209,7 @@ class MonoFnLowerer {
 | 
			
		||||
                `unexpected argument type '${anno.kind.type}' expected 'ident'`,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        const value = anno.kind.ident;
 | 
			
		||||
        const value = anno.kind.value;
 | 
			
		||||
        const builtin = Object.entries(Builtins).find((entry) =>
 | 
			
		||||
            entry[0] === value
 | 
			
		||||
        )?.[1];
 | 
			
		||||
@ -277,13 +252,11 @@ class MonoFnLowerer {
 | 
			
		||||
            case "group":
 | 
			
		||||
                return void this.lowerExpr(expr.kind.expr);
 | 
			
		||||
            case "field":
 | 
			
		||||
                return this.lowerFieldExpr(expr);
 | 
			
		||||
                break;
 | 
			
		||||
            case "index":
 | 
			
		||||
                return this.lowerIndexExpr(expr);
 | 
			
		||||
            case "call":
 | 
			
		||||
                return this.lowerCallExpr(expr);
 | 
			
		||||
            case "etype_args":
 | 
			
		||||
                return this.lowerETypeArgsExpr(expr);
 | 
			
		||||
            case "unary":
 | 
			
		||||
                return this.lowerUnaryExpr(expr);
 | 
			
		||||
            case "binary":
 | 
			
		||||
@ -298,20 +271,6 @@ class MonoFnLowerer {
 | 
			
		||||
        throw new Error(`unhandled expr '${expr.kind.type}'`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerFieldExpr(expr: Expr) {
 | 
			
		||||
        if (expr.kind.type !== "field") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        this.lowerExpr(expr.kind.subject);
 | 
			
		||||
        this.program.add(Ops.PushString, expr.kind.ident);
 | 
			
		||||
 | 
			
		||||
        if (expr.kind.subject.vtype?.type == "struct") {
 | 
			
		||||
            this.program.add(Ops.Builtin, Builtins.StructAt);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        throw new Error(`unhandled field subject type '${expr.kind.subject}'`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerIndexExpr(expr: Expr) {
 | 
			
		||||
        if (expr.kind.type !== "index") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
@ -347,42 +306,8 @@ class MonoFnLowerer {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (expr.kind.sym.type === "fn") {
 | 
			
		||||
            // Is this smart? Well, my presumption is
 | 
			
		||||
            // that it isn't. The underlying problem, which
 | 
			
		||||
            // this solutions raison d'être is to solve, is
 | 
			
		||||
            // that the compiler, as it d'être's currently
 | 
			
		||||
            // doesn't support checking and infering generic
 | 
			
		||||
            // fn args all the way down to the sym. Therefore,
 | 
			
		||||
            // when a sym is checked in a call expr, we can't
 | 
			
		||||
            // really do anything useful. Instead the actual
 | 
			
		||||
            // function pointer pointing to the actual
 | 
			
		||||
            // monomorphized function is emplaced when
 | 
			
		||||
            // lowering the call expression itself. But what
 | 
			
		||||
            // should we do then, if the user decides to
 | 
			
		||||
            // assign a function to a local? You might ask.
 | 
			
		||||
            // You see, that's where the problem lies.
 | 
			
		||||
            // My current, very thought out solution, as
 | 
			
		||||
            // you can read below, is to push a null pointer,
 | 
			
		||||
            // for it to then be replaced later. This will
 | 
			
		||||
            // probably cause many hastles in the future
 | 
			
		||||
            // for myself in particular, when trying to
 | 
			
		||||
            // decipher the lowerer's output. So if you're
 | 
			
		||||
            // the unlucky girl, who has tried for ages to
 | 
			
		||||
            // decipher why a zero value is pushed and then
 | 
			
		||||
            // later replaced, and then you finally
 | 
			
		||||
            // stumbled upon this here implementation,
 | 
			
		||||
            // let me first say, I'm so sorry. At the time
 | 
			
		||||
            // of writing, I really haven't thought out
 | 
			
		||||
            // very well, how the generic call system should
 | 
			
		||||
            // work, and it's therefore a bit flaky, and the
 | 
			
		||||
            // implementation kinda looks like it was
 | 
			
		||||
            // implementated by a girl who didn't really
 | 
			
		||||
            // understand very well what they were
 | 
			
		||||
            // implementing at the time that they were
 | 
			
		||||
            // implementing it. Anyway, I just wanted to
 | 
			
		||||
            // apologize. Happy coding.
 | 
			
		||||
            // -Your favorite compiler girl.
 | 
			
		||||
            this.program.add(Ops.PushPtr, 0);
 | 
			
		||||
            const label = this.fnStmtIdLabelMap[expr.kind.sym.stmt.id];
 | 
			
		||||
            this.program.add(Ops.PushPtr, { label });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        throw new Error(`unhandled sym type '${expr.kind.sym.type}'`);
 | 
			
		||||
@ -505,18 +430,6 @@ class MonoFnLowerer {
 | 
			
		||||
                default:
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (vtype.type === "bool") {
 | 
			
		||||
            switch (expr.kind.binaryType) {
 | 
			
		||||
                case "==":
 | 
			
		||||
                    this.program.add(Ops.And);
 | 
			
		||||
                    return;
 | 
			
		||||
                case "!=":
 | 
			
		||||
                    this.program.add(Ops.And);
 | 
			
		||||
                    this.program.add(Ops.Not);
 | 
			
		||||
                    return;
 | 
			
		||||
                default:
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (vtype.type === "string") {
 | 
			
		||||
            if (expr.kind.binaryType === "+") {
 | 
			
		||||
                this.program.add(Ops.Builtin, Builtins.StringConcat);
 | 
			
		||||
@ -549,18 +462,9 @@ class MonoFnLowerer {
 | 
			
		||||
            this.lowerExpr(arg);
 | 
			
		||||
        }
 | 
			
		||||
        this.lowerExpr(expr.kind.subject);
 | 
			
		||||
        this.program.add(Ops.Pop);
 | 
			
		||||
        this.program.add(Ops.PushPtr, { label: this.callMap[expr.id] });
 | 
			
		||||
        this.program.add(Ops.Call, expr.kind.args.length);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerETypeArgsExpr(expr: Expr) {
 | 
			
		||||
        if (expr.kind.type !== "etype_args") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        this.lowerExpr(expr.kind.subject);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerIfExpr(expr: Expr) {
 | 
			
		||||
        if (expr.kind.type !== "if") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
@ -624,6 +528,7 @@ class MonoFnLowerer {
 | 
			
		||||
        }
 | 
			
		||||
        const outerLocals = this.locals;
 | 
			
		||||
        this.locals = new LocalLeaf(this.locals);
 | 
			
		||||
        this.scoutFnHeaders(expr.kind.stmts);
 | 
			
		||||
        for (const stmt of expr.kind.stmts) {
 | 
			
		||||
            this.addSourceMap(stmt.pos);
 | 
			
		||||
            this.lowerStmt(stmt);
 | 
			
		||||
@ -636,4 +541,8 @@ class MonoFnLowerer {
 | 
			
		||||
        }
 | 
			
		||||
        this.locals = outerLocals;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public printProgram() {
 | 
			
		||||
        this.program.printProgram();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										102
									
								
								compiler/main.ts
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								compiler/main.ts
									
									
									
									
									
								
							@ -1,101 +1,5 @@
 | 
			
		||||
import * as path from "jsr:@std/path";
 | 
			
		||||
import { Parser } from "./parser/parser.ts";
 | 
			
		||||
import * as ast from "./ast/mod.ts";
 | 
			
		||||
import { Ctx } from "./ctx.ts";
 | 
			
		||||
import { File } from "./ctx.ts";
 | 
			
		||||
import { Compiler } from "./compiler.ts";
 | 
			
		||||
 | 
			
		||||
export type Pack = {
 | 
			
		||||
    rootMod: Mod;
 | 
			
		||||
};
 | 
			
		||||
const { program } = await new Compiler(Deno.args[0]).compile();
 | 
			
		||||
 | 
			
		||||
export type Mod = null;
 | 
			
		||||
 | 
			
		||||
export interface PackEmitter {
 | 
			
		||||
    emit(pack: Pack): void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class PackCompiler {
 | 
			
		||||
    private ctx = new Ctx();
 | 
			
		||||
 | 
			
		||||
    public constructor(
 | 
			
		||||
        private entryFilePath: string,
 | 
			
		||||
        private emitter: PackEmitter,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public compile() {
 | 
			
		||||
        FileTreeAstCollector
 | 
			
		||||
            .fromEntryFile(this.ctx, this.entryFilePath)
 | 
			
		||||
            .collect();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type _P = { file: File };
 | 
			
		||||
export class FileTreeAstCollector implements ast.Visitor<[_P]> {
 | 
			
		||||
    private subFilePromise = Promise.resolve();
 | 
			
		||||
 | 
			
		||||
    private constructor(
 | 
			
		||||
        private ctx: Ctx,
 | 
			
		||||
        private superFile: File | undefined,
 | 
			
		||||
        private ident: string,
 | 
			
		||||
        private absPath: string,
 | 
			
		||||
        private relPath: string,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public static fromEntryFile(
 | 
			
		||||
        ctx: Ctx,
 | 
			
		||||
        entryFilePath: string,
 | 
			
		||||
    ): FileTreeAstCollector {
 | 
			
		||||
        return new FileTreeAstCollector(
 | 
			
		||||
            ctx,
 | 
			
		||||
            undefined,
 | 
			
		||||
            "root",
 | 
			
		||||
            entryFilePath,
 | 
			
		||||
            entryFilePath,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async collect(): Promise<void> {
 | 
			
		||||
        const text = await Deno.readTextFile(this.absPath);
 | 
			
		||||
        const file = this.ctx.addFile(
 | 
			
		||||
            this.ident,
 | 
			
		||||
            this.absPath,
 | 
			
		||||
            this.relPath,
 | 
			
		||||
            this.superFile,
 | 
			
		||||
            text,
 | 
			
		||||
        );
 | 
			
		||||
        const fileAst = new Parser(file, text).parse();
 | 
			
		||||
        this.ctx.addFileAst(file, fileAst);
 | 
			
		||||
        ast.visitFile(this, fileAst, { file });
 | 
			
		||||
        await this.subFilePromise;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitModFileItem(
 | 
			
		||||
        item: ast.Item,
 | 
			
		||||
        kind: ast.ModFileItem,
 | 
			
		||||
        { file }: _P,
 | 
			
		||||
    ): ast.VisitRes {
 | 
			
		||||
        const { ident: { text: ident }, filePath: relPath } = kind;
 | 
			
		||||
        const absPath = path.join(path.dirname(this.absPath), relPath);
 | 
			
		||||
        this.subFilePromise = this.subFilePromise
 | 
			
		||||
            .then(() => {
 | 
			
		||||
                if (this.ctx.fileHasChildWithIdent(file, ident)) {
 | 
			
		||||
                    this.ctx.report({
 | 
			
		||||
                        severity: "fatal",
 | 
			
		||||
                        msg: `module '${ident}' already declared`,
 | 
			
		||||
                        file,
 | 
			
		||||
                        span: item.span,
 | 
			
		||||
                    });
 | 
			
		||||
                    Deno.exit(1);
 | 
			
		||||
                }
 | 
			
		||||
                return new FileTreeAstCollector(
 | 
			
		||||
                    this.ctx,
 | 
			
		||||
                    file,
 | 
			
		||||
                    ident,
 | 
			
		||||
                    absPath,
 | 
			
		||||
                    relPath,
 | 
			
		||||
                )
 | 
			
		||||
                    .collect();
 | 
			
		||||
            });
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
await Deno.writeTextFile("out.slgbc", JSON.stringify(program));
 | 
			
		||||
 | 
			
		||||
@ -1,241 +0,0 @@
 | 
			
		||||
import type { Syms } from "./resolver_syms.ts";
 | 
			
		||||
import { Pos } from "./token.ts";
 | 
			
		||||
import { GenericArgsMap, VType } from "./vtype.ts";
 | 
			
		||||
 | 
			
		||||
export type Mod = {
 | 
			
		||||
    filePath: string;
 | 
			
		||||
    ast: Stmt[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Stmt = {
 | 
			
		||||
    kind: StmtKind;
 | 
			
		||||
    pos: Pos;
 | 
			
		||||
    details?: StmtDetails;
 | 
			
		||||
    id: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type StmtKind =
 | 
			
		||||
    | { type: "error" }
 | 
			
		||||
    | { type: "mod_block"; ident: string; stmts: Stmt[] }
 | 
			
		||||
    | { type: "mod_file"; ident: string; filePath: string }
 | 
			
		||||
    | { type: "mod"; ident: string; mod: Mod }
 | 
			
		||||
    | { type: "break"; expr?: Expr }
 | 
			
		||||
    | { type: "return"; expr?: Expr }
 | 
			
		||||
    | FnStmtKind
 | 
			
		||||
    | { type: "let"; param: Param; value: Expr }
 | 
			
		||||
    | { type: "type_alias"; param: Param }
 | 
			
		||||
    | { type: "assign"; assignType: AssignType; subject: Expr; value: Expr }
 | 
			
		||||
    | { type: "expr"; expr: Expr };
 | 
			
		||||
 | 
			
		||||
export type FnStmtKind = {
 | 
			
		||||
    type: "fn";
 | 
			
		||||
    ident: string;
 | 
			
		||||
    genericParams?: GenericParam[];
 | 
			
		||||
    params: Param[];
 | 
			
		||||
    returnType?: EType;
 | 
			
		||||
    body: Expr;
 | 
			
		||||
    sym?: Sym;
 | 
			
		||||
    vtype?: VType;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type AssignType = "=" | "+=" | "-=";
 | 
			
		||||
 | 
			
		||||
export type StmtDetails = {
 | 
			
		||||
    pub: boolean;
 | 
			
		||||
    annos: Anno[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Expr = {
 | 
			
		||||
    kind: ExprKind;
 | 
			
		||||
    pos: Pos;
 | 
			
		||||
    vtype?: VType;
 | 
			
		||||
    id: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ExprKind =
 | 
			
		||||
    | { type: "error" }
 | 
			
		||||
    | { type: "int"; value: number }
 | 
			
		||||
    | { type: "string"; value: string }
 | 
			
		||||
    | { type: "ident"; ident: string }
 | 
			
		||||
    | {
 | 
			
		||||
        type: "sym";
 | 
			
		||||
        ident: string;
 | 
			
		||||
        sym: Sym;
 | 
			
		||||
    }
 | 
			
		||||
    | { type: "group"; expr: Expr }
 | 
			
		||||
    | { type: "ref"; subject: Expr }
 | 
			
		||||
    | { type: "ref_mut"; subject: Expr }
 | 
			
		||||
    | { type: "deref"; subject: Expr }
 | 
			
		||||
    | { type: "array"; exprs: Expr[] }
 | 
			
		||||
    | { type: "struct"; fields: Field[] }
 | 
			
		||||
    | { type: "field"; subject: Expr; ident: string }
 | 
			
		||||
    | { type: "index"; subject: Expr; value: Expr }
 | 
			
		||||
    | {
 | 
			
		||||
        type: "call";
 | 
			
		||||
        subject: Expr;
 | 
			
		||||
        args: Expr[];
 | 
			
		||||
        genericArgs?: GenericArgsMap;
 | 
			
		||||
    }
 | 
			
		||||
    | { type: "path"; subject: Expr; ident: string }
 | 
			
		||||
    | { type: "etype_args"; subject: Expr; etypeArgs: EType[] }
 | 
			
		||||
    | { type: "unary"; unaryType: UnaryType; subject: Expr }
 | 
			
		||||
    | { type: "binary"; binaryType: BinaryType; left: Expr; right: Expr }
 | 
			
		||||
    | { type: "if"; cond: Expr; truthy: Expr; falsy?: Expr; elsePos?: Pos }
 | 
			
		||||
    | { type: "bool"; value: boolean }
 | 
			
		||||
    | { type: "null" }
 | 
			
		||||
    | { type: "loop"; body: Expr }
 | 
			
		||||
    | { type: "block"; stmts: Stmt[]; expr?: Expr }
 | 
			
		||||
    | { type: "while"; cond: Expr; body: Expr }
 | 
			
		||||
    | { type: "for_in"; param: Param; value: Expr; body: Expr }
 | 
			
		||||
    | {
 | 
			
		||||
        type: "for";
 | 
			
		||||
        decl?: Stmt;
 | 
			
		||||
        cond?: Expr;
 | 
			
		||||
        incr?: Stmt;
 | 
			
		||||
        body: Expr;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
export type UnaryType = "not" | "-";
 | 
			
		||||
export type BinaryType =
 | 
			
		||||
    | "+"
 | 
			
		||||
    | "*"
 | 
			
		||||
    | "=="
 | 
			
		||||
    | "-"
 | 
			
		||||
    | "/"
 | 
			
		||||
    | "!="
 | 
			
		||||
    | "<"
 | 
			
		||||
    | ">"
 | 
			
		||||
    | "<="
 | 
			
		||||
    | ">="
 | 
			
		||||
    | "or"
 | 
			
		||||
    | "and";
 | 
			
		||||
 | 
			
		||||
export type Field = {
 | 
			
		||||
    ident: string;
 | 
			
		||||
    expr: Expr;
 | 
			
		||||
    pos: Pos;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Param = {
 | 
			
		||||
    id: number;
 | 
			
		||||
    index?: number;
 | 
			
		||||
    ident: string;
 | 
			
		||||
    mut: boolean;
 | 
			
		||||
    etype?: EType;
 | 
			
		||||
    pos: Pos;
 | 
			
		||||
    sym?: Sym;
 | 
			
		||||
    vtype?: VType;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Sym = {
 | 
			
		||||
    ident: string;
 | 
			
		||||
    fullPath: string;
 | 
			
		||||
    pos?: Pos;
 | 
			
		||||
} & SymKind;
 | 
			
		||||
 | 
			
		||||
export type SymKind =
 | 
			
		||||
    | { type: "let"; stmt: Stmt; param: Param }
 | 
			
		||||
    | { type: "let_static"; stmt: Stmt; param: Param }
 | 
			
		||||
    | { type: "type_alias"; stmt: Stmt; param: Param }
 | 
			
		||||
    | { type: "fn"; stmt: Stmt }
 | 
			
		||||
    | { type: "fn_param"; param: Param }
 | 
			
		||||
    | { type: "closure"; inner: Sym }
 | 
			
		||||
    | { type: "generic"; stmt: Stmt; genericParam: GenericParam }
 | 
			
		||||
    | { type: "mod"; syms: Syms };
 | 
			
		||||
 | 
			
		||||
export type EType = {
 | 
			
		||||
    kind: ETypeKind;
 | 
			
		||||
    pos: Pos;
 | 
			
		||||
    id: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ETypeKind =
 | 
			
		||||
    | { type: "error" }
 | 
			
		||||
    | { type: "null" }
 | 
			
		||||
    | { type: "int" }
 | 
			
		||||
    | { type: "bool" }
 | 
			
		||||
    | { type: "string" }
 | 
			
		||||
    | { type: "ident"; ident: string }
 | 
			
		||||
    | {
 | 
			
		||||
        type: "sym";
 | 
			
		||||
        ident: string;
 | 
			
		||||
        sym: Sym;
 | 
			
		||||
    }
 | 
			
		||||
    | { type: "ref"; subject: EType }
 | 
			
		||||
    | { type: "ref_mut"; subject: EType }
 | 
			
		||||
    | { type: "ptr"; subject: EType }
 | 
			
		||||
    | { type: "ptr_mut"; subject: EType }
 | 
			
		||||
    | { type: "array"; subject: EType }
 | 
			
		||||
    | { type: "struct"; fields: Param[] }
 | 
			
		||||
    | { type: "type_of"; expr: Expr };
 | 
			
		||||
 | 
			
		||||
export type GenericParam = {
 | 
			
		||||
    id: number;
 | 
			
		||||
    index: number;
 | 
			
		||||
    ident: string;
 | 
			
		||||
    pos: Pos;
 | 
			
		||||
    vtype?: VType;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Anno = {
 | 
			
		||||
    ident: string;
 | 
			
		||||
    args: Expr[];
 | 
			
		||||
    pos: Pos;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export class AstCreator {
 | 
			
		||||
    private nextNodeId = 0;
 | 
			
		||||
 | 
			
		||||
    public stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt {
 | 
			
		||||
        const id = this.genId();
 | 
			
		||||
        return { kind, pos, details, id };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public expr(kind: ExprKind, pos: Pos): Expr {
 | 
			
		||||
        const id = this.genId();
 | 
			
		||||
        return { kind, pos, id };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public etype(kind: ETypeKind, pos: Pos): EType {
 | 
			
		||||
        const id = this.genId();
 | 
			
		||||
        return { kind, pos, id };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public param(val: Omit<Param, "id">): Param {
 | 
			
		||||
        const id = this.genId();
 | 
			
		||||
        return { ...val, id };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public genericParam(val: Omit<GenericParam, "id">): GenericParam {
 | 
			
		||||
        const id = this.genId();
 | 
			
		||||
        return { ...val, id };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private genId(): number {
 | 
			
		||||
        const id = this.nextNodeId;
 | 
			
		||||
        this.nextNodeId += 1;
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class AnnoView {
 | 
			
		||||
    public constructor(private details?: StmtDetails) {}
 | 
			
		||||
 | 
			
		||||
    public has(...idents: string[]): boolean {
 | 
			
		||||
        return this.details?.annos.some((anno) =>
 | 
			
		||||
            idents.some((ident) => anno.ident === ident)
 | 
			
		||||
        ) ?? false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public get(ident: string): Anno {
 | 
			
		||||
        const anno = this.details?.annos.find((anno) => anno.ident === ident);
 | 
			
		||||
        if (!anno) {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        return anno;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function forceType(v: unknown): { type: string } {
 | 
			
		||||
    return v as { type: string };
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,183 +0,0 @@
 | 
			
		||||
import { AstCreator, Mod, Stmt } from "./ast.ts";
 | 
			
		||||
import { Checker } from "./checker.ts";
 | 
			
		||||
import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts";
 | 
			
		||||
import { StructLiteralDesugarer } from "./desugar/struct_literal.ts";
 | 
			
		||||
import { SpecialLoopDesugarer } from "./desugar/special_loop.ts";
 | 
			
		||||
import { Reporter } from "./info.ts";
 | 
			
		||||
import { Lexer } from "./lexer.ts";
 | 
			
		||||
import { Monomorphizer } from "./mono.ts";
 | 
			
		||||
import { FnNamesMap, Lowerer } from "./lowerer.ts";
 | 
			
		||||
import { Parser } from "./parser.ts";
 | 
			
		||||
import { Resolver } from "./resolver.ts";
 | 
			
		||||
import { AstVisitor, VisitRes, visitStmts } from "./ast_visitor.ts";
 | 
			
		||||
import { Pos } from "./token.ts";
 | 
			
		||||
import { ArrayLiteralDesugarer } from "./desugar/array_literal.ts";
 | 
			
		||||
import { mirOpCount, printMir } from "./middle/mir.ts";
 | 
			
		||||
 | 
			
		||||
import * as path from "jsr:@std/path";
 | 
			
		||||
import { lowerAst } from "./middle/lower_ast.ts";
 | 
			
		||||
import { eliminateUnusedLocals } from "./middle/elim_unused_local.ts";
 | 
			
		||||
import {
 | 
			
		||||
    eliminateOnlyChildsBlocks,
 | 
			
		||||
    eliminateUnreachableBlocks,
 | 
			
		||||
} from "./middle/elim_blocks.ts";
 | 
			
		||||
import { checkBorrows } from "./middle/borrow_checker.ts";
 | 
			
		||||
import { makeMoveCopyExplicit } from "./middle/explicit_move_copy.ts";
 | 
			
		||||
 | 
			
		||||
export type CompileResult = {
 | 
			
		||||
    program: number[];
 | 
			
		||||
    fnNames: FnNamesMap;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export class Compiler {
 | 
			
		||||
    private astCreator = new AstCreator();
 | 
			
		||||
    private reporter;
 | 
			
		||||
 | 
			
		||||
    public constructor(private startFilePath: string) {
 | 
			
		||||
        this.reporter = new Reporter(this.startFilePath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async compile(): Promise<CompileResult> {
 | 
			
		||||
        const { ast } = new ModTree(
 | 
			
		||||
            this.startFilePath,
 | 
			
		||||
            this.astCreator,
 | 
			
		||||
            this.reporter,
 | 
			
		||||
        ).resolve();
 | 
			
		||||
 | 
			
		||||
        new SpecialLoopDesugarer(this.astCreator).desugar(ast);
 | 
			
		||||
        new ArrayLiteralDesugarer(this.astCreator).desugar(ast);
 | 
			
		||||
        new StructLiteralDesugarer(this.astCreator).desugar(ast);
 | 
			
		||||
 | 
			
		||||
        new Resolver(this.reporter).resolve(ast);
 | 
			
		||||
 | 
			
		||||
        new CompoundAssignDesugarer(this.astCreator).desugar(ast);
 | 
			
		||||
 | 
			
		||||
        new Checker(this.reporter).check(ast);
 | 
			
		||||
 | 
			
		||||
        //const mir = lowerAst(ast);
 | 
			
		||||
        //
 | 
			
		||||
        //console.log("Before optimizations:");
 | 
			
		||||
        //printMir(mir);
 | 
			
		||||
 | 
			
		||||
        //const mirHistory = [mirOpCount(mir)];
 | 
			
		||||
        //for (let i = 0; i < 1; ++i) {
 | 
			
		||||
        //    eliminateUnusedLocals(mir, this.reporter, mirHistory.length === 1);
 | 
			
		||||
        //    eliminateOnlyChildsBlocks(mir);
 | 
			
		||||
        //    eliminateUnreachableBlocks(mir);
 | 
			
		||||
        //    eliminateTransientVals(mir);
 | 
			
		||||
        //
 | 
			
		||||
        //    const opCount = mirOpCount(mir);
 | 
			
		||||
        //    const histOccurence = mirHistory
 | 
			
		||||
        //        .filter((v) => v === opCount).length;
 | 
			
		||||
        //    if (histOccurence >= 2) {
 | 
			
		||||
        //        break;
 | 
			
		||||
        //    }
 | 
			
		||||
        //    mirHistory.push(opCount);
 | 
			
		||||
        //}
 | 
			
		||||
        //
 | 
			
		||||
        //console.log("After optimizations:");
 | 
			
		||||
        //printMir(mir);
 | 
			
		||||
 | 
			
		||||
        if (this.reporter.errorOccured()) {
 | 
			
		||||
            console.error("Errors occurred, stopping compilation.");
 | 
			
		||||
            Deno.exit(1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const mir = lowerAst(ast);
 | 
			
		||||
 | 
			
		||||
        makeMoveCopyExplicit(mir);
 | 
			
		||||
        checkBorrows(mir, this.reporter);
 | 
			
		||||
 | 
			
		||||
        printMir(mir);
 | 
			
		||||
 | 
			
		||||
        //const { monoFns, callMap } = new Monomorphizer(ast).monomorphize();
 | 
			
		||||
        //
 | 
			
		||||
        //const lastPos = await lastPosInTextFile(this.startFilePath);
 | 
			
		||||
        //
 | 
			
		||||
        //const lowerer = new Lowerer(monoFns, callMap, lastPos);
 | 
			
		||||
        //const { program, fnNames } = lowerer.lower();
 | 
			
		||||
        ////lowerer.printProgram();
 | 
			
		||||
        //
 | 
			
		||||
        //return { program, fnNames };
 | 
			
		||||
        return { program: [], fnNames: {} };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ModTree implements AstVisitor<[string]> {
 | 
			
		||||
    constructor(
 | 
			
		||||
        private entryFilePath: string,
 | 
			
		||||
        private astCreator: AstCreator,
 | 
			
		||||
        private reporter: Reporter,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public resolve(): Mod {
 | 
			
		||||
        const entryAst = this.parseFile(this.entryFilePath);
 | 
			
		||||
 | 
			
		||||
        visitStmts(entryAst, this, this.entryFilePath);
 | 
			
		||||
 | 
			
		||||
        return { filePath: this.entryFilePath, ast: entryAst };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseFile(filePath: string): Stmt[] {
 | 
			
		||||
        const text = Deno.readTextFileSync(filePath);
 | 
			
		||||
 | 
			
		||||
        const lexer = new Lexer(text, this.reporter);
 | 
			
		||||
 | 
			
		||||
        const parser = new Parser(lexer, this.astCreator, this.reporter);
 | 
			
		||||
        const ast = parser.parse();
 | 
			
		||||
 | 
			
		||||
        return ast;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitModBlockStmt(stmt: Stmt, filePath: string): VisitRes {
 | 
			
		||||
        if (stmt.kind.type !== "mod_block") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const { ident, stmts: ast } = stmt.kind;
 | 
			
		||||
        stmt.kind = {
 | 
			
		||||
            type: "mod",
 | 
			
		||||
            ident,
 | 
			
		||||
            mod: { filePath, ast },
 | 
			
		||||
        };
 | 
			
		||||
        visitStmts(ast, this, filePath);
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitModFileStmt(stmt: Stmt, filePath: string): VisitRes {
 | 
			
		||||
        if (stmt.kind.type !== "mod_file") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const { ident, filePath: modFilePath } = stmt.kind;
 | 
			
		||||
        const ast = this.parseFile(
 | 
			
		||||
            ident === "std"
 | 
			
		||||
                ? path.join(
 | 
			
		||||
                    path.dirname(path.fromFileUrl(Deno.mainModule)),
 | 
			
		||||
                    "../std/lib.slg",
 | 
			
		||||
                )
 | 
			
		||||
                : path.join(path.dirname(filePath), modFilePath),
 | 
			
		||||
        );
 | 
			
		||||
        stmt.kind = { type: "mod", ident, mod: { filePath, ast } };
 | 
			
		||||
        visitStmts(ast, this, filePath);
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function lastPosInTextFile(filePath: string): Promise<Pos> {
 | 
			
		||||
    const text = await Deno.readTextFile(filePath);
 | 
			
		||||
 | 
			
		||||
    let index = 0;
 | 
			
		||||
    let line = 1;
 | 
			
		||||
    let col = 1;
 | 
			
		||||
 | 
			
		||||
    while (index < text.length) {
 | 
			
		||||
        if (text[index] == "\n") {
 | 
			
		||||
            line += 1;
 | 
			
		||||
            col = 1;
 | 
			
		||||
        } else {
 | 
			
		||||
            col += 1;
 | 
			
		||||
        }
 | 
			
		||||
        index += 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return { index, line, col };
 | 
			
		||||
}
 | 
			
		||||
@ -1,94 +0,0 @@
 | 
			
		||||
import {
 | 
			
		||||
    AstCreator,
 | 
			
		||||
    ETypeKind,
 | 
			
		||||
    Expr,
 | 
			
		||||
    ExprKind,
 | 
			
		||||
    Stmt,
 | 
			
		||||
    StmtKind,
 | 
			
		||||
} from "../ast.ts";
 | 
			
		||||
import { AstVisitor, visitExpr, VisitRes, visitStmts } from "../ast_visitor.ts";
 | 
			
		||||
import { Pos } from "../token.ts";
 | 
			
		||||
 | 
			
		||||
export class ArrayLiteralDesugarer implements AstVisitor {
 | 
			
		||||
    public constructor(
 | 
			
		||||
        private astCreator: AstCreator,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public desugar(stmts: Stmt[]) {
 | 
			
		||||
        visitStmts(stmts, this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitArrayExpr(expr: Expr): VisitRes {
 | 
			
		||||
        if (expr.kind.type !== "array") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const npos: Pos = { index: 0, line: 1, col: 1 };
 | 
			
		||||
        const Expr = (kind: ExprKind, pos = npos) =>
 | 
			
		||||
            this.astCreator.expr(kind, pos);
 | 
			
		||||
        const Stmt = (kind: StmtKind, pos = npos) =>
 | 
			
		||||
            this.astCreator.stmt(kind, pos);
 | 
			
		||||
        const EType = (kind: ETypeKind, pos = npos) =>
 | 
			
		||||
            this.astCreator.etype(kind, pos);
 | 
			
		||||
 | 
			
		||||
        const std = (ident: string): Expr =>
 | 
			
		||||
            Expr({
 | 
			
		||||
                type: "path",
 | 
			
		||||
                subject: Expr({
 | 
			
		||||
                    type: "ident",
 | 
			
		||||
                    ident: "std",
 | 
			
		||||
                }),
 | 
			
		||||
                ident,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        if (expr.kind.exprs.length < 1) {
 | 
			
		||||
            throw new Error("");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        expr.kind = {
 | 
			
		||||
            type: "block",
 | 
			
		||||
            stmts: [
 | 
			
		||||
                Stmt({
 | 
			
		||||
                    type: "let",
 | 
			
		||||
                    param: this.astCreator.param({
 | 
			
		||||
                        ident: "::value",
 | 
			
		||||
                        mut: true,
 | 
			
		||||
                        pos: npos,
 | 
			
		||||
                    }),
 | 
			
		||||
                    value: Expr({
 | 
			
		||||
                        type: "call",
 | 
			
		||||
                        subject: Expr({
 | 
			
		||||
                            type: "etype_args",
 | 
			
		||||
                            subject: std("array_new"),
 | 
			
		||||
                            etypeArgs: [
 | 
			
		||||
                                EType({
 | 
			
		||||
                                    type: "type_of",
 | 
			
		||||
                                    expr: expr.kind.exprs[0],
 | 
			
		||||
                                }),
 | 
			
		||||
                            ],
 | 
			
		||||
                        }),
 | 
			
		||||
                        args: [],
 | 
			
		||||
                    }),
 | 
			
		||||
                }),
 | 
			
		||||
                ...expr.kind.exprs
 | 
			
		||||
                    .map((expr) =>
 | 
			
		||||
                        Stmt({
 | 
			
		||||
                            type: "expr",
 | 
			
		||||
                            expr: Expr({
 | 
			
		||||
                                type: "call",
 | 
			
		||||
                                subject: std("array_push"),
 | 
			
		||||
                                args: [
 | 
			
		||||
                                    Expr({ type: "ident", ident: "::value" }),
 | 
			
		||||
                                    expr,
 | 
			
		||||
                                ],
 | 
			
		||||
                            }),
 | 
			
		||||
                        })
 | 
			
		||||
                    ),
 | 
			
		||||
            ],
 | 
			
		||||
            expr: Expr({ type: "ident", ident: "::value" }),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        visitExpr(expr, this);
 | 
			
		||||
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,101 +0,0 @@
 | 
			
		||||
import {
 | 
			
		||||
    AstCreator,
 | 
			
		||||
    ETypeKind,
 | 
			
		||||
    Expr,
 | 
			
		||||
    ExprKind,
 | 
			
		||||
    Stmt,
 | 
			
		||||
    StmtKind,
 | 
			
		||||
} from "../ast.ts";
 | 
			
		||||
import {
 | 
			
		||||
    AstVisitor,
 | 
			
		||||
    visitField,
 | 
			
		||||
    VisitRes,
 | 
			
		||||
    visitStmts,
 | 
			
		||||
} from "../ast_visitor.ts";
 | 
			
		||||
import { Pos } from "../token.ts";
 | 
			
		||||
 | 
			
		||||
export class StructLiteralDesugarer implements AstVisitor {
 | 
			
		||||
    public constructor(
 | 
			
		||||
        private astCreator: AstCreator,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public desugar(stmts: Stmt[]) {
 | 
			
		||||
        visitStmts(stmts, this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitStructExpr(expr: Expr): VisitRes {
 | 
			
		||||
        if (expr.kind.type !== "struct") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const npos: Pos = { index: 0, line: 1, col: 1 };
 | 
			
		||||
        const Expr = (kind: ExprKind, pos = npos) =>
 | 
			
		||||
            this.astCreator.expr(kind, pos);
 | 
			
		||||
        const Stmt = (kind: StmtKind, pos = npos) =>
 | 
			
		||||
            this.astCreator.stmt(kind, pos);
 | 
			
		||||
        const EType = (kind: ETypeKind, pos = npos) =>
 | 
			
		||||
            this.astCreator.etype(kind, pos);
 | 
			
		||||
 | 
			
		||||
        const std = (ident: string): Expr =>
 | 
			
		||||
            Expr({
 | 
			
		||||
                type: "path",
 | 
			
		||||
                subject: Expr({
 | 
			
		||||
                    type: "ident",
 | 
			
		||||
                    ident: "std",
 | 
			
		||||
                }),
 | 
			
		||||
                ident,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        const fields = expr.kind.fields;
 | 
			
		||||
 | 
			
		||||
        expr.kind = {
 | 
			
		||||
            type: "block",
 | 
			
		||||
            stmts: [
 | 
			
		||||
                Stmt({
 | 
			
		||||
                    type: "let",
 | 
			
		||||
                    param: this.astCreator.param({
 | 
			
		||||
                        ident: "::value",
 | 
			
		||||
                        mut: true,
 | 
			
		||||
                        pos: npos,
 | 
			
		||||
                    }),
 | 
			
		||||
                    value: Expr({
 | 
			
		||||
                        type: "call",
 | 
			
		||||
                        subject: Expr({
 | 
			
		||||
                            type: "etype_args",
 | 
			
		||||
                            subject: std("struct_new"),
 | 
			
		||||
                            etypeArgs: [
 | 
			
		||||
                                EType({
 | 
			
		||||
                                    type: "type_of",
 | 
			
		||||
                                    expr: Expr({ ...expr.kind }),
 | 
			
		||||
                                }),
 | 
			
		||||
                            ],
 | 
			
		||||
                        }),
 | 
			
		||||
                        args: [],
 | 
			
		||||
                    }),
 | 
			
		||||
                }),
 | 
			
		||||
                ...expr.kind.fields
 | 
			
		||||
                    .map((field) =>
 | 
			
		||||
                        Stmt({
 | 
			
		||||
                            type: "assign",
 | 
			
		||||
                            assignType: "=",
 | 
			
		||||
                            subject: Expr({
 | 
			
		||||
                                type: "field",
 | 
			
		||||
                                subject: Expr({
 | 
			
		||||
                                    type: "ident",
 | 
			
		||||
                                    ident: "::value",
 | 
			
		||||
                                }),
 | 
			
		||||
                                ident: field.ident,
 | 
			
		||||
                            }),
 | 
			
		||||
                            value: field.expr,
 | 
			
		||||
                        })
 | 
			
		||||
                    ),
 | 
			
		||||
            ],
 | 
			
		||||
            expr: Expr({ type: "ident", ident: "::value" }),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        for (const field of fields) {
 | 
			
		||||
            visitField(field, this);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,5 +0,0 @@
 | 
			
		||||
import { Compiler } from "./compiler.ts";
 | 
			
		||||
 | 
			
		||||
const { program } = await new Compiler(Deno.args[0]).compile();
 | 
			
		||||
 | 
			
		||||
await Deno.writeTextFile("out.slgbc", JSON.stringify(program));
 | 
			
		||||
@ -1,237 +0,0 @@
 | 
			
		||||
import { Reporter } from "../info.ts";
 | 
			
		||||
import { Pos } from "../token.ts";
 | 
			
		||||
import { createCfg } from "./cfg.ts";
 | 
			
		||||
import { Cfg } from "./cfg.ts";
 | 
			
		||||
import { Block, BlockId, Fn, Local, LocalId, Mir, RValue } from "./mir.ts";
 | 
			
		||||
 | 
			
		||||
export function checkBorrows(
 | 
			
		||||
    mir: Mir,
 | 
			
		||||
    reporter: Reporter,
 | 
			
		||||
) {
 | 
			
		||||
    for (const fn of mir.fns) {
 | 
			
		||||
        new BorrowCheckerFnPass(fn, reporter).pass();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class BorrowCheckerFnPass {
 | 
			
		||||
    private cfg: Cfg;
 | 
			
		||||
 | 
			
		||||
    public constructor(
 | 
			
		||||
        private fn: Fn,
 | 
			
		||||
        private reporter: Reporter,
 | 
			
		||||
    ) {
 | 
			
		||||
        this.cfg = createCfg(this.fn);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public pass() {
 | 
			
		||||
        for (const local of this.fn.locals) {
 | 
			
		||||
            new LocalChecker(local, this.fn, this.cfg, this.reporter).check();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class LocalChecker {
 | 
			
		||||
    private visitedBlocks = new Set<BlockId>();
 | 
			
		||||
 | 
			
		||||
    private assignedTo = false;
 | 
			
		||||
    private moved = false;
 | 
			
		||||
    private borrowed = false;
 | 
			
		||||
    private borrowedMut = false;
 | 
			
		||||
 | 
			
		||||
    private movedPos?: Pos;
 | 
			
		||||
    private borrowedPos?: Pos;
 | 
			
		||||
 | 
			
		||||
    public constructor(
 | 
			
		||||
        private local: Local,
 | 
			
		||||
        private fn: Fn,
 | 
			
		||||
        private cfg: Cfg,
 | 
			
		||||
        private reporter: Reporter,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public check() {
 | 
			
		||||
        this.checkBlock(this.cfg.entry());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private checkBlock(block: Block) {
 | 
			
		||||
        if (this.visitedBlocks.has(block.id)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this.visitedBlocks.add(block.id);
 | 
			
		||||
        for (const op of block.ops) {
 | 
			
		||||
            const ok = op.kind;
 | 
			
		||||
            switch (ok.type) {
 | 
			
		||||
                case "error":
 | 
			
		||||
                    break;
 | 
			
		||||
                case "assign":
 | 
			
		||||
                    this.markDst(ok.dst);
 | 
			
		||||
                    this.markSrc(ok.src);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "ref":
 | 
			
		||||
                case "ptr":
 | 
			
		||||
                    this.markDst(ok.dst);
 | 
			
		||||
                    this.markBorrow(ok.src);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "ref_mut":
 | 
			
		||||
                case "ptr_mut":
 | 
			
		||||
                    this.markDst(ok.dst);
 | 
			
		||||
                    this.markBorrowMut(ok.src);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "deref":
 | 
			
		||||
                    this.markDst(ok.dst);
 | 
			
		||||
                    this.markSrc(ok.src);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "assign_deref":
 | 
			
		||||
                    this.markSrc(ok.subject);
 | 
			
		||||
                    this.markSrc(ok.src);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "field":
 | 
			
		||||
                    this.markDst(ok.dst);
 | 
			
		||||
                    this.markSrc(ok.subject);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "assign_field":
 | 
			
		||||
                    this.markSrc(ok.subject);
 | 
			
		||||
                    this.markSrc(ok.src);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "index":
 | 
			
		||||
                    this.markDst(ok.dst);
 | 
			
		||||
                    this.markSrc(ok.subject);
 | 
			
		||||
                    this.markSrc(ok.index);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "assign_index":
 | 
			
		||||
                    this.markSrc(ok.subject);
 | 
			
		||||
                    this.markSrc(ok.index);
 | 
			
		||||
                    this.markSrc(ok.src);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "call_val":
 | 
			
		||||
                    this.markDst(ok.dst);
 | 
			
		||||
                    this.markSrc(ok.subject);
 | 
			
		||||
                    for (const arg of ok.args) {
 | 
			
		||||
                        this.markSrc(arg);
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                case "binary":
 | 
			
		||||
                    this.markDst(ok.dst);
 | 
			
		||||
                    this.markSrc(ok.left);
 | 
			
		||||
                    this.markSrc(ok.right);
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        const tk = block.ter.kind;
 | 
			
		||||
        switch (tk.type) {
 | 
			
		||||
            case "error":
 | 
			
		||||
                break;
 | 
			
		||||
            case "return":
 | 
			
		||||
                break;
 | 
			
		||||
            case "jump":
 | 
			
		||||
                break;
 | 
			
		||||
            case "if":
 | 
			
		||||
                this.markSrc(tk.cond);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
        for (const child of this.cfg.children(block)) {
 | 
			
		||||
            this.checkBlock(child);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private markDst(localId: LocalId) {
 | 
			
		||||
        if (localId !== this.local.id) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (!this.assignedTo) {
 | 
			
		||||
            this.assignedTo = true;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (!this.local.mut) {
 | 
			
		||||
            this.reportReassignToNonMut();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private markBorrow(localId: LocalId) {
 | 
			
		||||
        if (localId !== this.local.id) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.assignedTo) {
 | 
			
		||||
            this.assignedTo = true;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private markBorrowMut(localId: LocalId) {
 | 
			
		||||
        if (localId !== this.local.id) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.assignedTo) {
 | 
			
		||||
            this.assignedTo = true;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private markSrc(src: RValue) {
 | 
			
		||||
        if (
 | 
			
		||||
            (src.type !== "copy" && src.type !== "move") ||
 | 
			
		||||
            src.id !== this.local.id
 | 
			
		||||
        ) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (src.type === "move") {
 | 
			
		||||
            if (this.moved) {
 | 
			
		||||
                this.reportUseMoved();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.borrowed) {
 | 
			
		||||
                this.reportUseBorrowed();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            this.moved = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private reportReassignToNonMut() {
 | 
			
		||||
        const ident = this.local.sym!.ident;
 | 
			
		||||
        this.reporter.reportError({
 | 
			
		||||
            reporter: "borrow checker",
 | 
			
		||||
            msg: `cannot re-assign to '${ident}' as it was not declared mutable`,
 | 
			
		||||
            pos: this.local.sym!.pos!,
 | 
			
		||||
        });
 | 
			
		||||
        this.reporter.addNote({
 | 
			
		||||
            reporter: "borrow checker",
 | 
			
		||||
            msg: `declared here`,
 | 
			
		||||
            pos: this.local.sym!.pos!,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private reportUseMoved() {
 | 
			
		||||
        const ident = this.local.sym!.ident;
 | 
			
		||||
        this.reporter.reportError({
 | 
			
		||||
            reporter: "borrow checker",
 | 
			
		||||
            msg: `cannot use '${ident}' as it has been moved`,
 | 
			
		||||
            pos: this.local.sym!.pos!,
 | 
			
		||||
        });
 | 
			
		||||
        if (this.movedPos) {
 | 
			
		||||
            this.reporter.addNote({
 | 
			
		||||
                reporter: "borrow checker",
 | 
			
		||||
                msg: `moved here`,
 | 
			
		||||
                pos: this.movedPos,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private reportUseBorrowed() {
 | 
			
		||||
        const ident = this.local.sym!.ident;
 | 
			
		||||
        this.reporter.reportError({
 | 
			
		||||
            reporter: "borrow checker",
 | 
			
		||||
            msg: `cannot use '${ident}' as it has been borrowed`,
 | 
			
		||||
            pos: this.local.sym!.pos!,
 | 
			
		||||
        });
 | 
			
		||||
        if (this.borrowedPos) {
 | 
			
		||||
            this.reporter.addNote({
 | 
			
		||||
                reporter: "borrow checker",
 | 
			
		||||
                msg: `borrowed here`,
 | 
			
		||||
                pos: this.movedPos,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,107 +0,0 @@
 | 
			
		||||
import { Block, BlockId, Fn } from "./mir.ts";
 | 
			
		||||
 | 
			
		||||
export function createCfg(fn: Fn): Cfg {
 | 
			
		||||
    return new CfgBuilder(fn).build();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Cfg {
 | 
			
		||||
    public constructor(
 | 
			
		||||
        private graph: Map<BlockId, CfgNode>,
 | 
			
		||||
        private entry_: BlockId,
 | 
			
		||||
        private exit: BlockId,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public entry(): Block {
 | 
			
		||||
        return this.graph.get(this.entry_)!.block;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public parents(block: Block): Block[] {
 | 
			
		||||
        return this.graph
 | 
			
		||||
            .get(block.id)!.parents
 | 
			
		||||
            .map((id) => this.graph.get(id)!.block);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public children(block: Block): Block[] {
 | 
			
		||||
        return this.graph
 | 
			
		||||
            .get(block.id)!.children
 | 
			
		||||
            .map((id) => this.graph.get(id)!.block);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public index(block: Block): number {
 | 
			
		||||
        return this.graph.get(block.id)!.index;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public print() {
 | 
			
		||||
        for (const [id, node] of this.graph.entries()) {
 | 
			
		||||
            const l = <T>(v: T[]) => v.map((v) => `${v}`).join(", ");
 | 
			
		||||
 | 
			
		||||
            console.log(`graph[${id}] = {`);
 | 
			
		||||
            console.log(`    id: ${node.block.id},`);
 | 
			
		||||
            console.log(`    index: ${node.index},`);
 | 
			
		||||
            console.log(`    parents: [${l(node.parents)}],`);
 | 
			
		||||
            console.log(`    children: [${l(node.children)}],`);
 | 
			
		||||
            console.log(`}`);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CfgNode = {
 | 
			
		||||
    block: Block;
 | 
			
		||||
    index: number;
 | 
			
		||||
    parents: BlockId[];
 | 
			
		||||
    children: BlockId[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class CfgBuilder {
 | 
			
		||||
    private nodes: [Block, number][] = [];
 | 
			
		||||
    private edges: [BlockId, BlockId][] = [];
 | 
			
		||||
 | 
			
		||||
    public constructor(private fn: Fn) {}
 | 
			
		||||
 | 
			
		||||
    public build(): Cfg {
 | 
			
		||||
        for (
 | 
			
		||||
            const [block, index] of this.fn.blocks
 | 
			
		||||
                .map((v, i) => [v, i] as const)
 | 
			
		||||
        ) {
 | 
			
		||||
            this.addNode(block, index);
 | 
			
		||||
 | 
			
		||||
            const tk = block.ter.kind;
 | 
			
		||||
            switch (tk.type) {
 | 
			
		||||
                case "error":
 | 
			
		||||
                    break;
 | 
			
		||||
                case "return":
 | 
			
		||||
                    break;
 | 
			
		||||
                case "jump":
 | 
			
		||||
                    this.addEdge(block.id, tk.target);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "if":
 | 
			
		||||
                    this.addEdge(block.id, tk.truthy);
 | 
			
		||||
                    this.addEdge(block.id, tk.falsy);
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const graph = new Map<BlockId, CfgNode>();
 | 
			
		||||
        for (const [block, index] of this.nodes) {
 | 
			
		||||
            const parents = this.edges
 | 
			
		||||
                .filter(([_from, to]) => to === block.id)
 | 
			
		||||
                .map(([from, _to]) => from);
 | 
			
		||||
 | 
			
		||||
            const children = this.edges
 | 
			
		||||
                .filter(([from, _to]) => from === block.id)
 | 
			
		||||
                .map(([_from, to]) => to);
 | 
			
		||||
 | 
			
		||||
            graph.set(block.id, { block, index, parents, children });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Cfg(graph, this.fn.entry, this.fn.exit);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private addNode(block: Block, index: number) {
 | 
			
		||||
        this.nodes.push([block, index]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private addEdge(from: BlockId, to: BlockId) {
 | 
			
		||||
        this.edges.push([from, to]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,59 +0,0 @@
 | 
			
		||||
import { createCfg } from "./cfg.ts";
 | 
			
		||||
import { Block, Mir } from "./mir.ts";
 | 
			
		||||
 | 
			
		||||
export function eliminateOnlyChildsBlocks(mir: Mir) {
 | 
			
		||||
    for (const fn of mir.fns) {
 | 
			
		||||
        const cfg = createCfg(fn);
 | 
			
		||||
 | 
			
		||||
        const candidates: { parent: Block; child: Block }[] = [];
 | 
			
		||||
 | 
			
		||||
        for (const block of fn.blocks) {
 | 
			
		||||
            const children = cfg.children(block);
 | 
			
		||||
            if (children.length !== 1) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (cfg.parents(children[0]).length !== 1) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            candidates.push({ parent: block, child: children[0] });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const elimIndices: number[] = [];
 | 
			
		||||
 | 
			
		||||
        for (const { parent, child } of candidates) {
 | 
			
		||||
            parent.ops.push(...child.ops);
 | 
			
		||||
            parent.ter = child.ter;
 | 
			
		||||
            elimIndices.push(cfg.index(child));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (const i of elimIndices.toReversed()) {
 | 
			
		||||
            fn.blocks.splice(i, 1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function eliminateUnreachableBlocks(mir: Mir) {
 | 
			
		||||
    for (const fn of mir.fns) {
 | 
			
		||||
        const cfg = createCfg(fn);
 | 
			
		||||
 | 
			
		||||
        const candidates: Block[] = [];
 | 
			
		||||
 | 
			
		||||
        for (const block of fn.blocks) {
 | 
			
		||||
            if (block.id === fn.entry) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (cfg.parents(block).length !== 0) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            candidates.push(block);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (
 | 
			
		||||
            const i of candidates
 | 
			
		||||
                .map((block) => cfg.index(block))
 | 
			
		||||
                .toReversed()
 | 
			
		||||
        ) {
 | 
			
		||||
            fn.blocks.splice(i, 1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,81 +0,0 @@
 | 
			
		||||
import { FnStmtKind } from "../ast.ts";
 | 
			
		||||
import { Reporter } from "../info.ts";
 | 
			
		||||
import {
 | 
			
		||||
    Block,
 | 
			
		||||
    Fn,
 | 
			
		||||
    LocalId,
 | 
			
		||||
    Mir,
 | 
			
		||||
    RValue,
 | 
			
		||||
    visitBlockDsts,
 | 
			
		||||
    visitBlockSrcs,
 | 
			
		||||
} from "./mir.ts";
 | 
			
		||||
 | 
			
		||||
export function eliminateUnusedLocals(
 | 
			
		||||
    mir: Mir,
 | 
			
		||||
    reporter: Reporter,
 | 
			
		||||
    isPassOne: boolean,
 | 
			
		||||
) {
 | 
			
		||||
    for (const fn of mir.fns) {
 | 
			
		||||
        new EliminateUnusedLocalsFnPass(fn, reporter, isPassOne).pass();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class EliminateUnusedLocalsFnPass {
 | 
			
		||||
    private locals: LocalId[];
 | 
			
		||||
 | 
			
		||||
    public constructor(
 | 
			
		||||
        private fn: Fn,
 | 
			
		||||
        private reporter: Reporter,
 | 
			
		||||
        private isPassOne: boolean,
 | 
			
		||||
    ) {
 | 
			
		||||
        this.locals = this.fn.locals
 | 
			
		||||
            .slice(1 + (fn.stmt.kind as FnStmtKind).params.length)
 | 
			
		||||
            .map((local) => local.id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public pass() {
 | 
			
		||||
        for (const block of this.fn.blocks) {
 | 
			
		||||
            this.markLocalsInBlock(block);
 | 
			
		||||
        }
 | 
			
		||||
        for (const local of this.locals) {
 | 
			
		||||
            for (const block of this.fn.blocks) {
 | 
			
		||||
                this.eliminateLocalInBlock(block, local);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        for (const id of this.locals) {
 | 
			
		||||
            const local = this.fn.locals.find((local) => local.id === id)!;
 | 
			
		||||
            if (local.sym?.type === "let" && this.isPassOne) {
 | 
			
		||||
                this.reporter.reportWarning({
 | 
			
		||||
                    reporter: "analysis mf'er",
 | 
			
		||||
                    msg: `unused let symbol '${local.sym.ident}'`,
 | 
			
		||||
                    pos: local.sym.pos,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        this.fn.locals = this.fn.locals
 | 
			
		||||
            .filter((local) => !this.locals.includes(local.id));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private eliminateLocalInBlock(block: Block, local: LocalId) {
 | 
			
		||||
        const elimIndices: number[] = [];
 | 
			
		||||
        visitBlockDsts(block, (dst, i) => {
 | 
			
		||||
            if (dst === local) {
 | 
			
		||||
                elimIndices.push(i);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        for (const i of elimIndices.toReversed()) {
 | 
			
		||||
            block.ops.splice(i, 1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private markLocalsInBlock(block: Block) {
 | 
			
		||||
        visitBlockSrcs(block, (src) => this.markUsed(src));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private markUsed(local: RValue) {
 | 
			
		||||
        if (local.type !== "copy" && local.type !== "move") {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this.locals = this.locals.filter((lid) => lid !== local.id);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,60 +0,0 @@
 | 
			
		||||
import { VType } from "../vtype.ts";
 | 
			
		||||
import { Fn, Local, Mir, replaceBlockSrcs, RValue } from "./mir.ts";
 | 
			
		||||
 | 
			
		||||
export function makeMoveCopyExplicit(mir: Mir) {
 | 
			
		||||
    for (const fn of mir.fns) {
 | 
			
		||||
        for (const local of fn.locals) {
 | 
			
		||||
            new LocalExpliciter(fn, local).pass();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class LocalExpliciter {
 | 
			
		||||
    private copyable: boolean;
 | 
			
		||||
 | 
			
		||||
    public constructor(private fn: Fn, private local: Local) {
 | 
			
		||||
        this.copyable = copyableIsType(this.local.vtype);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public pass() {
 | 
			
		||||
        for (const block of this.fn.blocks) {
 | 
			
		||||
            replaceBlockSrcs(block, (src) => this.explicitSrc(src));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private explicitSrc(src: RValue): RValue {
 | 
			
		||||
        if (src.type !== "move" || src.id !== this.local.id) {
 | 
			
		||||
            return src;
 | 
			
		||||
        }
 | 
			
		||||
        return this.copyable
 | 
			
		||||
            ? { type: "copy", id: src.id }
 | 
			
		||||
            : { type: "move", id: src.id };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function copyableIsType(vtype: VType): boolean {
 | 
			
		||||
    switch (vtype.type) {
 | 
			
		||||
        case "error":
 | 
			
		||||
        case "unknown":
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        case "null":
 | 
			
		||||
        case "int":
 | 
			
		||||
        case "bool":
 | 
			
		||||
        case "string":
 | 
			
		||||
            return true;
 | 
			
		||||
        case "ref":
 | 
			
		||||
        case "ref_mut":
 | 
			
		||||
            return false;
 | 
			
		||||
        case "ptr":
 | 
			
		||||
        case "ptr_mut":
 | 
			
		||||
            return true;
 | 
			
		||||
        case "array":
 | 
			
		||||
        case "struct":
 | 
			
		||||
        case "fn":
 | 
			
		||||
            return false;
 | 
			
		||||
        case "generic":
 | 
			
		||||
            return false;
 | 
			
		||||
        case "generic_spec":
 | 
			
		||||
            throw new Error();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,513 +0,0 @@
 | 
			
		||||
import * as Ast from "../ast.ts";
 | 
			
		||||
import { AllFnsCollector } from "../mono.ts";
 | 
			
		||||
import { VType, vtypesEqual } from "../vtype.ts";
 | 
			
		||||
import {
 | 
			
		||||
    Block,
 | 
			
		||||
    BlockId,
 | 
			
		||||
    Fn,
 | 
			
		||||
    Local,
 | 
			
		||||
    LocalId,
 | 
			
		||||
    Mir,
 | 
			
		||||
    OpKind,
 | 
			
		||||
    RValue,
 | 
			
		||||
    Ter,
 | 
			
		||||
    TerKind,
 | 
			
		||||
} from "./mir.ts";
 | 
			
		||||
 | 
			
		||||
export function lowerAst(ast: Ast.Stmt[]): Mir {
 | 
			
		||||
    return new AstLowerer(ast).lower();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class AstLowerer {
 | 
			
		||||
    public constructor(private ast: Ast.Stmt[]) {}
 | 
			
		||||
 | 
			
		||||
    public lower(): Mir {
 | 
			
		||||
        const fnAsts = new AllFnsCollector().collect(this.ast).values();
 | 
			
		||||
        const fns = fnAsts
 | 
			
		||||
            .map((fnAst) => new FnAstLowerer(fnAst).lower())
 | 
			
		||||
            .toArray();
 | 
			
		||||
        return { fns };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class LocalAllocator {
 | 
			
		||||
    private locals: Local[] = [];
 | 
			
		||||
 | 
			
		||||
    public alloc(vtype: VType, sym?: Ast.Sym): LocalId {
 | 
			
		||||
        const id = this.locals.length;
 | 
			
		||||
        this.locals.push({ id, mut: false, vtype, sym });
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public allocMut(vtype: VType, sym?: Ast.Sym): LocalId {
 | 
			
		||||
        const id = this.locals.length;
 | 
			
		||||
        this.locals.push({ id, mut: true, vtype, sym });
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public finish(): Local[] {
 | 
			
		||||
        return this.locals;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class FnAstLowerer {
 | 
			
		||||
    private locals = new LocalAllocator();
 | 
			
		||||
    private blockIdCounter = 0;
 | 
			
		||||
    private currentBlockId = 0;
 | 
			
		||||
    private blocks = new Map<BlockId, Block>();
 | 
			
		||||
 | 
			
		||||
    private fnParamIndexLocals = new Map<number, LocalId>();
 | 
			
		||||
    private letStmtIdLocals = new Map<number, LocalId>();
 | 
			
		||||
 | 
			
		||||
    private breakStack: { local: LocalId; block: BlockId }[] = [];
 | 
			
		||||
 | 
			
		||||
    public constructor(private ast: Ast.Stmt) {}
 | 
			
		||||
 | 
			
		||||
    public lower(): Fn {
 | 
			
		||||
        const stmt = this.ast;
 | 
			
		||||
        if (stmt.kind.type !== "fn") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const vtype = stmt.kind.vtype;
 | 
			
		||||
        if (vtype?.type !== "fn") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const rLoc = this.locals.alloc(vtype.returnType);
 | 
			
		||||
        for (const param of stmt.kind.params) {
 | 
			
		||||
            const id = this.locals.allocMut(param.vtype!);
 | 
			
		||||
            this.fnParamIndexLocals.set(param.index!, id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const entry = this.pushBlock();
 | 
			
		||||
        const rVal = this.lowerBlockExpr(stmt.kind.body);
 | 
			
		||||
        this.addOp({ type: "assign", dst: rLoc, src: local(rVal) });
 | 
			
		||||
        this.setTer({ type: "return" });
 | 
			
		||||
        const exit = this.currentBlock();
 | 
			
		||||
 | 
			
		||||
        const locals = this.locals.finish();
 | 
			
		||||
        const blocks = this.blocks.values().toArray();
 | 
			
		||||
        return { stmt, locals, blocks, entry, exit };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerStmt(stmt: Ast.Stmt) {
 | 
			
		||||
        switch (stmt.kind.type) {
 | 
			
		||||
            case "error":
 | 
			
		||||
            case "mod_block":
 | 
			
		||||
            case "mod_file":
 | 
			
		||||
            case "mod":
 | 
			
		||||
                break;
 | 
			
		||||
            case "break": {
 | 
			
		||||
                const { local: dst, block } = this.breakStack.at(-1)!;
 | 
			
		||||
                if (stmt.kind.expr) {
 | 
			
		||||
                    const val = this.lowerExpr(stmt.kind.expr);
 | 
			
		||||
                    this.addOp({ type: "assign", dst, src: local(val) });
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.addOp({ type: "assign", dst, src: { type: "null" } });
 | 
			
		||||
                }
 | 
			
		||||
                this.setTer({ type: "jump", target: block });
 | 
			
		||||
                this.pushBlock();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            case "return":
 | 
			
		||||
                break;
 | 
			
		||||
            case "fn":
 | 
			
		||||
                // nothing
 | 
			
		||||
                return;
 | 
			
		||||
            case "let":
 | 
			
		||||
                this.lowerLetStmt(stmt);
 | 
			
		||||
                return;
 | 
			
		||||
            case "type_alias":
 | 
			
		||||
                break;
 | 
			
		||||
            case "assign":
 | 
			
		||||
                return this.lowerAssign(stmt);
 | 
			
		||||
            case "expr": {
 | 
			
		||||
                this.lowerExpr(stmt.kind.expr);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        throw new Error(`statement type '${stmt.kind.type}' not covered`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerAssign(stmt: Ast.Stmt) {
 | 
			
		||||
        if (stmt.kind.type !== "assign") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        if (stmt.kind.assignType !== "=") {
 | 
			
		||||
            throw new Error("incomplete desugar");
 | 
			
		||||
        }
 | 
			
		||||
        const src = local(this.lowerExpr(stmt.kind.value));
 | 
			
		||||
        const s = stmt.kind.subject;
 | 
			
		||||
        switch (s.kind.type) {
 | 
			
		||||
            case "field": {
 | 
			
		||||
                const subject = local(this.lowerExpr(s.kind.subject));
 | 
			
		||||
                const ident = s.kind.ident;
 | 
			
		||||
                this.addOp({ type: "assign_field", subject, ident, src });
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            case "index": {
 | 
			
		||||
                const subject = local(this.lowerExpr(s.kind.subject));
 | 
			
		||||
                const index = local(this.lowerExpr(s.kind.value));
 | 
			
		||||
                this.addOp({ type: "assign_index", subject, index, src });
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            case "sym": {
 | 
			
		||||
                const sym = s.kind.sym;
 | 
			
		||||
                switch (sym.type) {
 | 
			
		||||
                    case "let": {
 | 
			
		||||
                        const dst = this.letStmtIdLocals.get(sym.stmt.id)!;
 | 
			
		||||
                        this.addOp({ type: "assign", dst, src });
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    case "fn_param": {
 | 
			
		||||
                        const dst = this.fnParamIndexLocals.get(
 | 
			
		||||
                            sym.param.index!,
 | 
			
		||||
                        )!;
 | 
			
		||||
                        this.addOp({ type: "assign", dst, src });
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                throw new Error(`symbol type '${sym.type}' not covered`);
 | 
			
		||||
            }
 | 
			
		||||
            default:
 | 
			
		||||
                throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerLetStmt(stmt: Ast.Stmt) {
 | 
			
		||||
        if (stmt.kind.type !== "let") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const srcId = this.lowerExpr(stmt.kind.value);
 | 
			
		||||
        const dst = this.locals.allocMut(
 | 
			
		||||
            stmt.kind.param.vtype!,
 | 
			
		||||
            stmt.kind.param.sym!,
 | 
			
		||||
        );
 | 
			
		||||
        this.addOp({ type: "assign", dst, src: local(srcId) });
 | 
			
		||||
        this.letStmtIdLocals.set(stmt.id, dst);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        switch (expr.kind.type) {
 | 
			
		||||
            case "error": {
 | 
			
		||||
                const dst = this.locals.alloc({ type: "error" });
 | 
			
		||||
                this.addOp({ type: "assign", dst, src: { type: "error" } });
 | 
			
		||||
                return dst;
 | 
			
		||||
            }
 | 
			
		||||
            case "null": {
 | 
			
		||||
                const dst = this.locals.alloc({ type: "null" });
 | 
			
		||||
                this.addOp({ type: "assign", dst, src: { type: "null" } });
 | 
			
		||||
                return dst;
 | 
			
		||||
            }
 | 
			
		||||
            case "bool": {
 | 
			
		||||
                const val = expr.kind.value;
 | 
			
		||||
                const dst = this.locals.alloc({ type: "bool" });
 | 
			
		||||
                this.addOp({ type: "assign", dst, src: { type: "bool", val } });
 | 
			
		||||
                return dst;
 | 
			
		||||
            }
 | 
			
		||||
            case "int": {
 | 
			
		||||
                const val = expr.kind.value;
 | 
			
		||||
                const dst = this.locals.alloc({ type: "int" });
 | 
			
		||||
                this.addOp({ type: "assign", dst, src: { type: "int", val } });
 | 
			
		||||
                return dst;
 | 
			
		||||
            }
 | 
			
		||||
            case "string": {
 | 
			
		||||
                const val = expr.kind.value;
 | 
			
		||||
                const dst = this.locals.alloc({ type: "string" });
 | 
			
		||||
                this.addOp({
 | 
			
		||||
                    type: "assign",
 | 
			
		||||
                    dst,
 | 
			
		||||
                    src: { type: "string", val },
 | 
			
		||||
                });
 | 
			
		||||
                return dst;
 | 
			
		||||
            }
 | 
			
		||||
            case "ident":
 | 
			
		||||
                throw new Error("should've been resolved");
 | 
			
		||||
            case "sym":
 | 
			
		||||
                return this.lowerSymExpr(expr);
 | 
			
		||||
            case "group":
 | 
			
		||||
                return this.lowerExpr(expr.kind.expr);
 | 
			
		||||
            case "ref": {
 | 
			
		||||
                const src = this.lowerExpr(expr.kind.subject);
 | 
			
		||||
                const dst = this.locals.alloc(expr.vtype!);
 | 
			
		||||
                this.addOp({ type: "ref", dst, src });
 | 
			
		||||
                return dst;
 | 
			
		||||
            }
 | 
			
		||||
            case "ref_mut": {
 | 
			
		||||
                const src = this.lowerExpr(expr.kind.subject);
 | 
			
		||||
                const dst = this.locals.alloc(expr.vtype!);
 | 
			
		||||
                this.addOp({ type: "ref_mut", dst, src });
 | 
			
		||||
                return dst;
 | 
			
		||||
            }
 | 
			
		||||
            case "deref": {
 | 
			
		||||
                const src = local(this.lowerExpr(expr.kind.subject));
 | 
			
		||||
                const dst = this.locals.alloc(expr.kind.subject.vtype!);
 | 
			
		||||
                this.addOp({ type: "deref", dst, src });
 | 
			
		||||
                return dst;
 | 
			
		||||
            }
 | 
			
		||||
            case "array":
 | 
			
		||||
                throw new Error("incomplete desugar");
 | 
			
		||||
            case "struct":
 | 
			
		||||
                throw new Error("incomplete desugar");
 | 
			
		||||
            case "field":
 | 
			
		||||
                return this.lowerFieldExpr(expr);
 | 
			
		||||
            case "index":
 | 
			
		||||
                return this.lowerIndexExpr(expr);
 | 
			
		||||
            case "call":
 | 
			
		||||
                return this.lowerCallExpr(expr);
 | 
			
		||||
            case "path":
 | 
			
		||||
            case "etype_args":
 | 
			
		||||
            case "unary":
 | 
			
		||||
                break;
 | 
			
		||||
            case "binary":
 | 
			
		||||
                return this.lowerBinaryExpr(expr);
 | 
			
		||||
            case "if":
 | 
			
		||||
                return this.lowerIfExpr(expr);
 | 
			
		||||
            case "loop":
 | 
			
		||||
                return this.lowerLoopExpr(expr);
 | 
			
		||||
            case "block":
 | 
			
		||||
                return this.lowerBlockExpr(expr);
 | 
			
		||||
            case "while":
 | 
			
		||||
            case "for_in":
 | 
			
		||||
            case "for":
 | 
			
		||||
                throw new Error("incomplete desugar");
 | 
			
		||||
        }
 | 
			
		||||
        throw new Error(`expression type '${expr.kind.type}' not covered`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerSymExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        if (expr.kind.type !== "sym") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const sym = expr.kind.sym;
 | 
			
		||||
        switch (sym.type) {
 | 
			
		||||
            case "let":
 | 
			
		||||
                return this.letStmtIdLocals.get(sym.stmt.id)!;
 | 
			
		||||
            case "let_static":
 | 
			
		||||
            case "type_alias":
 | 
			
		||||
                break;
 | 
			
		||||
            case "fn": {
 | 
			
		||||
                const stmt = sym.stmt;
 | 
			
		||||
                if (sym.stmt.kind.type !== "fn") {
 | 
			
		||||
                    throw new Error();
 | 
			
		||||
                }
 | 
			
		||||
                const dst = this.locals.alloc(sym.stmt.kind.vtype!);
 | 
			
		||||
                this.addOp({ type: "assign", dst, src: { type: "fn", stmt } });
 | 
			
		||||
                return dst;
 | 
			
		||||
            }
 | 
			
		||||
            case "fn_param": {
 | 
			
		||||
                return this.fnParamIndexLocals.get(sym.param.index!)!;
 | 
			
		||||
            }
 | 
			
		||||
            case "closure":
 | 
			
		||||
            case "generic":
 | 
			
		||||
            case "mod":
 | 
			
		||||
        }
 | 
			
		||||
        throw new Error(`symbol type '${sym.type}' not covered`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerFieldExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        if (expr.kind.type !== "field") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const ident = expr.kind.ident;
 | 
			
		||||
        const subject = local(this.lowerExpr(expr.kind.subject));
 | 
			
		||||
 | 
			
		||||
        const subjectVType = expr.kind.subject.vtype!;
 | 
			
		||||
        if (subjectVType.type !== "struct") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const fieldVType = subjectVType.fields.find((field) =>
 | 
			
		||||
            field.ident === ident
 | 
			
		||||
        );
 | 
			
		||||
        if (fieldVType === undefined) {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const dst = this.locals.alloc(fieldVType.vtype);
 | 
			
		||||
        this.addOp({ type: "field", dst, subject, ident });
 | 
			
		||||
        return dst;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerIndexExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        if (expr.kind.type !== "index") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const subject = local(this.lowerExpr(expr.kind.subject));
 | 
			
		||||
        const index = local(this.lowerExpr(expr.kind.value));
 | 
			
		||||
 | 
			
		||||
        const dstVType = ((): VType => {
 | 
			
		||||
            const outer = expr.kind.subject.vtype!;
 | 
			
		||||
            if (outer.type === "array") {
 | 
			
		||||
                return outer.subject;
 | 
			
		||||
            }
 | 
			
		||||
            if (outer.type === "string") {
 | 
			
		||||
                return { type: "int" };
 | 
			
		||||
            }
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        })();
 | 
			
		||||
 | 
			
		||||
        const dst = this.locals.alloc(dstVType);
 | 
			
		||||
        this.addOp({ type: "index", dst, subject, index });
 | 
			
		||||
        return dst;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerCallExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        if (expr.kind.type !== "call") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const args = expr.kind.args.map((arg) => local(this.lowerExpr(arg)));
 | 
			
		||||
 | 
			
		||||
        const subject = local(this.lowerExpr(expr.kind.subject));
 | 
			
		||||
 | 
			
		||||
        const subjectVType = expr.kind.subject.vtype!;
 | 
			
		||||
        if (subjectVType.type !== "fn") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const dst = this.locals.alloc(subjectVType.returnType);
 | 
			
		||||
        this.addOp({ type: "call_val", dst, subject, args });
 | 
			
		||||
        return dst;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerBinaryExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        if (expr.kind.type !== "binary") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const leftVType = expr.kind.left.vtype!;
 | 
			
		||||
        const rightVType = expr.kind.right.vtype!;
 | 
			
		||||
        if (!vtypesEqual(leftVType, rightVType)) {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        //const vtype = leftVType.type === "error" && rightVType || leftVType;
 | 
			
		||||
 | 
			
		||||
        const binaryType = expr.kind.binaryType;
 | 
			
		||||
        const left = local(this.lowerExpr(expr.kind.left));
 | 
			
		||||
        const right = local(this.lowerExpr(expr.kind.right));
 | 
			
		||||
 | 
			
		||||
        const dst = this.locals.alloc(expr.vtype!);
 | 
			
		||||
 | 
			
		||||
        this.addOp({ type: "binary", binaryType, dst, left, right });
 | 
			
		||||
        return dst;
 | 
			
		||||
 | 
			
		||||
        //throw new Error(
 | 
			
		||||
        //    `binary vtype '${vtypeToString(leftVType)}' not covered`,
 | 
			
		||||
        //);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerIfExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        if (expr.kind.type !== "if") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const condBlock = this.currentBlock();
 | 
			
		||||
        const cond = local(this.lowerExpr(expr.kind.cond));
 | 
			
		||||
        const end = this.reserveBlock();
 | 
			
		||||
 | 
			
		||||
        const val = this.locals.alloc(expr.vtype!);
 | 
			
		||||
 | 
			
		||||
        const truthy = this.pushBlock();
 | 
			
		||||
        const truthyVal = local(this.lowerExpr(expr.kind.truthy));
 | 
			
		||||
        this.addOp({ type: "assign", dst: val, src: truthyVal });
 | 
			
		||||
        this.setTer({ type: "jump", target: end });
 | 
			
		||||
 | 
			
		||||
        if (expr.kind.falsy) {
 | 
			
		||||
            const falsy = this.pushBlock();
 | 
			
		||||
            const falsyVal = local(this.lowerExpr(expr.kind.falsy));
 | 
			
		||||
            this.addOp({ type: "assign", dst: val, src: falsyVal });
 | 
			
		||||
            this.setTer({ type: "jump", target: end });
 | 
			
		||||
 | 
			
		||||
            this.setTerOn(condBlock, { type: "if", cond, truthy, falsy });
 | 
			
		||||
        } else {
 | 
			
		||||
            this.setTerOn(condBlock, { type: "if", cond, truthy, falsy: end });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.pushBlockWithId(end);
 | 
			
		||||
 | 
			
		||||
        return val;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerLoopExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        if (expr.kind.type !== "loop") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const val = this.locals.alloc(expr.vtype!);
 | 
			
		||||
        const breakBlock = this.reserveBlock();
 | 
			
		||||
        this.breakStack.push({ local: val, block: breakBlock });
 | 
			
		||||
 | 
			
		||||
        const before = this.currentBlock();
 | 
			
		||||
        const body = this.pushBlock();
 | 
			
		||||
        this.setTerOn(before, { type: "jump", target: body });
 | 
			
		||||
 | 
			
		||||
        this.lowerExpr(expr.kind.body);
 | 
			
		||||
        this.setTer({ type: "jump", target: body });
 | 
			
		||||
 | 
			
		||||
        this.breakStack.pop();
 | 
			
		||||
 | 
			
		||||
        this.pushBlockWithId(breakBlock);
 | 
			
		||||
        return val;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerBlockExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        if (expr.kind.type !== "block") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (const stmt of expr.kind.stmts) {
 | 
			
		||||
            this.lowerStmt(stmt);
 | 
			
		||||
        }
 | 
			
		||||
        if (expr.kind.expr) {
 | 
			
		||||
            return this.lowerExpr(expr.kind.expr);
 | 
			
		||||
        } else {
 | 
			
		||||
            const local = this.locals.alloc({ type: "null" });
 | 
			
		||||
            this.addOp({ type: "assign", dst: local, src: { type: "null" } });
 | 
			
		||||
            return local;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private addOp(kind: OpKind) {
 | 
			
		||||
        this.blocks.get(this.currentBlockId)!.ops.push({ kind });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private addOpOn(blockId: BlockId, kind: OpKind) {
 | 
			
		||||
        this.blocks.get(blockId)!.ops.push({ kind });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private setTer(kind: TerKind) {
 | 
			
		||||
        this.blocks.get(this.currentBlockId)!.ter = { kind };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private setTerOn(blockId: BlockId, kind: TerKind) {
 | 
			
		||||
        this.blocks.get(blockId)!.ter = { kind };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private currentBlock(): BlockId {
 | 
			
		||||
        return this.currentBlockId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private reserveBlock(): BlockId {
 | 
			
		||||
        const id = this.blockIdCounter;
 | 
			
		||||
        this.blockIdCounter += 1;
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private pushBlock(label?: string): BlockId {
 | 
			
		||||
        const id = this.blockIdCounter;
 | 
			
		||||
        this.blockIdCounter += 1;
 | 
			
		||||
        const ter: Ter = { kind: { type: "error" } };
 | 
			
		||||
        this.blocks.set(id, { id, ops: [], ter, label });
 | 
			
		||||
        this.currentBlockId = id;
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private pushBlockWithId(id: BlockId): BlockId {
 | 
			
		||||
        const ter: Ter = { kind: { type: "error" } };
 | 
			
		||||
        this.blocks.set(id, { id, ops: [], ter });
 | 
			
		||||
        this.currentBlockId = id;
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function local(id: LocalId): RValue {
 | 
			
		||||
    return { type: "move", id };
 | 
			
		||||
}
 | 
			
		||||
@ -1,388 +0,0 @@
 | 
			
		||||
import { BinaryType, Stmt, Sym } from "../ast.ts";
 | 
			
		||||
import { VType, vtypeToString } from "../vtype.ts";
 | 
			
		||||
 | 
			
		||||
export type Mir = {
 | 
			
		||||
    fns: Fn[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Fn = {
 | 
			
		||||
    stmt: Stmt;
 | 
			
		||||
    locals: Local[];
 | 
			
		||||
    blocks: Block[];
 | 
			
		||||
    entry: BlockId;
 | 
			
		||||
    exit: BlockId;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type LocalId = number;
 | 
			
		||||
 | 
			
		||||
export type Local = {
 | 
			
		||||
    id: LocalId;
 | 
			
		||||
    mut: boolean;
 | 
			
		||||
    vtype: VType;
 | 
			
		||||
    sym?: Sym;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type BlockId = number;
 | 
			
		||||
 | 
			
		||||
export type Block = {
 | 
			
		||||
    id: BlockId;
 | 
			
		||||
    ops: Op[];
 | 
			
		||||
    ter: Ter;
 | 
			
		||||
    label?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Op = {
 | 
			
		||||
    kind: OpKind;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type L = LocalId;
 | 
			
		||||
type R = RValue;
 | 
			
		||||
 | 
			
		||||
export type OpKind =
 | 
			
		||||
    | { type: "error" }
 | 
			
		||||
    | { type: "assign"; dst: L; src: R }
 | 
			
		||||
    | { type: "ref"; dst: L; src: L }
 | 
			
		||||
    | { type: "ref_mut"; dst: L; src: L }
 | 
			
		||||
    | { type: "ptr"; dst: L; src: L }
 | 
			
		||||
    | { type: "ptr_mut"; dst: L; src: L }
 | 
			
		||||
    | { type: "deref"; dst: L; src: R }
 | 
			
		||||
    | { type: "assign_deref"; subject: R; src: R }
 | 
			
		||||
    | { type: "field"; dst: L; subject: R; ident: string }
 | 
			
		||||
    | { type: "assign_field"; subject: R; ident: string; src: R }
 | 
			
		||||
    | { type: "index"; dst: L; subject: R; index: R }
 | 
			
		||||
    | { type: "assign_index"; subject: R; index: R; src: R }
 | 
			
		||||
    | { type: "call_val"; dst: L; subject: R; args: R[] }
 | 
			
		||||
    | { type: "binary"; binaryType: BinaryType; dst: L; left: R; right: R };
 | 
			
		||||
 | 
			
		||||
export type Ter = {
 | 
			
		||||
    kind: TerKind;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type TerKind =
 | 
			
		||||
    | { type: "error" }
 | 
			
		||||
    | { type: "return" }
 | 
			
		||||
    | { type: "jump"; target: BlockId }
 | 
			
		||||
    | { type: "if"; cond: R; truthy: BlockId; falsy: BlockId };
 | 
			
		||||
 | 
			
		||||
export type RValue =
 | 
			
		||||
    | { type: "error" }
 | 
			
		||||
    | { type: "copy"; id: BlockId }
 | 
			
		||||
    | { type: "move"; id: BlockId }
 | 
			
		||||
    | { type: "null" }
 | 
			
		||||
    | { type: "bool"; val: boolean }
 | 
			
		||||
    | { type: "int"; val: number }
 | 
			
		||||
    | { type: "string"; val: string }
 | 
			
		||||
    | { type: "fn"; stmt: Stmt };
 | 
			
		||||
 | 
			
		||||
export function visitBlockDsts(
 | 
			
		||||
    block: Block,
 | 
			
		||||
    visit: (local: LocalId, index: number) => void,
 | 
			
		||||
) {
 | 
			
		||||
    for (const [op, i] of block.ops.map((v, i) => [v, i] as const)) {
 | 
			
		||||
        const ok = op.kind;
 | 
			
		||||
        switch (ok.type) {
 | 
			
		||||
            case "error":
 | 
			
		||||
                break;
 | 
			
		||||
            case "assign":
 | 
			
		||||
            case "ref":
 | 
			
		||||
            case "ref_mut":
 | 
			
		||||
            case "ptr":
 | 
			
		||||
            case "ptr_mut":
 | 
			
		||||
            case "deref":
 | 
			
		||||
            case "field":
 | 
			
		||||
            case "index":
 | 
			
		||||
            case "call_val":
 | 
			
		||||
            case "binary":
 | 
			
		||||
                visit(ok.dst, i);
 | 
			
		||||
                break;
 | 
			
		||||
            case "assign_deref":
 | 
			
		||||
            case "assign_field":
 | 
			
		||||
            case "assign_index":
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function replaceBlockSrcs(
 | 
			
		||||
    block: Block,
 | 
			
		||||
    replace: (src: RValue) => RValue,
 | 
			
		||||
) {
 | 
			
		||||
    for (const op of block.ops) {
 | 
			
		||||
        const ok = op.kind;
 | 
			
		||||
        switch (ok.type) {
 | 
			
		||||
            case "error":
 | 
			
		||||
                break;
 | 
			
		||||
            case "assign":
 | 
			
		||||
                ok.src = replace(ok.src);
 | 
			
		||||
                break;
 | 
			
		||||
            case "ref":
 | 
			
		||||
            case "ref_mut":
 | 
			
		||||
            case "ptr":
 | 
			
		||||
            case "ptr_mut":
 | 
			
		||||
                break;
 | 
			
		||||
            case "deref":
 | 
			
		||||
                ok.src = replace(ok.src);
 | 
			
		||||
                break;
 | 
			
		||||
            case "assign_deref":
 | 
			
		||||
                ok.subject = replace(ok.subject);
 | 
			
		||||
                ok.src = replace(ok.src);
 | 
			
		||||
                break;
 | 
			
		||||
            case "field":
 | 
			
		||||
                ok.subject = replace(ok.subject);
 | 
			
		||||
                break;
 | 
			
		||||
            case "assign_field":
 | 
			
		||||
                ok.subject = replace(ok.subject);
 | 
			
		||||
                ok.src = replace(ok.src);
 | 
			
		||||
                break;
 | 
			
		||||
            case "index":
 | 
			
		||||
                ok.subject = replace(ok.subject);
 | 
			
		||||
                ok.index = replace(ok.index);
 | 
			
		||||
                break;
 | 
			
		||||
            case "assign_index":
 | 
			
		||||
                ok.subject = replace(ok.subject);
 | 
			
		||||
                ok.index = replace(ok.index);
 | 
			
		||||
                ok.src = replace(ok.src);
 | 
			
		||||
                break;
 | 
			
		||||
            case "call_val":
 | 
			
		||||
                ok.subject = replace(ok.subject);
 | 
			
		||||
                ok.args = ok.args.map((arg) => replace(arg));
 | 
			
		||||
                break;
 | 
			
		||||
            case "binary":
 | 
			
		||||
                ok.left = replace(ok.left);
 | 
			
		||||
                ok.right = replace(ok.right);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    const tk = block.ter.kind;
 | 
			
		||||
    switch (tk.type) {
 | 
			
		||||
        case "error":
 | 
			
		||||
            break;
 | 
			
		||||
        case "return":
 | 
			
		||||
            break;
 | 
			
		||||
        case "jump":
 | 
			
		||||
            break;
 | 
			
		||||
        case "if":
 | 
			
		||||
            tk.cond = replace(tk.cond);
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function visitBlockSrcs(
 | 
			
		||||
    block: Block,
 | 
			
		||||
    visitor: (src: RValue, op?: Op, index?: number, ter?: Ter) => void,
 | 
			
		||||
) {
 | 
			
		||||
    for (const [op, i] of block.ops.map((v, i) => [v, i] as const)) {
 | 
			
		||||
        const ok = op.kind;
 | 
			
		||||
        switch (ok.type) {
 | 
			
		||||
            case "error":
 | 
			
		||||
                break;
 | 
			
		||||
            case "assign":
 | 
			
		||||
                visitor(ok.src, op, i);
 | 
			
		||||
                break;
 | 
			
		||||
            case "ref":
 | 
			
		||||
            case "ref_mut":
 | 
			
		||||
            case "ptr":
 | 
			
		||||
            case "ptr_mut":
 | 
			
		||||
                break;
 | 
			
		||||
            case "deref":
 | 
			
		||||
                visitor(ok.src, op, i);
 | 
			
		||||
                break;
 | 
			
		||||
            case "assign_deref":
 | 
			
		||||
                visitor(ok.src, op, i);
 | 
			
		||||
                visitor(ok.subject, op, i);
 | 
			
		||||
                break;
 | 
			
		||||
            case "field":
 | 
			
		||||
                visitor(ok.subject, op, i);
 | 
			
		||||
                break;
 | 
			
		||||
            case "assign_field":
 | 
			
		||||
                visitor(ok.subject, op, i);
 | 
			
		||||
                visitor(ok.src, op, i);
 | 
			
		||||
                break;
 | 
			
		||||
            case "index":
 | 
			
		||||
                visitor(ok.subject, op, i);
 | 
			
		||||
                visitor(ok.index, op, i);
 | 
			
		||||
                break;
 | 
			
		||||
            case "assign_index":
 | 
			
		||||
                visitor(ok.subject, op, i);
 | 
			
		||||
                visitor(ok.index, op, i);
 | 
			
		||||
                visitor(ok.src, op, i);
 | 
			
		||||
                break;
 | 
			
		||||
            case "call_val":
 | 
			
		||||
                visitor(ok.subject, op, i);
 | 
			
		||||
                ok.args.map((arg) => visitor(arg, op, i));
 | 
			
		||||
                break;
 | 
			
		||||
            case "binary":
 | 
			
		||||
                visitor(ok.left, op, i);
 | 
			
		||||
                visitor(ok.right, op, i);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    const tk = block.ter.kind;
 | 
			
		||||
    switch (tk.type) {
 | 
			
		||||
        case "error":
 | 
			
		||||
            break;
 | 
			
		||||
        case "return":
 | 
			
		||||
            break;
 | 
			
		||||
        case "jump":
 | 
			
		||||
            break;
 | 
			
		||||
        case "if":
 | 
			
		||||
            visitor(tk.cond, undefined, undefined, block.ter);
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function mirOpCount(mir: Mir): number {
 | 
			
		||||
    return mir.fns
 | 
			
		||||
        .reduce((acc, fn) =>
 | 
			
		||||
            acc + fn.blocks
 | 
			
		||||
                .reduce((acc, block) => acc + block.ops.length + 1, 0), 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function printMir(mir: Mir) {
 | 
			
		||||
    for (const fn of mir.fns) {
 | 
			
		||||
        const stmt = fn.stmt;
 | 
			
		||||
        if (stmt.kind.type !== "fn") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const name = stmt.kind.sym!.fullPath;
 | 
			
		||||
 | 
			
		||||
        const vtype = stmt.kind.vtype;
 | 
			
		||||
        if (vtype?.type !== "fn") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const generics = vtype.genericParams
 | 
			
		||||
            ?.map(({ ident }) => `${ident}`).join(", ") ?? "";
 | 
			
		||||
        const params = vtype.params
 | 
			
		||||
            .map(({ mut, vtype }, i) =>
 | 
			
		||||
                `${mut && "mut" || ""} _${fn.locals[i + 1].id}: ${
 | 
			
		||||
                    vtypeToString(vtype)
 | 
			
		||||
                }`
 | 
			
		||||
            )
 | 
			
		||||
            .join(", ");
 | 
			
		||||
        const returnType = vtypeToString(vtype.returnType);
 | 
			
		||||
        console.log(`${name}${generics}(${params}) -> ${returnType} {`);
 | 
			
		||||
 | 
			
		||||
        const paramIndices = vtype.params.map((_v, i) => i + 1);
 | 
			
		||||
        for (
 | 
			
		||||
            const { id, vtype, mut } of fn.locals
 | 
			
		||||
                .filter((_v, i) => !paramIndices.includes(i))
 | 
			
		||||
        ) {
 | 
			
		||||
            const m = mut ? "mut" : "";
 | 
			
		||||
            const v = vtypeToString(vtype);
 | 
			
		||||
            console.log(`    let ${m} _${id}: ${v};`);
 | 
			
		||||
        }
 | 
			
		||||
        for (const block of fn.blocks) {
 | 
			
		||||
            const l = (msg: string) => console.log(`        ${msg}`);
 | 
			
		||||
            const r = rvalueToString;
 | 
			
		||||
 | 
			
		||||
            console.log(`    ${block.label ?? "bb" + block.id}: {`);
 | 
			
		||||
            for (const op of block.ops) {
 | 
			
		||||
                const k = op.kind;
 | 
			
		||||
                switch (k.type) {
 | 
			
		||||
                    case "error":
 | 
			
		||||
                        l(`<error>;`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "assign":
 | 
			
		||||
                        l(`_${k.dst} = ${r(k.src)};`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "ref":
 | 
			
		||||
                        l(`_${k.dst} = &_${k.src};`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "ref_mut":
 | 
			
		||||
                        l(`_${k.dst} = &mut _${k.src};`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "ptr":
 | 
			
		||||
                        l(`_${k.dst} = *_${k.src};`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "ptr_mut":
 | 
			
		||||
                        l(`_${k.dst} = *mut _${k.src};`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "deref":
 | 
			
		||||
                        l(`_${k.dst} = *${r(k.src)};`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "assign_deref":
 | 
			
		||||
                        l(`*${r(k.subject)} = ${r(k.src)};`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "field":
 | 
			
		||||
                        l(`_${k.dst} = ${r(k.subject)}.${k.ident};`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "assign_field":
 | 
			
		||||
                        l(`${r(k.subject)}.${k.ident} = ${r(k.src)};`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "index":
 | 
			
		||||
                        l(`_${k.dst} = ${r(k.subject)}[${r(k.index)}];`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "assign_index":
 | 
			
		||||
                        l(`${r(k.subject)}[${r(k.index)}] = ${r(k.src)};`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "call_val": {
 | 
			
		||||
                        const args = k.args.map((arg) => r(arg)).join(", ");
 | 
			
		||||
                        l(`_${k.dst} = call ${r(k.subject)}(${args});`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    case "binary": {
 | 
			
		||||
                        l(`_${k.dst} = ${r(k.left)} ${k.binaryType} ${
 | 
			
		||||
                            r(k.right)
 | 
			
		||||
                        };`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    default:
 | 
			
		||||
                        throw new Error();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            const tk = block.ter.kind;
 | 
			
		||||
            switch (tk.type) {
 | 
			
		||||
                case "error":
 | 
			
		||||
                    l(`<error>;`);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "return":
 | 
			
		||||
                    l(`return;`);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "jump":
 | 
			
		||||
                    l(`jump bb${tk.target};`);
 | 
			
		||||
                    break;
 | 
			
		||||
                case "if":
 | 
			
		||||
                    l(`if ${
 | 
			
		||||
                        r(tk.cond)
 | 
			
		||||
                    }, true: bb${tk.truthy}, false: bb${tk.falsy};`);
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    throw new Error();
 | 
			
		||||
            }
 | 
			
		||||
            console.log("    }");
 | 
			
		||||
        }
 | 
			
		||||
        console.log("}");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function rvalueToString(rvalue: RValue): string {
 | 
			
		||||
    switch (rvalue.type) {
 | 
			
		||||
        case "error":
 | 
			
		||||
            return `<error>`;
 | 
			
		||||
        case "copy":
 | 
			
		||||
            return `copy _${rvalue.id}`;
 | 
			
		||||
        case "move":
 | 
			
		||||
            return `move _${rvalue.id}`;
 | 
			
		||||
        case "null":
 | 
			
		||||
            return "null";
 | 
			
		||||
        case "bool":
 | 
			
		||||
            return `${rvalue.val}`;
 | 
			
		||||
        case "int":
 | 
			
		||||
            return `${rvalue.val}`;
 | 
			
		||||
        case "string":
 | 
			
		||||
            return `"${rvalue.val}"`;
 | 
			
		||||
        case "fn": {
 | 
			
		||||
            const stmt = rvalue.stmt;
 | 
			
		||||
            if (stmt.kind.type !== "fn") {
 | 
			
		||||
                throw new Error();
 | 
			
		||||
            }
 | 
			
		||||
            return stmt.kind.sym!.fullPath;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,315 +0,0 @@
 | 
			
		||||
import { Expr, Stmt } from "./ast.ts";
 | 
			
		||||
import { AstVisitor, visitExpr, VisitRes, visitStmts } from "./ast_visitor.ts";
 | 
			
		||||
import { GenericArgsMap, VType } from "./vtype.ts";
 | 
			
		||||
 | 
			
		||||
export class Monomorphizer {
 | 
			
		||||
    private fnIdCounter = 0;
 | 
			
		||||
    private fns: MonoFnsMap = {};
 | 
			
		||||
    private callMap: MonoCallNameGenMap = {};
 | 
			
		||||
    private allFns: Map<number, Stmt>;
 | 
			
		||||
    private entryFn: Stmt;
 | 
			
		||||
 | 
			
		||||
    constructor(private ast: Stmt[]) {
 | 
			
		||||
        this.allFns = new AllFnsCollector().collect(this.ast);
 | 
			
		||||
        this.entryFn = findMain(this.allFns);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public monomorphize(): MonoResult {
 | 
			
		||||
        this.monomorphizeFn(this.entryFn);
 | 
			
		||||
        return { monoFns: this.fns, callMap: this.callMap };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private monomorphizeFn(
 | 
			
		||||
        stmt: Stmt,
 | 
			
		||||
        genericArgs?: GenericArgsMap,
 | 
			
		||||
    ): MonoFn {
 | 
			
		||||
        const id = this.fnIdCounter;
 | 
			
		||||
        this.fnIdCounter += 1;
 | 
			
		||||
        const nameGen = monoFnNameGen(id, stmt, genericArgs);
 | 
			
		||||
        if (nameGen in this.fns) {
 | 
			
		||||
            return this.fns[nameGen];
 | 
			
		||||
        }
 | 
			
		||||
        const monoFn = { id, nameGen, stmt, genericArgs };
 | 
			
		||||
        this.fns[nameGen] = monoFn;
 | 
			
		||||
        const calls = new CallCollector().collect(stmt);
 | 
			
		||||
        for (const call of calls) {
 | 
			
		||||
            this.callMap[call.id] = nameGen;
 | 
			
		||||
            if (call.kind.type !== "call") {
 | 
			
		||||
                throw new Error();
 | 
			
		||||
            }
 | 
			
		||||
            if (
 | 
			
		||||
                call.kind.subject.vtype?.type === "fn" &&
 | 
			
		||||
                call.kind.subject.vtype.genericParams === undefined
 | 
			
		||||
            ) {
 | 
			
		||||
                const fn = this.allFns.get(call.kind.subject.vtype.stmtId);
 | 
			
		||||
                if (fn === undefined) {
 | 
			
		||||
                    throw new Error();
 | 
			
		||||
                }
 | 
			
		||||
                const monoFn = this.monomorphizeFn(fn);
 | 
			
		||||
                this.callMap[call.id] = monoFn.nameGen;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (
 | 
			
		||||
                call.kind.subject.vtype?.type === "fn" &&
 | 
			
		||||
                call.kind.subject.vtype.genericParams !== undefined
 | 
			
		||||
            ) {
 | 
			
		||||
                if (call.kind.genericArgs === undefined) {
 | 
			
		||||
                    throw new Error();
 | 
			
		||||
                }
 | 
			
		||||
                const genericArgs = call.kind.genericArgs;
 | 
			
		||||
 | 
			
		||||
                const monoArgs: GenericArgsMap = {};
 | 
			
		||||
                for (const key in call.kind.genericArgs) {
 | 
			
		||||
                    const vtype = genericArgs[key];
 | 
			
		||||
                    if (vtype.type === "generic") {
 | 
			
		||||
                        if (genericArgs === undefined) {
 | 
			
		||||
                            throw new Error();
 | 
			
		||||
                        }
 | 
			
		||||
                        monoArgs[key] = genericArgs[vtype.param.id];
 | 
			
		||||
                    } else {
 | 
			
		||||
                        monoArgs[key] = vtype;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const fnType = call.kind.subject.vtype!;
 | 
			
		||||
                if (fnType.type !== "fn") {
 | 
			
		||||
                    throw new Error();
 | 
			
		||||
                }
 | 
			
		||||
                const fn = this.allFns.get(fnType.stmtId);
 | 
			
		||||
                if (fn === undefined) {
 | 
			
		||||
                    throw new Error();
 | 
			
		||||
                }
 | 
			
		||||
                const monoFn = this.monomorphizeFn(fn, monoArgs);
 | 
			
		||||
                this.callMap[call.id] = monoFn.nameGen;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (call.kind.subject.vtype?.type === "generic_spec") {
 | 
			
		||||
                const genericSpecType = call.kind.subject.vtype!;
 | 
			
		||||
                if (genericSpecType.subject.type !== "fn") {
 | 
			
		||||
                    throw new Error();
 | 
			
		||||
                }
 | 
			
		||||
                const fnType = genericSpecType.subject;
 | 
			
		||||
 | 
			
		||||
                const monoArgs: GenericArgsMap = {};
 | 
			
		||||
                for (const key in genericSpecType.genericArgs) {
 | 
			
		||||
                    const vtype = genericSpecType.genericArgs[key];
 | 
			
		||||
                    if (vtype.type === "generic") {
 | 
			
		||||
                        if (genericArgs === undefined) {
 | 
			
		||||
                            throw new Error();
 | 
			
		||||
                        }
 | 
			
		||||
                        monoArgs[key] = genericArgs[vtype.param.id];
 | 
			
		||||
                    } else {
 | 
			
		||||
                        monoArgs[key] = vtype;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const fn = this.allFns.get(fnType.stmtId);
 | 
			
		||||
                if (fn === undefined) {
 | 
			
		||||
                    throw new Error();
 | 
			
		||||
                }
 | 
			
		||||
                const monoFn = this.monomorphizeFn(fn, monoArgs);
 | 
			
		||||
                this.callMap[call.id] = monoFn.nameGen;
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        return monoFn;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type MonoResult = {
 | 
			
		||||
    monoFns: MonoFnsMap;
 | 
			
		||||
    callMap: MonoCallNameGenMap;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type MonoFnsMap = { [nameGen: string]: MonoFn };
 | 
			
		||||
 | 
			
		||||
export type MonoFn = {
 | 
			
		||||
    id: number;
 | 
			
		||||
    nameGen: string;
 | 
			
		||||
    stmt: Stmt;
 | 
			
		||||
    genericArgs?: GenericArgsMap;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type MonoCallNameGenMap = { [exprId: number]: string };
 | 
			
		||||
 | 
			
		||||
function monoFnNameGen(
 | 
			
		||||
    id: number,
 | 
			
		||||
    stmt: Stmt,
 | 
			
		||||
    genericArgs?: GenericArgsMap,
 | 
			
		||||
): string {
 | 
			
		||||
    if (stmt.kind.type !== "fn") {
 | 
			
		||||
        throw new Error();
 | 
			
		||||
    }
 | 
			
		||||
    if (stmt.kind.ident === "main") {
 | 
			
		||||
        return "main";
 | 
			
		||||
    }
 | 
			
		||||
    if (genericArgs === undefined) {
 | 
			
		||||
        return `${stmt.kind.ident}_${id}`;
 | 
			
		||||
    }
 | 
			
		||||
    const args = Object.values(genericArgs)
 | 
			
		||||
        .map((arg) => vtypeNameGenPart(arg))
 | 
			
		||||
        .join("_");
 | 
			
		||||
    return `${stmt.kind.ident}_${id}_${args}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function vtypeNameGenPart(vtype: VType): string {
 | 
			
		||||
    switch (vtype.type) {
 | 
			
		||||
        case "error":
 | 
			
		||||
            throw new Error("error in type");
 | 
			
		||||
        case "string":
 | 
			
		||||
        case "int":
 | 
			
		||||
        case "bool":
 | 
			
		||||
        case "null":
 | 
			
		||||
        case "unknown":
 | 
			
		||||
            return vtype.type;
 | 
			
		||||
        case "ref":
 | 
			
		||||
            return `&${vtypeNameGenPart(vtype.subject)}`;
 | 
			
		||||
        case "ref_mut":
 | 
			
		||||
            return `&mut ${vtypeNameGenPart(vtype.subject)}`;
 | 
			
		||||
        case "ptr":
 | 
			
		||||
            return `*${vtypeNameGenPart(vtype.subject)}`;
 | 
			
		||||
        case "ptr_mut":
 | 
			
		||||
            return `*mut ${vtypeNameGenPart(vtype.subject)}`;
 | 
			
		||||
        case "array":
 | 
			
		||||
            return `[${vtypeNameGenPart(vtype.subject)}]`;
 | 
			
		||||
        case "struct": {
 | 
			
		||||
            const fields = vtype.fields
 | 
			
		||||
                .map((field) =>
 | 
			
		||||
                    `${field.ident}, ${vtypeNameGenPart(field.vtype)}`
 | 
			
		||||
                )
 | 
			
		||||
                .join(", ");
 | 
			
		||||
            return `struct { ${fields} }`;
 | 
			
		||||
        }
 | 
			
		||||
        case "fn":
 | 
			
		||||
            return `fn(${vtype.stmtId})`;
 | 
			
		||||
        case "generic":
 | 
			
		||||
        case "generic_spec":
 | 
			
		||||
            throw new Error("cannot be monomorphized");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class AllFnsCollector implements AstVisitor {
 | 
			
		||||
    private allFns = new Map<number, Stmt>();
 | 
			
		||||
 | 
			
		||||
    public collect(ast: Stmt[]): Map<number, Stmt> {
 | 
			
		||||
        visitStmts(ast, this);
 | 
			
		||||
        return this.allFns;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitFnStmt(stmt: Stmt): VisitRes {
 | 
			
		||||
        if (stmt.kind.type !== "fn") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        this.allFns.set(stmt.id, stmt);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function findMain(fns: Map<number, Stmt>): Stmt {
 | 
			
		||||
    const mainId = fns.values().find((stmt) =>
 | 
			
		||||
        stmt.kind.type === "fn" && stmt.kind.ident === "main"
 | 
			
		||||
    );
 | 
			
		||||
    if (mainId === undefined) {
 | 
			
		||||
        console.error("error: cannot find function 'main'");
 | 
			
		||||
        console.error(apology);
 | 
			
		||||
        throw new Error("cannot find function 'main'");
 | 
			
		||||
    }
 | 
			
		||||
    return mainId;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class CallCollector implements AstVisitor {
 | 
			
		||||
    private calls: Expr[] = [];
 | 
			
		||||
 | 
			
		||||
    public collect(fn: Stmt): Expr[] {
 | 
			
		||||
        if (fn.kind.type !== "fn") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        visitExpr(fn.kind.body, this);
 | 
			
		||||
        return this.calls;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitFnStmt(_stmt: Stmt): VisitRes {
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitCallExpr(expr: Expr): VisitRes {
 | 
			
		||||
        if (expr.kind.type !== "call") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        this.calls.push(expr);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const apology = `
 | 
			
		||||
    Hear me out. Monomorphization, meaning the process
 | 
			
		||||
    inwich generic functions are stamped out into seperate
 | 
			
		||||
    specialized functions is actually really hard, and I
 | 
			
		||||
    have a really hard time right now, figuring out, how
 | 
			
		||||
    to do it in a smart way. To really explain it, let's
 | 
			
		||||
    imagine you have a function, you defined as a<T>().
 | 
			
		||||
    For each call with seperate generics arguments given,
 | 
			
		||||
    such as a::<int>() and a::<string>(), a specialized
 | 
			
		||||
    function has to be 'stamped out', ie. created and put
 | 
			
		||||
    into the compilation with the rest of the program. Now
 | 
			
		||||
    to the reason as to why 'main' is needed. To do the
 | 
			
		||||
    monomorphization, we have to do it recursively. To
 | 
			
		||||
    explain this, imagine you have a generic function a<T>
 | 
			
		||||
    and inside the body of a<T>, you call another generic
 | 
			
		||||
    function such as b<T> with the same generic type. This
 | 
			
		||||
    means that the monomorphization process of b<T> depends
 | 
			
		||||
    on the monomorphization of a<T>. What this essentially
 | 
			
		||||
    means, is that the monomorphization process works on
 | 
			
		||||
    the program as a call graph, meaning a graph or tree
 | 
			
		||||
    structure where each represents a function call to
 | 
			
		||||
    either another function or a recursive call to the
 | 
			
		||||
    function itself. But a problem arises from doing it
 | 
			
		||||
    this way, which is that a call graph will need an
 | 
			
		||||
    entrypoint. The language, as it is currently, does
 | 
			
		||||
    not really require a 'main'-function. Or maybe it
 | 
			
		||||
    does, but that's beside the point. The point is that
 | 
			
		||||
    we need a main function, to be the entry point for
 | 
			
		||||
    the call graph. The monomorphization process then
 | 
			
		||||
    runs through the program from that entry point. This
 | 
			
		||||
    means that each function we call, will itself be
 | 
			
		||||
    monomorphized and added to the compilation. It also
 | 
			
		||||
    means that functions that are not called, will also
 | 
			
		||||
    not be added to the compilation. This essentially
 | 
			
		||||
    eliminates uncalled/dead functions. Is this
 | 
			
		||||
    particularly smart to do in such a high level part
 | 
			
		||||
    of the compilation process? I don't know. It's
 | 
			
		||||
    obvious that we can't just use every function as
 | 
			
		||||
    an entry point in the call graph, because we're
 | 
			
		||||
    actively added new functions. Additionally, with
 | 
			
		||||
    generic functions, we don't know, if they're the
 | 
			
		||||
    entry point, what generic arguments, they should
 | 
			
		||||
    be monomorphized with. We could do monomorphization
 | 
			
		||||
    the same way C++ does it, where all non-generic
 | 
			
		||||
    functions before monomorphization are treated as
 | 
			
		||||
    entry points in the call graph. But this has the
 | 
			
		||||
    drawback that generic and non-generic functions
 | 
			
		||||
    are treated differently, which has many underlying
 | 
			
		||||
    drawbacks, especially pertaining to the amount of
 | 
			
		||||
    work needed to handle both in all proceeding steps
 | 
			
		||||
    of the compiler. Anyways, I just wanted to yap and
 | 
			
		||||
    complain about the way generics and monomorphization
 | 
			
		||||
    has made the compiler 100x more complicated, and
 | 
			
		||||
    that I find it really hard to implement in a way,
 | 
			
		||||
    that is not either too simplistic or so complicated
 | 
			
		||||
    and advanced I'm too dumb to implement it. So if
 | 
			
		||||
    you would be so kind as to make it clear to the
 | 
			
		||||
    compiler, what function it should designate as
 | 
			
		||||
    the entry point to the call graph, it will use
 | 
			
		||||
    for monomorphization, that would be very kind of
 | 
			
		||||
    you. The way you do this, is by added or selecting
 | 
			
		||||
    one of your current functions and giving it the
 | 
			
		||||
    name of 'main'. This is spelled m-a-i-n. The word
 | 
			
		||||
    is synonemous with the words primary and principle.
 | 
			
		||||
    The name is meant to designate the entry point into
 | 
			
		||||
    the program, which is why the monomorphization
 | 
			
		||||
    process uses this specific function as the entry
 | 
			
		||||
    point into the call graph, it generates. So if you
 | 
			
		||||
    would be so kind as to do that, that would really
 | 
			
		||||
    make my day. In any case, keep hacking ferociously
 | 
			
		||||
    on whatever you're working on. I have monomorphizer
 | 
			
		||||
    to implement. See ya. -Your favorite compiler girl <3
 | 
			
		||||
`.replaceAll("    ", "").trim();
 | 
			
		||||
@ -1,279 +0,0 @@
 | 
			
		||||
import { EType, Expr, Stmt } from "./ast.ts";
 | 
			
		||||
import {
 | 
			
		||||
    AstVisitor,
 | 
			
		||||
    visitEType,
 | 
			
		||||
    visitExpr,
 | 
			
		||||
    visitParam,
 | 
			
		||||
    VisitRes,
 | 
			
		||||
    visitStmt,
 | 
			
		||||
    visitStmts,
 | 
			
		||||
} from "./ast_visitor.ts";
 | 
			
		||||
import { printStackTrace, Reporter } from "./info.ts";
 | 
			
		||||
import {
 | 
			
		||||
    EntryModSyms,
 | 
			
		||||
    FnSyms,
 | 
			
		||||
    LeafSyms,
 | 
			
		||||
    ModSyms,
 | 
			
		||||
    Syms,
 | 
			
		||||
} from "./resolver_syms.ts";
 | 
			
		||||
import { Pos } from "./token.ts";
 | 
			
		||||
 | 
			
		||||
export class Resolver implements AstVisitor<[Syms]> {
 | 
			
		||||
    public constructor(private reporter: Reporter) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public resolve(stmts: Stmt[]): VisitRes {
 | 
			
		||||
        const syms = new EntryModSyms("root");
 | 
			
		||||
        this.scout(stmts, syms);
 | 
			
		||||
        visitStmts(stmts, this, syms);
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private scout(stmts: Stmt[], syms: Syms) {
 | 
			
		||||
        for (const stmt of stmts) {
 | 
			
		||||
            if (stmt.kind.type === "fn") {
 | 
			
		||||
                if (syms.definedLocally(stmt.kind.ident)) {
 | 
			
		||||
                    this.reportAlreadyDefined(stmt.kind.ident, stmt.pos, syms);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                const ident = stmt.kind.ident;
 | 
			
		||||
                stmt.kind.sym = syms.define(ident, {
 | 
			
		||||
                    ident: stmt.kind.ident,
 | 
			
		||||
                    type: "fn",
 | 
			
		||||
                    fullPath: `${syms.pathString()}::${ident}`,
 | 
			
		||||
                    pos: stmt.pos,
 | 
			
		||||
                    stmt,
 | 
			
		||||
                });
 | 
			
		||||
            } else if (stmt.kind.type === "type_alias") {
 | 
			
		||||
                const ident = stmt.kind.param.ident;
 | 
			
		||||
                if (syms.definedLocally(ident)) {
 | 
			
		||||
                    this.reportAlreadyDefined(ident, stmt.pos, syms);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                syms.define(ident, {
 | 
			
		||||
                    ident,
 | 
			
		||||
                    type: "type_alias",
 | 
			
		||||
                    fullPath: `${syms.pathString()}::${ident}`,
 | 
			
		||||
                    pos: stmt.kind.param.pos,
 | 
			
		||||
                    stmt,
 | 
			
		||||
                    param: stmt.kind.param,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitModStmt(stmt: Stmt, syms: Syms): VisitRes {
 | 
			
		||||
        if (stmt.kind.type !== "mod") {
 | 
			
		||||
            throw new Error("expected let statement");
 | 
			
		||||
        }
 | 
			
		||||
        const modSyms = new ModSyms(syms, stmt.kind.ident);
 | 
			
		||||
        const { mod, ident } = stmt.kind;
 | 
			
		||||
        this.scout(mod.ast, modSyms);
 | 
			
		||||
        visitStmts(mod.ast, this, modSyms);
 | 
			
		||||
 | 
			
		||||
        if (syms.definedLocally(ident)) {
 | 
			
		||||
            this.reportAlreadyDefined(ident, stmt.pos, syms);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        syms.define(ident, {
 | 
			
		||||
            type: "mod",
 | 
			
		||||
            ident,
 | 
			
		||||
            fullPath: `${syms.pathString()}::${ident}`,
 | 
			
		||||
            pos: stmt.pos,
 | 
			
		||||
            syms: modSyms,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitLetStmt(stmt: Stmt, syms: Syms): VisitRes {
 | 
			
		||||
        if (stmt.kind.type !== "let") {
 | 
			
		||||
            throw new Error("expected let statement");
 | 
			
		||||
        }
 | 
			
		||||
        visitExpr(stmt.kind.value, this, syms);
 | 
			
		||||
        const ident = stmt.kind.param.ident;
 | 
			
		||||
        if (syms.definedLocally(ident)) {
 | 
			
		||||
            this.reportAlreadyDefined(ident, stmt.pos, syms);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        stmt.kind.param.sym = syms.define(ident, {
 | 
			
		||||
            ident,
 | 
			
		||||
            type: "let",
 | 
			
		||||
            fullPath: ident,
 | 
			
		||||
            pos: stmt.kind.param.pos,
 | 
			
		||||
            stmt,
 | 
			
		||||
            param: stmt.kind.param,
 | 
			
		||||
        });
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitTypeAliasStmt(stmt: Stmt, _syms: Syms): VisitRes {
 | 
			
		||||
        if (stmt.kind.type !== "type_alias") {
 | 
			
		||||
            throw new Error("expected type_alias statement");
 | 
			
		||||
        }
 | 
			
		||||
        // nothing to do here
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitFnStmt(stmt: Stmt, syms: Syms): VisitRes {
 | 
			
		||||
        if (stmt.kind.type !== "fn") {
 | 
			
		||||
            throw new Error("expected fn statement");
 | 
			
		||||
        }
 | 
			
		||||
        const fnScopeSyms = new FnSyms(syms);
 | 
			
		||||
        for (const param of stmt.kind.genericParams ?? []) {
 | 
			
		||||
            if (fnScopeSyms.definedLocally(param.ident)) {
 | 
			
		||||
                this.reportAlreadyDefined(param.ident, param.pos, syms);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            fnScopeSyms.define(param.ident, {
 | 
			
		||||
                ident: param.ident,
 | 
			
		||||
                type: "generic",
 | 
			
		||||
                fullPath: param.ident,
 | 
			
		||||
                pos: param.pos,
 | 
			
		||||
                stmt,
 | 
			
		||||
                genericParam: param,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        for (const param of stmt.kind.params) {
 | 
			
		||||
            if (fnScopeSyms.definedLocally(param.ident)) {
 | 
			
		||||
                this.reportAlreadyDefined(param.ident, param.pos, syms);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            visitParam(param, this, fnScopeSyms);
 | 
			
		||||
            fnScopeSyms.define(param.ident, {
 | 
			
		||||
                ident: param.ident,
 | 
			
		||||
                type: "fn_param",
 | 
			
		||||
                fullPath: param.ident,
 | 
			
		||||
                pos: param.pos,
 | 
			
		||||
                param,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        if (stmt.kind.returnType) {
 | 
			
		||||
            visitEType(stmt.kind.returnType, this, fnScopeSyms);
 | 
			
		||||
        }
 | 
			
		||||
        visitExpr(stmt.kind.body, this, fnScopeSyms);
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitIdentExpr(expr: Expr, syms: Syms): VisitRes {
 | 
			
		||||
        if (expr.kind.type !== "ident") {
 | 
			
		||||
            throw new Error("expected ident");
 | 
			
		||||
        }
 | 
			
		||||
        const ident = expr.kind.ident;
 | 
			
		||||
        const symResult = syms.get(ident);
 | 
			
		||||
        if (!symResult.ok) {
 | 
			
		||||
            this.reportUseOfUndefined(ident, expr.pos, syms);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const sym = symResult.sym;
 | 
			
		||||
        expr.kind = { type: "sym", ident, sym };
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitPathExpr(expr: Expr, syms: Syms): VisitRes {
 | 
			
		||||
        if (expr.kind.type !== "path") {
 | 
			
		||||
            throw new Error("expected ident");
 | 
			
		||||
        }
 | 
			
		||||
        visitExpr(expr.kind.subject, this, syms);
 | 
			
		||||
        if (expr.kind.subject.kind.type !== "sym") {
 | 
			
		||||
            throw new Error("this error is not handled properly");
 | 
			
		||||
        }
 | 
			
		||||
        const subjectSym = expr.kind.subject.kind.sym;
 | 
			
		||||
        if (subjectSym.type !== "mod") {
 | 
			
		||||
            this.reporter.reportError({
 | 
			
		||||
                reporter: "Resolver",
 | 
			
		||||
                msg: `path expression are not implemented for '${subjectSym.type}' symbols`,
 | 
			
		||||
                pos: expr.pos,
 | 
			
		||||
            });
 | 
			
		||||
            printStackTrace();
 | 
			
		||||
            return "stop";
 | 
			
		||||
        }
 | 
			
		||||
        const getRes = subjectSym.syms.get(expr.kind.ident);
 | 
			
		||||
        if (!getRes.ok) {
 | 
			
		||||
            this.reportUseOfUndefined(
 | 
			
		||||
                expr.kind.ident,
 | 
			
		||||
                expr.pos,
 | 
			
		||||
                subjectSym.syms,
 | 
			
		||||
            );
 | 
			
		||||
            return "stop";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        expr.kind = {
 | 
			
		||||
            type: "sym",
 | 
			
		||||
            ident: expr.kind.ident,
 | 
			
		||||
            sym: getRes.sym,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitBlockExpr(expr: Expr, syms: Syms): VisitRes {
 | 
			
		||||
        if (expr.kind.type !== "block") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const childSyms = new LeafSyms(syms);
 | 
			
		||||
        this.scout(expr.kind.stmts, childSyms);
 | 
			
		||||
        visitStmts(expr.kind.stmts, this, childSyms);
 | 
			
		||||
        if (expr.kind.expr) {
 | 
			
		||||
            visitExpr(expr.kind.expr, this, childSyms);
 | 
			
		||||
        }
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitForExpr(expr: Expr, syms: Syms): VisitRes {
 | 
			
		||||
        if (expr.kind.type !== "for") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const childSyms = new LeafSyms(syms);
 | 
			
		||||
        if (expr.kind.decl) visitStmt(expr.kind.decl, this, syms);
 | 
			
		||||
        if (expr.kind.cond) visitExpr(expr.kind.cond, this, syms);
 | 
			
		||||
        if (expr.kind.incr) visitStmt(expr.kind.incr, this, syms);
 | 
			
		||||
        visitExpr(expr.kind.body, this, childSyms);
 | 
			
		||||
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitIdentEType(etype: EType, syms: Syms): VisitRes {
 | 
			
		||||
        if (etype.kind.type !== "ident") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const ident = etype.kind.ident;
 | 
			
		||||
        const symResult = syms.get(ident);
 | 
			
		||||
        if (!symResult.ok) {
 | 
			
		||||
            this.reportUseOfUndefined(ident, etype.pos, syms);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const sym = symResult.sym;
 | 
			
		||||
        etype.kind = { type: "sym", ident, sym };
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private reportUseOfUndefined(ident: string, pos: Pos, _syms: Syms) {
 | 
			
		||||
        this.reporter.reportError({
 | 
			
		||||
            reporter: "Resolver",
 | 
			
		||||
            msg: `use of undefined symbol '${ident}'`,
 | 
			
		||||
            pos,
 | 
			
		||||
        });
 | 
			
		||||
        printStackTrace();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private reportAlreadyDefined(ident: string, pos: Pos, syms: Syms) {
 | 
			
		||||
        this.reporter.reportError({
 | 
			
		||||
            reporter: "Resolver",
 | 
			
		||||
            msg: `symbol already defined '${ident}'`,
 | 
			
		||||
            pos,
 | 
			
		||||
        });
 | 
			
		||||
        const prev = syms.get(ident);
 | 
			
		||||
        if (!prev.ok) {
 | 
			
		||||
            throw new Error("expected to be defined");
 | 
			
		||||
        }
 | 
			
		||||
        if (!prev.sym.pos) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this.reporter.addNote({
 | 
			
		||||
            reporter: "Resolver",
 | 
			
		||||
            msg: `previous definition of '${ident}'`,
 | 
			
		||||
            pos: prev.sym.pos,
 | 
			
		||||
        });
 | 
			
		||||
        printStackTrace();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,192 +0,0 @@
 | 
			
		||||
import type { Sym } from "./ast.ts";
 | 
			
		||||
 | 
			
		||||
export type SymMap = { [ident: string]: Sym };
 | 
			
		||||
 | 
			
		||||
type GetRes = { ok: true; sym: Sym } | { ok: false };
 | 
			
		||||
 | 
			
		||||
export interface Syms {
 | 
			
		||||
    define(ident: string, sym: Sym): Sym;
 | 
			
		||||
    definedLocally(ident: string): boolean;
 | 
			
		||||
    get(ident: string): GetRes;
 | 
			
		||||
    getPub(ident: string): GetRes;
 | 
			
		||||
    rootMod(): Sym;
 | 
			
		||||
    pathString(): string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class EntryModSyms implements Syms {
 | 
			
		||||
    private syms: SymMap = {};
 | 
			
		||||
 | 
			
		||||
    public constructor(private modName: string) {}
 | 
			
		||||
 | 
			
		||||
    public define(ident: string, sym: Sym): Sym {
 | 
			
		||||
        if (sym.type === "let") {
 | 
			
		||||
            return this.define(ident, {
 | 
			
		||||
                ...sym,
 | 
			
		||||
                type: "let_static",
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        this.syms[ident] = sym;
 | 
			
		||||
        return sym;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public definedLocally(ident: string): boolean {
 | 
			
		||||
        return ident in this.syms;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public get(ident: string): GetRes {
 | 
			
		||||
        if (ident in this.syms) {
 | 
			
		||||
            return { ok: true, sym: this.syms[ident] };
 | 
			
		||||
        }
 | 
			
		||||
        return { ok: false };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public getPub(ident: string): GetRes {
 | 
			
		||||
        if (ident in this.syms) {
 | 
			
		||||
            return { ok: true, sym: this.syms[ident] };
 | 
			
		||||
        }
 | 
			
		||||
        return { ok: false };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public rootMod(): Sym {
 | 
			
		||||
        return {
 | 
			
		||||
            type: "mod",
 | 
			
		||||
            ident: this.modName,
 | 
			
		||||
            fullPath: this.modName,
 | 
			
		||||
            syms: this,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public pathString(): string {
 | 
			
		||||
        return this.modName;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ModSyms implements Syms {
 | 
			
		||||
    private syms: SymMap = {};
 | 
			
		||||
 | 
			
		||||
    public constructor(private parent: Syms, private modName: string) {
 | 
			
		||||
        this.syms["super"] = {
 | 
			
		||||
            type: "mod",
 | 
			
		||||
            ident: "super",
 | 
			
		||||
            fullPath: this.pathString(),
 | 
			
		||||
            syms: this.parent,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public define(ident: string, sym: Sym): Sym {
 | 
			
		||||
        if (sym.type === "let") {
 | 
			
		||||
            return this.define(ident, {
 | 
			
		||||
                ...sym,
 | 
			
		||||
                type: "let_static",
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return this.syms[ident] = sym;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public definedLocally(ident: string): boolean {
 | 
			
		||||
        return ident in this.syms;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public get(ident: string): GetRes {
 | 
			
		||||
        if (ident in this.syms) {
 | 
			
		||||
            return { ok: true, sym: this.syms[ident] };
 | 
			
		||||
        }
 | 
			
		||||
        return { ok: false };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public getPub(ident: string): GetRes {
 | 
			
		||||
        if (ident in this.syms) {
 | 
			
		||||
            return { ok: true, sym: this.syms[ident] };
 | 
			
		||||
        }
 | 
			
		||||
        return { ok: false };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public rootMod(): Sym {
 | 
			
		||||
        return this.parent.rootMod();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public pathString(): string {
 | 
			
		||||
        return `${this.parent.pathString()}::${this.modName}`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class FnSyms implements Syms {
 | 
			
		||||
    private syms: SymMap = {};
 | 
			
		||||
 | 
			
		||||
    public constructor(private parent: Syms) {}
 | 
			
		||||
 | 
			
		||||
    public define(ident: string, sym: Sym): Sym {
 | 
			
		||||
        if (sym.type === "let") {
 | 
			
		||||
            return this.define(ident, {
 | 
			
		||||
                ...sym,
 | 
			
		||||
                type: "closure",
 | 
			
		||||
                inner: sym,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        this.syms[ident] = sym;
 | 
			
		||||
        return sym;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public definedLocally(ident: string): boolean {
 | 
			
		||||
        return ident in this.syms;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public get(ident: string): GetRes {
 | 
			
		||||
        if (ident in this.syms) {
 | 
			
		||||
            return { ok: true, sym: this.syms[ident] };
 | 
			
		||||
        }
 | 
			
		||||
        return this.parent.get(ident);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public getPub(ident: string): GetRes {
 | 
			
		||||
        if (ident in this.syms) {
 | 
			
		||||
            return { ok: true, sym: this.syms[ident] };
 | 
			
		||||
        }
 | 
			
		||||
        return { ok: false };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public rootMod(): Sym {
 | 
			
		||||
        return this.parent.rootMod();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public pathString(): string {
 | 
			
		||||
        return this.parent.pathString();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class LeafSyms implements Syms {
 | 
			
		||||
    private syms: SymMap = {};
 | 
			
		||||
 | 
			
		||||
    public constructor(private parent: Syms) {}
 | 
			
		||||
 | 
			
		||||
    public define(ident: string, sym: Sym): Sym {
 | 
			
		||||
        this.syms[ident] = sym;
 | 
			
		||||
        return sym;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public definedLocally(ident: string): boolean {
 | 
			
		||||
        return ident in this.syms;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public get(ident: string): GetRes {
 | 
			
		||||
        if (ident in this.syms) {
 | 
			
		||||
            return { ok: true, sym: this.syms[ident] };
 | 
			
		||||
        }
 | 
			
		||||
        return this.parent.get(ident);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public getPub(ident: string): GetRes {
 | 
			
		||||
        if (ident in this.syms) {
 | 
			
		||||
            return { ok: true, sym: this.syms[ident] };
 | 
			
		||||
        }
 | 
			
		||||
        return { ok: false };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public rootMod(): Sym {
 | 
			
		||||
        return this.parent.rootMod();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public pathString(): string {
 | 
			
		||||
        return this.parent.pathString();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,168 +0,0 @@
 | 
			
		||||
export type VType =
 | 
			
		||||
    | { type: "error" }
 | 
			
		||||
    | { type: "unknown" }
 | 
			
		||||
    | { type: "null" }
 | 
			
		||||
    | { type: "int" }
 | 
			
		||||
    | { type: "string" }
 | 
			
		||||
    | { type: "bool" }
 | 
			
		||||
    | { type: "ref"; subject: VType }
 | 
			
		||||
    | { type: "ref_mut"; subject: VType }
 | 
			
		||||
    | { type: "ptr"; subject: VType }
 | 
			
		||||
    | { type: "ptr_mut"; subject: VType }
 | 
			
		||||
    | { type: "array"; subject: VType }
 | 
			
		||||
    | { type: "struct"; fields: VTypeParam[] }
 | 
			
		||||
    | {
 | 
			
		||||
        type: "fn";
 | 
			
		||||
        genericParams?: VTypeGenericParam[];
 | 
			
		||||
        params: VTypeParam[];
 | 
			
		||||
        returnType: VType;
 | 
			
		||||
        stmtId: number;
 | 
			
		||||
    }
 | 
			
		||||
    | { type: "generic"; param: VTypeGenericParam }
 | 
			
		||||
    | {
 | 
			
		||||
        type: "generic_spec";
 | 
			
		||||
        subject: VType;
 | 
			
		||||
        genericArgs: GenericArgsMap;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
export type VTypeParam = {
 | 
			
		||||
    ident: string;
 | 
			
		||||
    mut: boolean;
 | 
			
		||||
    vtype: VType;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type VTypeGenericParam = {
 | 
			
		||||
    id: number;
 | 
			
		||||
    ident: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type GenericArgsMap = { [id: number]: VType };
 | 
			
		||||
 | 
			
		||||
export function vtypesEqual(
 | 
			
		||||
    a: VType,
 | 
			
		||||
    b: VType,
 | 
			
		||||
    generics?: GenericArgsMap,
 | 
			
		||||
): boolean {
 | 
			
		||||
    if (
 | 
			
		||||
        ["error", "unknown", "null", "int", "string", "bool"]
 | 
			
		||||
            .includes(a.type) && a.type === b.type
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    if (a.type === "ref" && b.type === "ref") {
 | 
			
		||||
        return vtypesEqual(a.subject, b.subject, generics);
 | 
			
		||||
    }
 | 
			
		||||
    if (a.type === "ref_mut" && b.type === "ref_mut") {
 | 
			
		||||
        return vtypesEqual(a.subject, b.subject, generics);
 | 
			
		||||
    }
 | 
			
		||||
    if (a.type === "ptr" && b.type === "ptr") {
 | 
			
		||||
        return vtypesEqual(a.subject, b.subject, generics);
 | 
			
		||||
    }
 | 
			
		||||
    if (a.type === "ptr_mut" && b.type === "ptr_mut") {
 | 
			
		||||
        return vtypesEqual(a.subject, b.subject, generics);
 | 
			
		||||
    }
 | 
			
		||||
    if (a.type === "array" && b.type === "array") {
 | 
			
		||||
        return vtypesEqual(a.subject, b.subject, generics);
 | 
			
		||||
    }
 | 
			
		||||
    if (a.type === "struct" && b.type === "struct") {
 | 
			
		||||
        if (a.fields.length !== b.fields.length) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        const match = a.fields
 | 
			
		||||
            .map((af) => ({
 | 
			
		||||
                ident: af.ident,
 | 
			
		||||
                af,
 | 
			
		||||
                bf: b.fields.find((bf) => bf.ident === af.ident),
 | 
			
		||||
            }));
 | 
			
		||||
        if (match.some((m) => m.bf === undefined)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        if (match.some((m) => !vtypesEqual(m.af.vtype, m.bf!.vtype))) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    if (a.type === "fn" && b.type === "fn") {
 | 
			
		||||
        if (a.params.length !== b.params.length) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        for (let i = 0; i < a.params.length; ++i) {
 | 
			
		||||
            if (!vtypesEqual(a.params[i].vtype, b.params[i].vtype)) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return vtypesEqual(a.returnType, b.returnType, generics);
 | 
			
		||||
    }
 | 
			
		||||
    if (a.type === "generic" && b.type === "generic") {
 | 
			
		||||
        return a.param.id === b.param.id;
 | 
			
		||||
    }
 | 
			
		||||
    if (
 | 
			
		||||
        (a.type === "generic" || b.type === "generic") &&
 | 
			
		||||
        generics !== undefined
 | 
			
		||||
    ) {
 | 
			
		||||
        if (generics === undefined) {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const generic = a.type === "generic" ? a : b;
 | 
			
		||||
        const concrete = a.type === "generic" ? b : a;
 | 
			
		||||
 | 
			
		||||
        const genericType = extractGenericType(generic, generics);
 | 
			
		||||
        return vtypesEqual(genericType, concrete, generics);
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function extractGenericType(
 | 
			
		||||
    generic: VType,
 | 
			
		||||
    generics: GenericArgsMap,
 | 
			
		||||
): VType {
 | 
			
		||||
    if (generic.type !== "generic") {
 | 
			
		||||
        return generic;
 | 
			
		||||
    }
 | 
			
		||||
    if (!(generic.param.id in generics)) {
 | 
			
		||||
        throw new Error("generic not found (not supposed to happen)");
 | 
			
		||||
    }
 | 
			
		||||
    return generics[generic.param.id];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function vtypeToString(vtype: VType): string {
 | 
			
		||||
    if (
 | 
			
		||||
        ["error", "unknown", "null", "int", "string", "bool"]
 | 
			
		||||
            .includes(vtype.type)
 | 
			
		||||
    ) {
 | 
			
		||||
        return vtype.type;
 | 
			
		||||
    }
 | 
			
		||||
    if (vtype.type === "ref") {
 | 
			
		||||
        return `&${vtypeToString(vtype.subject)}`;
 | 
			
		||||
    }
 | 
			
		||||
    if (vtype.type === "ref_mut") {
 | 
			
		||||
        return `&mut ${vtypeToString(vtype.subject)}`;
 | 
			
		||||
    }
 | 
			
		||||
    if (vtype.type === "ptr") {
 | 
			
		||||
        return `*${vtypeToString(vtype.subject)}`;
 | 
			
		||||
    }
 | 
			
		||||
    if (vtype.type === "ptr_mut") {
 | 
			
		||||
        return `*mut ${vtypeToString(vtype.subject)}`;
 | 
			
		||||
    }
 | 
			
		||||
    if (vtype.type === "array") {
 | 
			
		||||
        return `[${vtypeToString(vtype.subject)}]`;
 | 
			
		||||
    }
 | 
			
		||||
    if (vtype.type === "struct") {
 | 
			
		||||
        const fields = vtype.fields
 | 
			
		||||
            .map((field) => `${field.ident}: ${vtypeToString(field.vtype)}`)
 | 
			
		||||
            .join(", ");
 | 
			
		||||
        return `struct { ${fields} }`;
 | 
			
		||||
    }
 | 
			
		||||
    if (vtype.type === "fn") {
 | 
			
		||||
        const paramString = vtype.params.map((param) =>
 | 
			
		||||
            `${param.ident}: ${vtypeToString(param.vtype)}`
 | 
			
		||||
        )
 | 
			
		||||
            .join(", ");
 | 
			
		||||
        return `fn(${paramString}) -> ${vtypeToString(vtype.returnType)}`;
 | 
			
		||||
    }
 | 
			
		||||
    if (vtype.type === "generic") {
 | 
			
		||||
        return `generic`;
 | 
			
		||||
    }
 | 
			
		||||
    throw new Error(`unhandled vtype '${vtype.type}'`);
 | 
			
		||||
}
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
import {
 | 
			
		||||
    Anno,
 | 
			
		||||
    AssignType,
 | 
			
		||||
    AstCreator,
 | 
			
		||||
    BinaryType,
 | 
			
		||||
@ -6,11 +7,8 @@ import {
 | 
			
		||||
    ETypeKind,
 | 
			
		||||
    Expr,
 | 
			
		||||
    ExprKind,
 | 
			
		||||
    Field,
 | 
			
		||||
    GenericParam,
 | 
			
		||||
    Param,
 | 
			
		||||
    Stmt,
 | 
			
		||||
    StmtDetails,
 | 
			
		||||
    StmtKind,
 | 
			
		||||
    UnaryType,
 | 
			
		||||
} from "./ast.ts";
 | 
			
		||||
@ -18,8 +16,6 @@ import { printStackTrace, Reporter } from "./info.ts";
 | 
			
		||||
import { Lexer } from "./lexer.ts";
 | 
			
		||||
import { Pos, Token } from "./token.ts";
 | 
			
		||||
 | 
			
		||||
type Res<T> = { ok: true; value: T } | { ok: false; pos?: Pos };
 | 
			
		||||
 | 
			
		||||
export class Parser {
 | 
			
		||||
    private currentToken: Token | null;
 | 
			
		||||
 | 
			
		||||
@ -38,33 +34,25 @@ export class Parser {
 | 
			
		||||
    private parseStmts(): Stmt[] {
 | 
			
		||||
        const stmts: Stmt[] = [];
 | 
			
		||||
        while (!this.done()) {
 | 
			
		||||
            stmts.push(this.parseStmt());
 | 
			
		||||
        }
 | 
			
		||||
        return stmts;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseStmt(): Stmt {
 | 
			
		||||
        if (
 | 
			
		||||
            ["#", "pub", "mod", "fn"].some((tt) => this.test(tt))
 | 
			
		||||
        ) {
 | 
			
		||||
            return this.parseItemStmt();
 | 
			
		||||
            if (this.test("fn")) {
 | 
			
		||||
                stmts.push(this.parseFn());
 | 
			
		||||
            } else if (
 | 
			
		||||
            ["let", "type_alias", "return", "break"].some((tt) => this.test(tt))
 | 
			
		||||
                this.test("let") || this.test("return") || this.test("break")
 | 
			
		||||
            ) {
 | 
			
		||||
            const expr = this.parseSingleLineBlockStmt();
 | 
			
		||||
                stmts.push(this.parseSingleLineBlockStmt());
 | 
			
		||||
                this.eatSemicolon();
 | 
			
		||||
            return expr;
 | 
			
		||||
            } else if (
 | 
			
		||||
                ["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
 | 
			
		||||
            ) {
 | 
			
		||||
                const expr = this.parseMultiLineBlockExpr();
 | 
			
		||||
            return (this.stmt({ type: "expr", expr }, expr.pos));
 | 
			
		||||
                stmts.push(this.stmt({ type: "expr", expr }, expr.pos));
 | 
			
		||||
            } else {
 | 
			
		||||
            const expr = this.parseAssign();
 | 
			
		||||
                stmts.push(this.parseAssign());
 | 
			
		||||
                this.eatSemicolon();
 | 
			
		||||
            return expr;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return stmts;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseMultiLineBlockExpr(): Expr {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
@ -92,9 +80,6 @@ export class Parser {
 | 
			
		||||
        if (this.test("let")) {
 | 
			
		||||
            return this.parseLet();
 | 
			
		||||
        }
 | 
			
		||||
        if (this.test("type_alias")) {
 | 
			
		||||
            return this.parseTypeAlias();
 | 
			
		||||
        }
 | 
			
		||||
        if (this.test("return")) {
 | 
			
		||||
            return this.parseReturn();
 | 
			
		||||
        }
 | 
			
		||||
@ -122,21 +107,19 @@ export class Parser {
 | 
			
		||||
    private parseBlock(): Expr {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
        this.step();
 | 
			
		||||
        const stmts: Stmt[] = [];
 | 
			
		||||
        let stmts: Stmt[] = [];
 | 
			
		||||
        while (!this.done()) {
 | 
			
		||||
            if (this.test("}")) {
 | 
			
		||||
                this.step();
 | 
			
		||||
                return this.expr({ type: "block", stmts }, pos);
 | 
			
		||||
            } else if (
 | 
			
		||||
                ["#", "pub", "mod", "fn"].some((tt) => this.test(tt))
 | 
			
		||||
            ) {
 | 
			
		||||
                stmts.push(this.parseItemStmt());
 | 
			
		||||
            } else if (
 | 
			
		||||
                ["let", "type_alias", "return", "break"]
 | 
			
		||||
                    .some((tt) => this.test(tt))
 | 
			
		||||
                this.test("return") || this.test("break") || this.test("let")
 | 
			
		||||
            ) {
 | 
			
		||||
                stmts.push(this.parseSingleLineBlockStmt());
 | 
			
		||||
                this.eatSemicolon();
 | 
			
		||||
            } else if (this.test("fn")) {
 | 
			
		||||
                stmts.push(this.parseSingleLineBlockStmt());
 | 
			
		||||
                stmts.push(this.parseFn());
 | 
			
		||||
            } else if (
 | 
			
		||||
                ["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
 | 
			
		||||
            ) {
 | 
			
		||||
@ -180,110 +163,7 @@ export class Parser {
 | 
			
		||||
        return this.expr({ type: "error" }, pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseItemStmt(
 | 
			
		||||
        pos = this.pos(),
 | 
			
		||||
        details: StmtDetails = {
 | 
			
		||||
            pub: false,
 | 
			
		||||
            annos: [],
 | 
			
		||||
        },
 | 
			
		||||
    ): Stmt {
 | 
			
		||||
        const spos = this.pos();
 | 
			
		||||
        if (this.test("#") && !details.pub) {
 | 
			
		||||
            this.step();
 | 
			
		||||
            if (!this.test("[")) {
 | 
			
		||||
                this.report("expected '['");
 | 
			
		||||
                return this.stmt({ type: "error" }, spos);
 | 
			
		||||
            }
 | 
			
		||||
            this.step();
 | 
			
		||||
            if (!this.test("ident")) {
 | 
			
		||||
                this.report("expected 'ident'");
 | 
			
		||||
                return this.stmt({ type: "error" }, spos);
 | 
			
		||||
            }
 | 
			
		||||
            const ident = this.current().identValue!;
 | 
			
		||||
            this.step();
 | 
			
		||||
            const args: Expr[] = [];
 | 
			
		||||
            if (this.test("(")) {
 | 
			
		||||
                this.step();
 | 
			
		||||
                if (!this.done() && !this.test(")")) {
 | 
			
		||||
                    args.push(this.parseExpr());
 | 
			
		||||
                    while (this.test(",")) {
 | 
			
		||||
                        this.step();
 | 
			
		||||
                        if (this.done() || this.test(")")) {
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                        args.push(this.parseExpr());
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (!this.test(")")) {
 | 
			
		||||
                    this.report("expected ')'");
 | 
			
		||||
                    return this.stmt({ type: "error" }, spos);
 | 
			
		||||
                }
 | 
			
		||||
                this.step();
 | 
			
		||||
            }
 | 
			
		||||
            if (!this.test("]")) {
 | 
			
		||||
                this.report("expected ']'");
 | 
			
		||||
                return this.stmt({ type: "error" }, spos);
 | 
			
		||||
            }
 | 
			
		||||
            this.step();
 | 
			
		||||
            const anno = { ident, args, pos: spos };
 | 
			
		||||
            return this.parseItemStmt(pos, {
 | 
			
		||||
                ...details,
 | 
			
		||||
                annos: [...details.annos, anno],
 | 
			
		||||
            });
 | 
			
		||||
        } else if (this.test("pub") && !details.pub) {
 | 
			
		||||
            this.step();
 | 
			
		||||
            return this.parseItemStmt(pos, { ...details, pub: true });
 | 
			
		||||
        } else if (this.test("mod")) {
 | 
			
		||||
            return this.parseMod(details);
 | 
			
		||||
        } else if (this.test("fn")) {
 | 
			
		||||
            return this.parseFn(details);
 | 
			
		||||
        } else {
 | 
			
		||||
            this.report("expected item statement");
 | 
			
		||||
            return this.stmt({ type: "error" }, pos);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseMod(details: StmtDetails): Stmt {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
        this.step();
 | 
			
		||||
        if (!this.test("ident")) {
 | 
			
		||||
            this.report("expected 'ident'");
 | 
			
		||||
            return this.stmt({ type: "error" }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        const ident = this.current().identValue!;
 | 
			
		||||
        this.step();
 | 
			
		||||
        if (this.test(";")) {
 | 
			
		||||
            this.eatSemicolon();
 | 
			
		||||
            return this.stmt({ type: "mod_file", ident, filePath: ident }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        if (this.test("string")) {
 | 
			
		||||
            const filePath = this.current().stringValue!;
 | 
			
		||||
            this.step();
 | 
			
		||||
            this.eatSemicolon();
 | 
			
		||||
            return this.stmt({ type: "mod_file", ident, filePath }, pos);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.test("{")) {
 | 
			
		||||
            this.report("expected '{' or 'string'");
 | 
			
		||||
            return this.stmt({ type: "error" }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        this.step();
 | 
			
		||||
 | 
			
		||||
        const stmts: Stmt[] = [];
 | 
			
		||||
        while (!this.done() && !this.test("}")) {
 | 
			
		||||
            stmts.push(this.parseStmt());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.test("}")) {
 | 
			
		||||
            this.report("expected '}'");
 | 
			
		||||
            return this.stmt({ type: "error" }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        this.step();
 | 
			
		||||
 | 
			
		||||
        return this.stmt({ type: "mod_block", ident, stmts }, pos, details);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseFn(details: StmtDetails): Stmt {
 | 
			
		||||
    private parseFn(): Stmt {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
        this.step();
 | 
			
		||||
        if (!this.test("ident")) {
 | 
			
		||||
@ -292,21 +172,21 @@ export class Parser {
 | 
			
		||||
        }
 | 
			
		||||
        const ident = this.current().identValue!;
 | 
			
		||||
        this.step();
 | 
			
		||||
        let genericParams: GenericParam[] | undefined;
 | 
			
		||||
        if (this.test("<")) {
 | 
			
		||||
            genericParams = this.parseFnETypeParams();
 | 
			
		||||
        }
 | 
			
		||||
        if (!this.test("(")) {
 | 
			
		||||
            this.report("expected '('");
 | 
			
		||||
            return this.stmt({ type: "error" }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        const params = this.parseFnParams();
 | 
			
		||||
        let returnType: EType | undefined;
 | 
			
		||||
        let returnType: EType | null = null;
 | 
			
		||||
        if (this.test("->")) {
 | 
			
		||||
            this.step();
 | 
			
		||||
            returnType = this.parseEType();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let anno: Anno | null = null;
 | 
			
		||||
        if (this.test("#")) {
 | 
			
		||||
            anno = this.parseAnno();
 | 
			
		||||
        }
 | 
			
		||||
        if (!this.test("{")) {
 | 
			
		||||
            this.report("expected block");
 | 
			
		||||
            return this.stmt({ type: "error" }, pos);
 | 
			
		||||
@ -316,104 +196,105 @@ export class Parser {
 | 
			
		||||
            {
 | 
			
		||||
                type: "fn",
 | 
			
		||||
                ident,
 | 
			
		||||
                genericParams,
 | 
			
		||||
                params,
 | 
			
		||||
                returnType,
 | 
			
		||||
                returnType: returnType !== null ? returnType : undefined,
 | 
			
		||||
                body,
 | 
			
		||||
                anno: anno != null ? anno : undefined,
 | 
			
		||||
            },
 | 
			
		||||
            pos,
 | 
			
		||||
            details,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseFnETypeParams(): GenericParam[] {
 | 
			
		||||
        return this.parseDelimitedList(this.parseETypeParam, ">", ",");
 | 
			
		||||
    private parseAnnoArgs(): Expr[] {
 | 
			
		||||
        this.step();
 | 
			
		||||
        if (!this.test("(")) {
 | 
			
		||||
            this.report("expected '('");
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
        this.step();
 | 
			
		||||
        const annoArgs: Expr[] = [];
 | 
			
		||||
        if (!this.test(")")) {
 | 
			
		||||
            annoArgs.push(this.parseExpr());
 | 
			
		||||
            while (this.test(",")) {
 | 
			
		||||
                this.step();
 | 
			
		||||
                if (this.test(")")) {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                annoArgs.push(this.parseExpr());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (!this.test(")")) {
 | 
			
		||||
            this.report("expected ')'");
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
        this.step();
 | 
			
		||||
        return annoArgs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseETypeParam(index: number): Res<GenericParam> {
 | 
			
		||||
    private parseAnno(): Anno | null {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
        if (this.test("ident")) {
 | 
			
		||||
            const ident = this.current().identValue!;
 | 
			
		||||
        this.step();
 | 
			
		||||
            return {
 | 
			
		||||
                ok: true,
 | 
			
		||||
                value: this.astCreator.genericParam({ index, ident, pos }),
 | 
			
		||||
            };
 | 
			
		||||
        if (!this.test("[")) {
 | 
			
		||||
            this.report("expected '['");
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        this.report("expected generic parameter");
 | 
			
		||||
        return { ok: false };
 | 
			
		||||
        this.step();
 | 
			
		||||
        if (!this.test("ident")) {
 | 
			
		||||
            this.report("expected identifier");
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        const ident = this.current().identValue!;
 | 
			
		||||
        const values = this.parseAnnoArgs();
 | 
			
		||||
        if (!this.test("]")) {
 | 
			
		||||
            this.report("expected ']'");
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        this.step();
 | 
			
		||||
        return { ident, pos, values };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseFnParams(): Param[] {
 | 
			
		||||
        return this.parseDelimitedList(this.parseParam, ")", ",");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseDelimitedList<T>(
 | 
			
		||||
        parseElem: (this: Parser, index: number) => Res<T>,
 | 
			
		||||
        endToken: string,
 | 
			
		||||
        delimiter: string,
 | 
			
		||||
    ): T[] {
 | 
			
		||||
        this.step();
 | 
			
		||||
        if (this.test(endToken)) {
 | 
			
		||||
        if (this.test(")")) {
 | 
			
		||||
            this.step();
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
        let i = 0;
 | 
			
		||||
        const elems: T[] = [];
 | 
			
		||||
        const elemRes = parseElem.call(this, i);
 | 
			
		||||
        if (!elemRes.ok) {
 | 
			
		||||
        const params: Param[] = [];
 | 
			
		||||
        const paramResult = this.parseParam();
 | 
			
		||||
        if (!paramResult.ok) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
        elems.push(elemRes.value);
 | 
			
		||||
        i += 1;
 | 
			
		||||
        while (this.test(delimiter)) {
 | 
			
		||||
        params.push(paramResult.value);
 | 
			
		||||
        while (this.test(",")) {
 | 
			
		||||
            this.step();
 | 
			
		||||
            if (this.test(endToken)) {
 | 
			
		||||
            if (this.test(")")) {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            const elemRes = parseElem.call(this, i);
 | 
			
		||||
            if (!elemRes.ok) {
 | 
			
		||||
            const paramResult = this.parseParam();
 | 
			
		||||
            if (!paramResult.ok) {
 | 
			
		||||
                return [];
 | 
			
		||||
            }
 | 
			
		||||
            elems.push(elemRes.value);
 | 
			
		||||
            i += 1;
 | 
			
		||||
            params.push(paramResult.value);
 | 
			
		||||
        }
 | 
			
		||||
        if (!this.test(endToken)) {
 | 
			
		||||
            this.report(`expected '${endToken}'`);
 | 
			
		||||
            return elems;
 | 
			
		||||
        if (!this.test(")")) {
 | 
			
		||||
            this.report("expected ')'");
 | 
			
		||||
            return params;
 | 
			
		||||
        }
 | 
			
		||||
        this.step();
 | 
			
		||||
        return elems;
 | 
			
		||||
        return params;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseParam(index?: number): Res<Param> {
 | 
			
		||||
    private parseParam(): { ok: true; value: Param } | { ok: false } {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
        if (this.test("ident") || this.test("mut")) {
 | 
			
		||||
            let mut = false;
 | 
			
		||||
            if (this.test("mut")) {
 | 
			
		||||
                mut = true;
 | 
			
		||||
                this.step();
 | 
			
		||||
            }
 | 
			
		||||
        if (this.test("ident")) {
 | 
			
		||||
            const ident = this.current().identValue!;
 | 
			
		||||
            this.step();
 | 
			
		||||
            if (this.test(":")) {
 | 
			
		||||
                this.step();
 | 
			
		||||
                const etype = this.parseEType();
 | 
			
		||||
                return {
 | 
			
		||||
                    ok: true,
 | 
			
		||||
                    value: this.astCreator.param({
 | 
			
		||||
                        index,
 | 
			
		||||
                        ident,
 | 
			
		||||
                        mut,
 | 
			
		||||
                        etype,
 | 
			
		||||
                        pos,
 | 
			
		||||
                    }),
 | 
			
		||||
                };
 | 
			
		||||
                return { ok: true, value: { ident, etype, pos } };
 | 
			
		||||
            }
 | 
			
		||||
            return {
 | 
			
		||||
                ok: true,
 | 
			
		||||
                value: this.astCreator.param({ index, ident, mut, pos }),
 | 
			
		||||
            };
 | 
			
		||||
            return { ok: true, value: { ident, pos } };
 | 
			
		||||
        }
 | 
			
		||||
        this.report("expected param");
 | 
			
		||||
        return { ok: false };
 | 
			
		||||
@ -436,17 +317,6 @@ export class Parser {
 | 
			
		||||
        return this.stmt({ type: "let", param, value }, pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseTypeAlias(): Stmt {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
        this.step();
 | 
			
		||||
        const paramResult = this.parseParam();
 | 
			
		||||
        if (!paramResult.ok) {
 | 
			
		||||
            return this.stmt({ type: "error" }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        const param = paramResult.value;
 | 
			
		||||
        return this.stmt({ type: "type_alias", param }, pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseAssign(): Stmt {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
        const subject = this.parseExpr();
 | 
			
		||||
@ -574,80 +444,6 @@ export class Parser {
 | 
			
		||||
        return this.expr({ type: "for", decl, cond, incr, body }, pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseArray(): Expr {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
        this.step();
 | 
			
		||||
        const exprs: Expr[] = [];
 | 
			
		||||
        if (!this.test("]")) {
 | 
			
		||||
            exprs.push(this.parseExpr());
 | 
			
		||||
            while (this.test(",")) {
 | 
			
		||||
                this.step();
 | 
			
		||||
                if (this.done() || this.test("]")) {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                exprs.push(this.parseExpr());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (!this.test("]")) {
 | 
			
		||||
            this.report("expected ']'");
 | 
			
		||||
            return this.expr({ type: "error" }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        this.step();
 | 
			
		||||
        return this.expr({ type: "array", exprs }, pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseStruct(): Expr {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
        this.step();
 | 
			
		||||
        if (!this.test("{")) {
 | 
			
		||||
            this.report("expected '{'");
 | 
			
		||||
            return this.expr({ type: "error" }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        this.step();
 | 
			
		||||
        const fields: Field[] = [];
 | 
			
		||||
        if (!this.test("}")) {
 | 
			
		||||
            const res = this.parseStructField();
 | 
			
		||||
            if (!res.ok) {
 | 
			
		||||
                return this.expr({ type: "error" }, res.pos!);
 | 
			
		||||
            }
 | 
			
		||||
            fields.push(res.value);
 | 
			
		||||
            while (this.test(",")) {
 | 
			
		||||
                this.step();
 | 
			
		||||
                if (this.done() || this.test("}")) {
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                const res = this.parseStructField();
 | 
			
		||||
                if (!res.ok) {
 | 
			
		||||
                    return this.expr({ type: "error" }, res.pos!);
 | 
			
		||||
                }
 | 
			
		||||
                fields.push(res.value);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (!this.test("}")) {
 | 
			
		||||
            this.report("expected '}'");
 | 
			
		||||
            return this.expr({ type: "error" }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        this.step();
 | 
			
		||||
        return this.expr({ type: "struct", fields }, pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseStructField(): Res<Field> {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
        if (!this.test("ident")) {
 | 
			
		||||
            this.report("expected 'ident'");
 | 
			
		||||
            return { ok: false, pos };
 | 
			
		||||
        }
 | 
			
		||||
        const ident = this.current().identValue!;
 | 
			
		||||
        this.step();
 | 
			
		||||
        if (!this.test(":")) {
 | 
			
		||||
            this.report("expected ':'");
 | 
			
		||||
            return { ok: false, pos };
 | 
			
		||||
        }
 | 
			
		||||
        this.step();
 | 
			
		||||
        const expr = this.parseExpr();
 | 
			
		||||
        return { ok: true, value: { ident, expr, pos } };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseIf(): Expr {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
        this.step();
 | 
			
		||||
@ -790,79 +586,25 @@ export class Parser {
 | 
			
		||||
            const subject = this.parsePrefix();
 | 
			
		||||
            return this.expr({ type: "unary", unaryType, subject }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        if (this.test("&")) {
 | 
			
		||||
            this.step();
 | 
			
		||||
            let type: "ref" | "ref_mut" = "ref";
 | 
			
		||||
            if (this.test("mut")) {
 | 
			
		||||
                this.step();
 | 
			
		||||
                type = "ref_mut";
 | 
			
		||||
            }
 | 
			
		||||
            const subject = this.parsePrefix();
 | 
			
		||||
            return this.expr({ type, subject }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        if (this.test("*")) {
 | 
			
		||||
            this.step();
 | 
			
		||||
            const subject = this.parsePrefix();
 | 
			
		||||
            return this.expr({ type: "deref", subject }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        return this.parsePostfix();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parsePostfix(): Expr {
 | 
			
		||||
        let subject = this.parseOperand();
 | 
			
		||||
        while (true) {
 | 
			
		||||
            const pos = this.pos();
 | 
			
		||||
            if (this.test(".")) {
 | 
			
		||||
                subject = this.parseFieldTail(subject);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.test("[")) {
 | 
			
		||||
                subject = this.parseIndexTail(subject);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.test("(")) {
 | 
			
		||||
                subject = this.parseCallTail(subject);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.test("::")) {
 | 
			
		||||
                subject = this.parsePathTail(subject);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.test("::<")) {
 | 
			
		||||
                subject = this.parseETypeArgsTail(subject);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        return subject;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseETypeArgsTail(subject: Expr): Expr {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
        const etypeArgs = this.parseDelimitedList(
 | 
			
		||||
            this.parseETypeArg,
 | 
			
		||||
            ">",
 | 
			
		||||
            ",",
 | 
			
		||||
        );
 | 
			
		||||
        return this.expr(
 | 
			
		||||
            { type: "etype_args", subject, etypeArgs },
 | 
			
		||||
            pos,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseFieldTail(subject: Expr): Expr {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
                this.step();
 | 
			
		||||
                if (!this.test("ident")) {
 | 
			
		||||
                    this.report("expected ident");
 | 
			
		||||
                    return this.expr({ type: "error" }, pos);
 | 
			
		||||
                }
 | 
			
		||||
        const ident = this.current().identValue!;
 | 
			
		||||
                const value = this.current().identValue!;
 | 
			
		||||
                this.step();
 | 
			
		||||
        return this.expr({ type: "field", subject, ident }, pos);
 | 
			
		||||
                subject = this.expr({ type: "field", subject, value }, pos);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
    private parseIndexTail(subject: Expr): Expr {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
            if (this.test("[")) {
 | 
			
		||||
                this.step();
 | 
			
		||||
                const value = this.parseExpr();
 | 
			
		||||
                if (!this.test("]")) {
 | 
			
		||||
@ -870,45 +612,41 @@ export class Parser {
 | 
			
		||||
                    return this.expr({ type: "error" }, pos);
 | 
			
		||||
                }
 | 
			
		||||
                this.step();
 | 
			
		||||
        return this.expr({ type: "index", subject, value }, pos);
 | 
			
		||||
                subject = this.expr({ type: "index", subject, value }, pos);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
    private parseCallTail(subject: Expr): Expr {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
        const args = this.parseDelimitedList(
 | 
			
		||||
            this.parseExprArg,
 | 
			
		||||
            ")",
 | 
			
		||||
            ",",
 | 
			
		||||
        );
 | 
			
		||||
        return this.expr({ type: "call", subject, args }, pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parsePathTail(subject: Expr): Expr {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
            if (this.test("(")) {
 | 
			
		||||
                this.step();
 | 
			
		||||
        if (!this.test("ident")) {
 | 
			
		||||
            this.report("expected ident");
 | 
			
		||||
                let args: Expr[] = [];
 | 
			
		||||
                if (!this.test(")")) {
 | 
			
		||||
                    args.push(this.parseExpr());
 | 
			
		||||
                    while (this.test(",")) {
 | 
			
		||||
                        this.step();
 | 
			
		||||
                        if (this.test(")")) {
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                        args.push(this.parseExpr());
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (!this.test(")")) {
 | 
			
		||||
                    this.report("expected ')'");
 | 
			
		||||
                    return this.expr({ type: "error" }, pos);
 | 
			
		||||
                }
 | 
			
		||||
        const ident = this.current().identValue!;
 | 
			
		||||
                this.step();
 | 
			
		||||
        return this.expr({ type: "path", subject, ident }, pos);
 | 
			
		||||
                subject = this.expr({ type: "call", subject, args }, pos);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
    private parseExprArg(): Res<Expr> {
 | 
			
		||||
        return { ok: true, value: this.parseExpr() };
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private parseETypeArg(): Res<EType> {
 | 
			
		||||
        return { ok: true, value: this.parseEType() };
 | 
			
		||||
        return subject;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseOperand(): Expr {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
        if (this.test("ident")) {
 | 
			
		||||
            const ident = this.current().identValue!;
 | 
			
		||||
            const value = this.current().identValue!;
 | 
			
		||||
            this.step();
 | 
			
		||||
            return this.expr({ type: "ident", ident }, pos);
 | 
			
		||||
            return this.expr({ type: "ident", value }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        if (this.test("int")) {
 | 
			
		||||
            const value = this.current().intValue!;
 | 
			
		||||
@ -942,12 +680,6 @@ export class Parser {
 | 
			
		||||
            this.step();
 | 
			
		||||
            return this.expr({ type: "group", expr }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        if (this.test("[")) {
 | 
			
		||||
            return this.parseArray();
 | 
			
		||||
        }
 | 
			
		||||
        if (this.test("struct")) {
 | 
			
		||||
            return this.parseStruct();
 | 
			
		||||
        }
 | 
			
		||||
        if (this.test("{")) {
 | 
			
		||||
            return this.parseBlock();
 | 
			
		||||
        }
 | 
			
		||||
@ -958,36 +690,27 @@ export class Parser {
 | 
			
		||||
            return this.parseLoop();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.report(`expected expr, got '${this.current().type}'`, pos);
 | 
			
		||||
        this.report("expected expr", pos);
 | 
			
		||||
        this.step();
 | 
			
		||||
        return this.expr({ type: "error" }, pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseEType(): EType {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
        if (["null", "int", "bool", "string"].includes(this.current().type)) {
 | 
			
		||||
            const type = this.current().type as
 | 
			
		||||
                | "null"
 | 
			
		||||
                | "int"
 | 
			
		||||
                | "bool"
 | 
			
		||||
                | "string";
 | 
			
		||||
            this.step();
 | 
			
		||||
            return this.etype({ type }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        if (this.test("ident")) {
 | 
			
		||||
            const ident = this.current().identValue!;
 | 
			
		||||
            this.step();
 | 
			
		||||
            return this.etype({ type: "ident", ident: ident }, pos);
 | 
			
		||||
            return this.etype({ type: "ident", value: ident }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        if (this.test("[")) {
 | 
			
		||||
            this.step();
 | 
			
		||||
            const subject = this.parseEType();
 | 
			
		||||
            const inner = this.parseEType();
 | 
			
		||||
            if (!this.test("]")) {
 | 
			
		||||
                this.report("expected ']'", pos);
 | 
			
		||||
                return this.etype({ type: "error" }, pos);
 | 
			
		||||
            }
 | 
			
		||||
            this.step();
 | 
			
		||||
            return this.etype({ type: "array", subject }, pos);
 | 
			
		||||
            return this.etype({ type: "array", inner }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        if (this.test("struct")) {
 | 
			
		||||
            this.step();
 | 
			
		||||
@ -998,26 +721,6 @@ export class Parser {
 | 
			
		||||
            const fields = this.parseETypeStructFields();
 | 
			
		||||
            return this.etype({ type: "struct", fields }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        if (this.test("&")) {
 | 
			
		||||
            this.step();
 | 
			
		||||
            let type: "ref" | "ref_mut" = "ref";
 | 
			
		||||
            if (this.test("mut")) {
 | 
			
		||||
                this.step();
 | 
			
		||||
                type = "ref_mut";
 | 
			
		||||
            }
 | 
			
		||||
            const subject = this.parseEType();
 | 
			
		||||
            return this.etype({ type, subject }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        if (this.test("*")) {
 | 
			
		||||
            this.step();
 | 
			
		||||
            let type: "ptr" | "ptr_mut" = "ptr";
 | 
			
		||||
            if (this.test("mut")) {
 | 
			
		||||
                this.step();
 | 
			
		||||
                type = "ptr_mut";
 | 
			
		||||
            }
 | 
			
		||||
            const subject = this.parseEType();
 | 
			
		||||
            return this.etype({ type, subject }, pos);
 | 
			
		||||
        }
 | 
			
		||||
        this.report("expected type");
 | 
			
		||||
        return this.etype({ type: "error" }, pos);
 | 
			
		||||
    }
 | 
			
		||||
@ -1074,6 +777,7 @@ export class Parser {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private report(msg: string, pos = this.pos()) {
 | 
			
		||||
        console.log(`Parser: ${msg} at ${pos.line}:${pos.col}`);
 | 
			
		||||
        this.reporter.reportError({
 | 
			
		||||
            msg,
 | 
			
		||||
            pos,
 | 
			
		||||
@ -1082,8 +786,8 @@ export class Parser {
 | 
			
		||||
        printStackTrace();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt {
 | 
			
		||||
        return this.astCreator.stmt(kind, pos, details);
 | 
			
		||||
    private stmt(kind: StmtKind, pos: Pos): Stmt {
 | 
			
		||||
        return this.astCreator.stmt(kind, pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private expr(kind: ExprKind, pos: Pos): Expr {
 | 
			
		||||
@ -1,14 +0,0 @@
 | 
			
		||||
import { File } from "../ast/ast.ts";
 | 
			
		||||
import { File as CtxFile } from "../ctx.ts";
 | 
			
		||||
import { todo } from "../util.ts";
 | 
			
		||||
 | 
			
		||||
export class Parser {
 | 
			
		||||
    public constructor(
 | 
			
		||||
        private file: CtxFile,
 | 
			
		||||
        private text: string,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public parse(): File {
 | 
			
		||||
        return todo();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										166
									
								
								compiler/resolver.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								compiler/resolver.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,166 @@
 | 
			
		||||
import { Expr, Stmt } from "./ast.ts";
 | 
			
		||||
import {
 | 
			
		||||
    AstVisitor,
 | 
			
		||||
    visitExpr,
 | 
			
		||||
    VisitRes,
 | 
			
		||||
    visitStmt,
 | 
			
		||||
    visitStmts,
 | 
			
		||||
} from "./ast_visitor.ts";
 | 
			
		||||
import { printStackTrace, Reporter } from "./info.ts";
 | 
			
		||||
import {
 | 
			
		||||
    FnSyms,
 | 
			
		||||
    GlobalSyms,
 | 
			
		||||
    LeafSyms,
 | 
			
		||||
    StaticSyms,
 | 
			
		||||
    Syms,
 | 
			
		||||
} from "./resolver_syms.ts";
 | 
			
		||||
import { Pos } from "./token.ts";
 | 
			
		||||
 | 
			
		||||
export class Resolver implements AstVisitor<[Syms]> {
 | 
			
		||||
    private root = new GlobalSyms();
 | 
			
		||||
 | 
			
		||||
    public constructor(private reporter: Reporter) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public resolve(stmts: Stmt[]): VisitRes {
 | 
			
		||||
        const scopeSyms = new StaticSyms(this.root);
 | 
			
		||||
        this.scoutFnStmts(stmts, scopeSyms);
 | 
			
		||||
        visitStmts(stmts, this, scopeSyms);
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitLetStmt(stmt: Stmt, syms: Syms): VisitRes {
 | 
			
		||||
        if (stmt.kind.type !== "let") {
 | 
			
		||||
            throw new Error("expected let statement");
 | 
			
		||||
        }
 | 
			
		||||
        visitExpr(stmt.kind.value, this, syms);
 | 
			
		||||
        const ident = stmt.kind.param.ident;
 | 
			
		||||
        if (syms.definedLocally(ident)) {
 | 
			
		||||
            this.reportAlreadyDefined(ident, stmt.pos, syms);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        syms.define(ident, {
 | 
			
		||||
            ident,
 | 
			
		||||
            type: "let",
 | 
			
		||||
            pos: stmt.kind.param.pos,
 | 
			
		||||
            stmt,
 | 
			
		||||
            param: stmt.kind.param,
 | 
			
		||||
        });
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private scoutFnStmts(stmts: Stmt[], syms: Syms) {
 | 
			
		||||
        for (const stmt of stmts) {
 | 
			
		||||
            if (stmt.kind.type !== "fn") {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (syms.definedLocally(stmt.kind.ident)) {
 | 
			
		||||
                this.reportAlreadyDefined(stmt.kind.ident, stmt.pos, syms);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            const ident = stmt.kind.ident;
 | 
			
		||||
            syms.define(ident, {
 | 
			
		||||
                ident: stmt.kind.ident,
 | 
			
		||||
                type: "fn",
 | 
			
		||||
                pos: stmt.pos,
 | 
			
		||||
                stmt,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitFnStmt(stmt: Stmt, syms: Syms): VisitRes {
 | 
			
		||||
        if (stmt.kind.type !== "fn") {
 | 
			
		||||
            throw new Error("expected fn statement");
 | 
			
		||||
        }
 | 
			
		||||
        const fnScopeSyms = new FnSyms(syms);
 | 
			
		||||
        for (const param of stmt.kind.params) {
 | 
			
		||||
            if (fnScopeSyms.definedLocally(param.ident)) {
 | 
			
		||||
                this.reportAlreadyDefined(param.ident, param.pos, syms);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            fnScopeSyms.define(param.ident, {
 | 
			
		||||
                ident: param.ident,
 | 
			
		||||
                type: "fn_param",
 | 
			
		||||
                pos: param.pos,
 | 
			
		||||
                param,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        visitExpr(stmt.kind.body, this, fnScopeSyms);
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitIdentExpr(expr: Expr, syms: Syms): VisitRes {
 | 
			
		||||
        if (expr.kind.type !== "ident") {
 | 
			
		||||
            throw new Error("expected ident");
 | 
			
		||||
        }
 | 
			
		||||
        const ident = expr.kind;
 | 
			
		||||
        const symResult = syms.get(ident.value);
 | 
			
		||||
        if (!symResult.ok) {
 | 
			
		||||
            this.reportUseOfUndefined(ident.value, expr.pos, syms);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const sym = symResult.sym;
 | 
			
		||||
        expr.kind = {
 | 
			
		||||
            type: "sym",
 | 
			
		||||
            ident: ident.value,
 | 
			
		||||
            sym,
 | 
			
		||||
        };
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitBlockExpr(expr: Expr, syms: Syms): VisitRes {
 | 
			
		||||
        if (expr.kind.type !== "block") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const childSyms = new LeafSyms(syms);
 | 
			
		||||
        this.scoutFnStmts(expr.kind.stmts, childSyms);
 | 
			
		||||
        visitStmts(expr.kind.stmts, this, childSyms);
 | 
			
		||||
        if (expr.kind.expr) {
 | 
			
		||||
            visitExpr(expr.kind.expr, this, childSyms);
 | 
			
		||||
        }
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    visitForExpr(expr: Expr, syms: Syms): VisitRes {
 | 
			
		||||
        if (expr.kind.type !== "for") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const childSyms = new LeafSyms(syms);
 | 
			
		||||
        if (expr.kind.decl) visitStmt(expr.kind.decl, this, syms);
 | 
			
		||||
        if (expr.kind.cond) visitExpr(expr.kind.cond, this, syms);
 | 
			
		||||
        if (expr.kind.incr) visitStmt(expr.kind.incr, this, syms);
 | 
			
		||||
        visitExpr(expr.kind.body, this, childSyms);
 | 
			
		||||
 | 
			
		||||
        return "stop";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private reportUseOfUndefined(ident: string, pos: Pos, _syms: Syms) {
 | 
			
		||||
        this.reporter.reportError({
 | 
			
		||||
            reporter: "Resolver",
 | 
			
		||||
            msg: `use of undefined symbol '${ident}'`,
 | 
			
		||||
            pos,
 | 
			
		||||
        });
 | 
			
		||||
        printStackTrace();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private reportAlreadyDefined(ident: string, pos: Pos, syms: Syms) {
 | 
			
		||||
        this.reporter.reportError({
 | 
			
		||||
            reporter: "Resolver",
 | 
			
		||||
            msg: `symbol already defined '${ident}'`,
 | 
			
		||||
            pos,
 | 
			
		||||
        });
 | 
			
		||||
        const prev = syms.get(ident);
 | 
			
		||||
        if (!prev.ok) {
 | 
			
		||||
            throw new Error("expected to be defined");
 | 
			
		||||
        }
 | 
			
		||||
        if (!prev.sym.pos) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this.reporter.addNote({
 | 
			
		||||
            reporter: "Resolver",
 | 
			
		||||
            msg: `previous definition of '${ident}'`,
 | 
			
		||||
            pos: prev.sym.pos,
 | 
			
		||||
        });
 | 
			
		||||
        printStackTrace();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										115
									
								
								compiler/resolver_syms.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								compiler/resolver_syms.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,115 @@
 | 
			
		||||
import { Sym } from "./ast.ts";
 | 
			
		||||
 | 
			
		||||
export type SymMap = { [ident: string]: Sym };
 | 
			
		||||
 | 
			
		||||
export interface Syms {
 | 
			
		||||
    define(ident: string, sym: Sym): void;
 | 
			
		||||
    definedLocally(ident: string): boolean;
 | 
			
		||||
    get(ident: string): { ok: true; sym: Sym } | { ok: false };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class GlobalSyms implements Syms {
 | 
			
		||||
    private syms: SymMap = {};
 | 
			
		||||
 | 
			
		||||
    public constructor() {}
 | 
			
		||||
 | 
			
		||||
    public define(ident: string, sym: Sym) {
 | 
			
		||||
        if (sym.type === "let") {
 | 
			
		||||
            this.define(ident, {
 | 
			
		||||
                ...sym,
 | 
			
		||||
                type: "let_static",
 | 
			
		||||
            });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this.syms[ident] = sym;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public definedLocally(ident: string): boolean {
 | 
			
		||||
        return ident in this.syms;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
 | 
			
		||||
        if (ident in this.syms) {
 | 
			
		||||
            return { ok: true, sym: this.syms[ident] };
 | 
			
		||||
        }
 | 
			
		||||
        return { ok: false };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class StaticSyms implements Syms {
 | 
			
		||||
    private syms: SymMap = {};
 | 
			
		||||
 | 
			
		||||
    public constructor(private parent: GlobalSyms) {}
 | 
			
		||||
 | 
			
		||||
    public define(ident: string, sym: Sym) {
 | 
			
		||||
        if (sym.type === "let") {
 | 
			
		||||
            this.define(ident, {
 | 
			
		||||
                ...sym,
 | 
			
		||||
                type: "let_static",
 | 
			
		||||
            });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this.syms[ident] = sym;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public definedLocally(ident: string): boolean {
 | 
			
		||||
        return ident in this.syms;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
 | 
			
		||||
        if (ident in this.syms) {
 | 
			
		||||
            return { ok: true, sym: this.syms[ident] };
 | 
			
		||||
        }
 | 
			
		||||
        return this.parent.get(ident);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class FnSyms implements Syms {
 | 
			
		||||
    private syms: SymMap = {};
 | 
			
		||||
 | 
			
		||||
    public constructor(private parent: Syms) {}
 | 
			
		||||
 | 
			
		||||
    public define(ident: string, sym: Sym) {
 | 
			
		||||
        if (sym.type === "let") {
 | 
			
		||||
            this.define(ident, {
 | 
			
		||||
                ...sym,
 | 
			
		||||
                type: "closure",
 | 
			
		||||
                inner: sym,
 | 
			
		||||
            });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this.syms[ident] = sym;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public definedLocally(ident: string): boolean {
 | 
			
		||||
        return ident in this.syms;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
 | 
			
		||||
        if (ident in this.syms) {
 | 
			
		||||
            return { ok: true, sym: this.syms[ident] };
 | 
			
		||||
        }
 | 
			
		||||
        return this.parent.get(ident);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class LeafSyms implements Syms {
 | 
			
		||||
    private syms: SymMap = {};
 | 
			
		||||
 | 
			
		||||
    public constructor(private parent: Syms) {}
 | 
			
		||||
 | 
			
		||||
    public define(ident: string, sym: Sym) {
 | 
			
		||||
        this.syms[ident] = sym;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public definedLocally(ident: string): boolean {
 | 
			
		||||
        return ident in this.syms;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
 | 
			
		||||
        if (ident in this.syms) {
 | 
			
		||||
            return { ok: true, sym: this.syms[ident] };
 | 
			
		||||
        }
 | 
			
		||||
        return this.parent.get(ident);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,64 +0,0 @@
 | 
			
		||||
import { Ctx } from "./ctx.ts";
 | 
			
		||||
import { prettyPrintReport } from "./diagnostics.ts";
 | 
			
		||||
 | 
			
		||||
const ctx = new Ctx();
 | 
			
		||||
 | 
			
		||||
const text = `
 | 
			
		||||
make an error here
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const biggerText = `
 | 
			
		||||
dont make error here
 | 
			
		||||
not here but start error here
 | 
			
		||||
and here
 | 
			
		||||
also here but not here
 | 
			
		||||
or here
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
const file = ctx.addFile(
 | 
			
		||||
    "root",
 | 
			
		||||
    "path/file.ts",
 | 
			
		||||
    "path/file.ts",
 | 
			
		||||
    undefined,
 | 
			
		||||
    text,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const biggerFile = ctx.addFile(
 | 
			
		||||
    "root",
 | 
			
		||||
    "path/file.ts",
 | 
			
		||||
    "path/file.ts",
 | 
			
		||||
    undefined,
 | 
			
		||||
    biggerText,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
prettyPrintReport(ctx, {
 | 
			
		||||
    file,
 | 
			
		||||
    msg: "an error",
 | 
			
		||||
    severity: "fatal",
 | 
			
		||||
    origin: "compiler",
 | 
			
		||||
    span: {
 | 
			
		||||
        begin: { idx: 5, line: 2, col: 5 },
 | 
			
		||||
        end: { idx: 13, line: 2, col: 13 },
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
prettyPrintReport(ctx, {
 | 
			
		||||
    file: biggerFile,
 | 
			
		||||
    msg: "an error",
 | 
			
		||||
    severity: "error",
 | 
			
		||||
    origin: "compiler",
 | 
			
		||||
    span: {
 | 
			
		||||
        begin: { idx: 6, line: 3, col: 14 },
 | 
			
		||||
        end: { idx: 13, line: 5, col: 13 },
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
prettyPrintReport(ctx, {
 | 
			
		||||
    file,
 | 
			
		||||
    msg: "an error",
 | 
			
		||||
    severity: "warning",
 | 
			
		||||
    origin: "compiler",
 | 
			
		||||
    pos: {
 | 
			
		||||
        idx: 6, line: 2, col: 8 
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
@ -1,9 +0,0 @@
 | 
			
		||||
export function todo<T>(msg?: string): T {
 | 
			
		||||
    class NotImplemented extends Error {}
 | 
			
		||||
    throw new NotImplemented(msg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function exhausted(_: never) {
 | 
			
		||||
    class Unexhausted extends Error {}
 | 
			
		||||
    throw new Unexhausted();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										62
									
								
								compiler/vtype.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								compiler/vtype.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,62 @@
 | 
			
		||||
export type VType =
 | 
			
		||||
    | { type: "error" }
 | 
			
		||||
    | { type: "unknown" }
 | 
			
		||||
    | { type: "null" }
 | 
			
		||||
    | { type: "int" }
 | 
			
		||||
    | { type: "string" }
 | 
			
		||||
    | { type: "bool" }
 | 
			
		||||
    | { type: "array"; inner: VType }
 | 
			
		||||
    | { type: "struct"; fields: VTypeParam[] }
 | 
			
		||||
    | { type: "fn"; params: VTypeParam[]; returnType: VType };
 | 
			
		||||
 | 
			
		||||
export type VTypeParam = {
 | 
			
		||||
    ident: string;
 | 
			
		||||
    vtype: VType;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function vtypesEqual(a: VType, b: VType): boolean {
 | 
			
		||||
    if (a.type !== b.type) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (
 | 
			
		||||
        ["error", "unknown", "null", "int", "string", "bool"]
 | 
			
		||||
            .includes(a.type)
 | 
			
		||||
    ) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    if (a.type === "array" && b.type === "array") {
 | 
			
		||||
        return vtypesEqual(a.inner, b.inner);
 | 
			
		||||
    }
 | 
			
		||||
    if (a.type === "fn" && b.type === "fn") {
 | 
			
		||||
        if (a.params.length !== b.params.length) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        for (let i = 0; i < a.params.length; ++i) {
 | 
			
		||||
            if (!vtypesEqual(a.params[i].vtype, b.params[i].vtype)) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return vtypesEqual(a.returnType, b.returnType);
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function vtypeToString(vtype: VType): string {
 | 
			
		||||
    if (
 | 
			
		||||
        ["error", "unknown", "null", "int", "string", "bool"]
 | 
			
		||||
            .includes(vtype.type)
 | 
			
		||||
    ) {
 | 
			
		||||
        return vtype.type;
 | 
			
		||||
    }
 | 
			
		||||
    if (vtype.type === "array") {
 | 
			
		||||
        return `[${vtypeToString(vtype.inner)}]`;
 | 
			
		||||
    }
 | 
			
		||||
    if (vtype.type === "fn") {
 | 
			
		||||
        const paramString = vtype.params.map((param) =>
 | 
			
		||||
            `${param.ident}: ${vtypeToString(param.vtype)}`
 | 
			
		||||
        )
 | 
			
		||||
            .join(", ");
 | 
			
		||||
        return `fn (${paramString}) -> ${vtypeToString(vtype.returnType)}`;
 | 
			
		||||
    }
 | 
			
		||||
    throw new Error(`unhandled vtype '${vtype.type}'`);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								dev-env/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								dev-env/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
			
		||||
FROM archlinux
 | 
			
		||||
RUN pacman -Syu git base-devel deno --noconfirm
 | 
			
		||||
WORKDIR /workspace
 | 
			
		||||
ENTRYPOINT ["/bin/bash"]
 | 
			
		||||
							
								
								
									
										3
									
								
								dev-env/run.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								dev-env/run.sh
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
docker build -t slige-dev-env dev-env
 | 
			
		||||
docker run --name dev-env --rm -it --mount type=bind,source="$(pwd)"/,target=/workspace slige-dev-env
 | 
			
		||||
@ -7,8 +7,7 @@ if exists("b:current_syntax")
 | 
			
		||||
  finish
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
syn keyword Keyword break return let fn loop if else struct import or and not while for in mod pub
 | 
			
		||||
syn keyword Keyword break return let fn loop if else struct import or and not while for in
 | 
			
		||||
syn keyword Special null
 | 
			
		||||
syn keyword Type int string bool
 | 
			
		||||
syn keyword Boolean true false
 | 
			
		||||
@ -38,9 +37,6 @@ syn match Number '0[0-7]\+'
 | 
			
		||||
syn match Number '0x[0-9a-fA-F]\+'
 | 
			
		||||
syn match Number '0b[01]\+'
 | 
			
		||||
 | 
			
		||||
syn match Character "'[^\\]'"
 | 
			
		||||
syn match Character "'\\.'"
 | 
			
		||||
 | 
			
		||||
syn region String   start=+"+  skip=+\\"+  end=+"+
 | 
			
		||||
 | 
			
		||||
syn keyword Todo contained TODO FIXME XXX NOTE
 | 
			
		||||
@ -48,23 +44,11 @@ syn match Comment "//.*$" contains=Todo
 | 
			
		||||
 | 
			
		||||
syn region Comment start=+/\*+ end=+\*/+ contains=Todo
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
syn match Identifier '[a-z_]\w*'
 | 
			
		||||
syn match Type '[A-Z]\w*'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
syn match Function '[a-zA-Z_]\w*\ze\s\{-}(.\{-})'
 | 
			
		||||
 | 
			
		||||
syn match sligePath '[a-zA-Z_]\w*\ze\s\{-}::'
 | 
			
		||||
syn match Function '[a-zA-Z_]\w*\ze\s\{-}::<.\{-}>'
 | 
			
		||||
 | 
			
		||||
syn match Function ' \zs[a-zA-Z_]\w*\ze\s\{-}<.\{-}>\s\{-}(.\{-})'
 | 
			
		||||
syn match Function '[a-zA-Z_]\w*\ze('
 | 
			
		||||
 | 
			
		||||
syn region sligeBlock start="{" end="}" transparent fold
 | 
			
		||||
 | 
			
		||||
syn region sligeAnno start="#!\?\[" end="]" contains=Identifier,Type
 | 
			
		||||
 | 
			
		||||
hi def link sligeAnno PreProc
 | 
			
		||||
hi def link sligePath Include
 | 
			
		||||
 | 
			
		||||
let b:current_syntax = "slige"
 | 
			
		||||
 | 
			
		||||
@ -53,17 +53,6 @@
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        "chars": {
 | 
			
		||||
            "name": "string.quoted.double.slige",
 | 
			
		||||
            "begin": "'",
 | 
			
		||||
            "end": "'",
 | 
			
		||||
            "patterns": [
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "constant.character.escape.slige",
 | 
			
		||||
                    "match": "\\\\."
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        "numbers": {
 | 
			
		||||
            "patterns": [
 | 
			
		||||
                {
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,4 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#[builtin(Print)]
 | 
			
		||||
fn print(msg: string) {
 | 
			
		||||
fn print(msg: string) #[builtin(print)] {
 | 
			
		||||
    "hello" + 0    
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,164 +0,0 @@
 | 
			
		||||
mod std;
 | 
			
		||||
 | 
			
		||||
type_alias Tok: struct {
 | 
			
		||||
    type: string,
 | 
			
		||||
    value: string,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
fn lex(text: string) -> [Tok] {
 | 
			
		||||
    let i = 0;
 | 
			
		||||
    let len = std::string_length(text);
 | 
			
		||||
    
 | 
			
		||||
    let toks = std::array_new::<Tok>();
 | 
			
		||||
 | 
			
		||||
    while i < len {
 | 
			
		||||
        if std::string_contains(" \t\n", text[i]) {
 | 
			
		||||
            i += 1;
 | 
			
		||||
            if i >= len {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if text[i] >= '1' and text[i] <= '9' {
 | 
			
		||||
            let value = std::ctos(text[i]);
 | 
			
		||||
            i += 1;
 | 
			
		||||
            while i < len and text[i] >= '0' and text[i] <= '9' {
 | 
			
		||||
                value = std::string_push_char(value, text[i]);
 | 
			
		||||
                i += 1;
 | 
			
		||||
            }
 | 
			
		||||
            let tok = struct { type: "int", value: value };
 | 
			
		||||
            std::array_push(toks, tok);
 | 
			
		||||
        } else if text[i] == '0' {
 | 
			
		||||
            i += 1;
 | 
			
		||||
            let tok = struct { type: "int", value: "0" };
 | 
			
		||||
            std::array_push(toks, tok);
 | 
			
		||||
        } else if text[i] == '+' {
 | 
			
		||||
            i += 1;
 | 
			
		||||
            let tok = struct { type: "+", value: "+" };
 | 
			
		||||
            std::array_push(toks, tok);
 | 
			
		||||
        } else if text[i] == '-' {
 | 
			
		||||
            i += 1;
 | 
			
		||||
            let tok = struct { type: "-", value: "-" };
 | 
			
		||||
            std::array_push(toks, tok);
 | 
			
		||||
        } else if text[i] == '*' {
 | 
			
		||||
            i += 1;
 | 
			
		||||
            let tok = struct { type: "*", value: "*" };
 | 
			
		||||
            std::array_push(toks, tok);
 | 
			
		||||
        } else if text[i] == '/' {
 | 
			
		||||
            i += 1;
 | 
			
		||||
            let tok = struct { type: "/", value: "/" };
 | 
			
		||||
            std::array_push(toks, tok);
 | 
			
		||||
        } else if text[i] == '(' {
 | 
			
		||||
            i += 1;
 | 
			
		||||
            let tok = struct { type: "(", value: "(" };
 | 
			
		||||
            std::array_push(toks, tok);
 | 
			
		||||
        } else if text[i] == ')' {
 | 
			
		||||
            i += 1;
 | 
			
		||||
            let tok = struct { type: ")", value: ")" };
 | 
			
		||||
            std::array_push(toks, tok);
 | 
			
		||||
        } else {
 | 
			
		||||
            std::println("error: illegal character '" + std::ctos(text[i]) + "'");
 | 
			
		||||
            i += 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    toks
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type_alias Calc: struct {
 | 
			
		||||
    toks: [Tok],
 | 
			
		||||
    toks_len: int,
 | 
			
		||||
    i: int,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
fn calc_new(text: string) -> Calc {
 | 
			
		||||
    let toks = lex(text);
 | 
			
		||||
    let toks_len = std::array_length(toks);
 | 
			
		||||
    struct { toks: toks, toks_len: toks_len, i: 0 }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn calc_expr(self: Calc) -> int {
 | 
			
		||||
    calc_add_sub(self)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn calc_add_sub(self: Calc) -> int {
 | 
			
		||||
    let left = calc_mul_div(self);
 | 
			
		||||
    loop {
 | 
			
		||||
        if self.toks[self.i].type == "+" {
 | 
			
		||||
            self.i += 1;
 | 
			
		||||
            let right = calc_mul_div(self);
 | 
			
		||||
            left = left + right;
 | 
			
		||||
        } else if self.toks[self.i].type == "-" {
 | 
			
		||||
            self.i += 1;
 | 
			
		||||
            let right = calc_mul_div(self);
 | 
			
		||||
            left = left - right;
 | 
			
		||||
        } else {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    left
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn calc_mul_div(self: Calc) -> int {
 | 
			
		||||
    let left = calc_unary(self);
 | 
			
		||||
    loop {
 | 
			
		||||
        if self.toks[self.i].type == "*" {
 | 
			
		||||
            self.i += 1;
 | 
			
		||||
            let right = calc_unary(self);
 | 
			
		||||
            left = left * right;
 | 
			
		||||
        } else if self.toks[self.i].type == "/" {
 | 
			
		||||
            self.i += 1;
 | 
			
		||||
            let right = calc_unary(self);
 | 
			
		||||
            left = left / right;
 | 
			
		||||
        } else {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    left
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn calc_unary(self: Calc) -> int {
 | 
			
		||||
    if self.toks[self.i].type == "-" {
 | 
			
		||||
        self.i += 1;
 | 
			
		||||
        let subject = calc_unary(self);
 | 
			
		||||
        -subject
 | 
			
		||||
    } else {
 | 
			
		||||
        calc_operand(self)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn calc_operand(self: Calc) -> int {
 | 
			
		||||
    if self.i >= self.toks_len {
 | 
			
		||||
        std::println("error: expected expr");
 | 
			
		||||
        0
 | 
			
		||||
    } else if self.toks[self.i].type == "int" {
 | 
			
		||||
        let val = std::stoi(self.toks[self.i].value);
 | 
			
		||||
        self.i += 1;
 | 
			
		||||
        val
 | 
			
		||||
    } else if self.toks[self.i].type == "(" {
 | 
			
		||||
        self.i += 1;
 | 
			
		||||
        let val = calc_expr(self);
 | 
			
		||||
        if self.i >= self.toks_len or self.toks[self.i].type != ")" {
 | 
			
		||||
            std::println("error: missing ')'");
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
        self.i += 1;
 | 
			
		||||
        val
 | 
			
		||||
    } else {
 | 
			
		||||
        std::println("error: expected expr");
 | 
			
		||||
        self.i += 1;
 | 
			
		||||
        0
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    loop {
 | 
			
		||||
        let line = std::input("> ");
 | 
			
		||||
        if line == "exit" {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        let calc = calc_new(line);
 | 
			
		||||
        let val = calc_expr(calc);
 | 
			
		||||
        std::println(line);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -4,14 +4,7 @@ fn add(a: int, b: int) -> int {
 | 
			
		||||
 | 
			
		||||
fn main() -> int {
 | 
			
		||||
    let result = 0;
 | 
			
		||||
 | 
			
		||||
    let a  = 0;
 | 
			
		||||
    let b  = a;
 | 
			
		||||
    let c  = b;
 | 
			
		||||
    let d  = c;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    let i = c;
 | 
			
		||||
    let i = 0;
 | 
			
		||||
    loop {
 | 
			
		||||
        if i >= 10 {
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
@ -1,21 +0,0 @@
 | 
			
		||||
mod std;
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    let strings = std::array_new::<string>();
 | 
			
		||||
    std::array_push(strings, "hello");
 | 
			
		||||
    std::array_push(strings, "world");
 | 
			
		||||
 | 
			
		||||
    let ints = std::array_new::<int>();
 | 
			
		||||
    std::array_push(ints, 1);
 | 
			
		||||
    std::array_push(ints, 2);
 | 
			
		||||
 | 
			
		||||
    for v in strings {
 | 
			
		||||
        std::println(v)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for v in ints {
 | 
			
		||||
        std::println(std::itos(v))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -56,50 +56,12 @@ fn input(prompt: string) -> string {
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
fn min(a: int, b: int) -> int {
 | 
			
		||||
    if b < a { b } else { a }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn max(a: int, b: int) -> int {
 | 
			
		||||
    if a < b { b } else { b }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn sqrt(n: int) -> int {
 | 
			
		||||
    let low = min(1, n);
 | 
			
		||||
    let high = max(1, n);
 | 
			
		||||
    let mid = 0;
 | 
			
		||||
 | 
			
		||||
    while 100 * low * low < n {
 | 
			
		||||
        low = low * 10;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    while (high * high) / 100 > n {
 | 
			
		||||
        high = high / 10;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < 100; i += 1) {
 | 
			
		||||
        mid = (low + high) / 2;
 | 
			
		||||
        if mid * mid == n {
 | 
			
		||||
            return mid;
 | 
			
		||||
        }
 | 
			
		||||
        if mid * mid > n {
 | 
			
		||||
            high = mid;
 | 
			
		||||
        } else {
 | 
			
		||||
            low = mid;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    mid
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn is_prime(n: int) -> bool {
 | 
			
		||||
    if n == 0{
 | 
			
		||||
    if n == 1 or n == 0{
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    if n == 1 {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    let n_root = sqrt(n);
 | 
			
		||||
    for (let i = 2; i < n_root; i += 1) {
 | 
			
		||||
 | 
			
		||||
    for (let i = 2; i < n; i += 1) {
 | 
			
		||||
        if remainder(n, i) == 0 {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
@ -108,7 +70,7 @@ fn is_prime(n: int) -> bool {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    for (let i = 1; i <= 10000; i += 1) {
 | 
			
		||||
    for (let i = 1; i < 10000; i += 1) {
 | 
			
		||||
        if is_prime(i) {
 | 
			
		||||
            print(int_to_string(i) + " ");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -1,7 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    let a = 5;
 | 
			
		||||
    let b: &int = &a;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,56 @@
 | 
			
		||||
 | 
			
		||||
fn string_push_char(str: string, value: int) -> string #[builtin(StringPushChar)] {}
 | 
			
		||||
fn string_char_at(str: string, index: int) -> int #[builtin(StringCharAt)] {}
 | 
			
		||||
fn string_length(str: string) -> int #[builtin(StringLength)] {}
 | 
			
		||||
 | 
			
		||||
fn string_array_new() -> [string] #[builtin(ArrayNew)] {}
 | 
			
		||||
fn string_array_push(array: [string], value: string) #[builtin(ArrayPush)] {}
 | 
			
		||||
fn string_array_length(array: [string]) -> int #[builtin(ArrayLength)] {}
 | 
			
		||||
fn string_array_at(array: [string], index: int) -> string #[builtin(ArrayAt)] {}
 | 
			
		||||
 | 
			
		||||
fn int_array_new() -> [int] #[builtin(ArrayNew)] {}
 | 
			
		||||
fn int_array_push(array: [int], value: int) #[builtin(ArrayPush)] {}
 | 
			
		||||
fn int_array_length(array: [int]) -> int #[builtin(ArrayLength)] {}
 | 
			
		||||
fn int_array_at(array: [int], index: int) -> int #[builtin(ArrayAt)] {}
 | 
			
		||||
 | 
			
		||||
fn file_open(filename: string, mode: string) -> int #[builtin(FileOpen)] {}
 | 
			
		||||
fn file_close(file: int) #[builtin(FileClose)] {}
 | 
			
		||||
fn file_write_string(file: int, content: string) -> int #[builtin(FileWriteString)] {}
 | 
			
		||||
fn file_read_char(file: int) -> int #[builtin(FileReadChar)] {}
 | 
			
		||||
fn file_read_to_string(file: int) -> string #[builtin(FileReadToString)] {}
 | 
			
		||||
fn file_flush(file: int) #[builtin(FileFlush)] {}
 | 
			
		||||
fn file_eof(file: int) -> bool #[builtin(FileEof)] {}
 | 
			
		||||
 | 
			
		||||
fn stdin() -> int { 0 }
 | 
			
		||||
fn stdout() -> int { 1 }
 | 
			
		||||
fn stderr() -> int { 2 }
 | 
			
		||||
 | 
			
		||||
fn file_read_line(file: int) -> string {
 | 
			
		||||
    let line = "";
 | 
			
		||||
    loop {
 | 
			
		||||
        if file_eof(file) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        let ch = file_read_char(file);
 | 
			
		||||
        if ch == "\n"[0] {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        line = string_push_char(line, ch);
 | 
			
		||||
    }
 | 
			
		||||
    line
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn print(msg: string) #[builtin(Print)] {}
 | 
			
		||||
fn println(msg: string) { print(msg + "\n") }
 | 
			
		||||
 | 
			
		||||
fn input(prompt: string) -> string {
 | 
			
		||||
    print("> ");
 | 
			
		||||
    file_flush(stdout());
 | 
			
		||||
    file_read_line(stdin())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    let i = 0;
 | 
			
		||||
    while i < 3 {
 | 
			
		||||
@ -10,34 +63,18 @@ fn main() {
 | 
			
		||||
    for char in chars {
 | 
			
		||||
        println(string_push_char("", char));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let values = array_new::<int>();
 | 
			
		||||
    array_push(values, 10);
 | 
			
		||||
    array_push(values, 20);
 | 
			
		||||
    array_push(values, 30);
 | 
			
		||||
 | 
			
		||||
    let pairs = array_new::<[int]>();
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < array_length(values); i += 1) {
 | 
			
		||||
        let pair = array_new::<int>();
 | 
			
		||||
        array_push(pair, i);
 | 
			
		||||
        array_push(pair, values[i]);
 | 
			
		||||
        array_push(pairs, pair);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for pair in pairs {
 | 
			
		||||
        println("values[" + itos(pair[0]) + "] = " + itos(pair[1]));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn string_to_array(value: string) -> [int] {
 | 
			
		||||
    let result = array_new::<int>();
 | 
			
		||||
    let result = int_array_new();
 | 
			
		||||
    let length = string_length(value);
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < length; i += 1) {
 | 
			
		||||
        array_push(result, value[i]);
 | 
			
		||||
        int_array_push(result, value[i]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,22 +0,0 @@
 | 
			
		||||
// mod std;
 | 
			
		||||
 | 
			
		||||
fn black_box(v: int) {  }
 | 
			
		||||
 | 
			
		||||
fn add(a: int, b: int) -> int {
 | 
			
		||||
    let s = a + b;
 | 
			
		||||
    if false {}
 | 
			
		||||
    s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    let a = 5;
 | 
			
		||||
 | 
			
		||||
    loop {
 | 
			
		||||
        a = 3;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let b = a;
 | 
			
		||||
    let c = b;
 | 
			
		||||
 | 
			
		||||
    black_box(b);
 | 
			
		||||
}
 | 
			
		||||
@ -1,6 +0,0 @@
 | 
			
		||||
// mod std;
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    let a = 5;
 | 
			
		||||
    let b = a;
 | 
			
		||||
}
 | 
			
		||||
@ -1,7 +1,6 @@
 | 
			
		||||
#include "alloc.hpp"
 | 
			
		||||
#include <format>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
using namespace sliger::heap;
 | 
			
		||||
 | 
			
		||||
@ -15,18 +14,3 @@ auto Array::at(int32_t index) & -> Value&
 | 
			
		||||
    }
 | 
			
		||||
    return values.at(static_cast<size_t>(index));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
auto Struct::at(const std::string& field) & -> Value&
 | 
			
		||||
{
 | 
			
		||||
    if (this->fields.find(field) == this->fields.end()) {
 | 
			
		||||
        std::cout << std::format(
 | 
			
		||||
            "field name not in struct, got: \"{}\"\n", field);
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
    return this->fields.at(field);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Struct::assign(const std::string& field, Value&& value)
 | 
			
		||||
{
 | 
			
		||||
    this->fields.insert_or_assign(field, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -19,9 +19,6 @@ struct Array {
 | 
			
		||||
 | 
			
		||||
struct Struct {
 | 
			
		||||
    std::unordered_map<std::string, Value> fields;
 | 
			
		||||
 | 
			
		||||
    auto at(const std::string&) & -> Value&;
 | 
			
		||||
    void assign(const std::string&, Value&& value);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class AllocType {
 | 
			
		||||
@ -205,7 +202,7 @@ private:
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    size_t max_size = 512;
 | 
			
		||||
    size_t max_size = 4;
 | 
			
		||||
 | 
			
		||||
    std::vector<AllocItem> heap_1;
 | 
			
		||||
    std::vector<AllocItem> heap_2;
 | 
			
		||||
 | 
			
		||||
@ -41,8 +41,7 @@ enum class Op : uint32_t {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class Builtin : uint32_t {
 | 
			
		||||
    Exit = 0x00,
 | 
			
		||||
    IntToString = 0x01,
 | 
			
		||||
    IntToString = 0x00,
 | 
			
		||||
    StringConcat = 0x10,
 | 
			
		||||
    StringEqual = 0x11,
 | 
			
		||||
    StringCharAt = 0x12,
 | 
			
		||||
@ -54,9 +53,7 @@ enum class Builtin : uint32_t {
 | 
			
		||||
    ArrayPush = 0x22,
 | 
			
		||||
    ArrayAt = 0x23,
 | 
			
		||||
    ArrayLength = 0x24,
 | 
			
		||||
    StructNew = 0x30,
 | 
			
		||||
    StructSet = 0x31,
 | 
			
		||||
    StructAt = 0x32,
 | 
			
		||||
    StructSet = 0x30,
 | 
			
		||||
    Print = 0x40,
 | 
			
		||||
    FileOpen = 0x41,
 | 
			
		||||
    FileClose = 0x42,
 | 
			
		||||
 | 
			
		||||
@ -289,11 +289,6 @@ void VM::run_instruction()
 | 
			
		||||
            this->current_pos = { index, line, col };
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        default:
 | 
			
		||||
            std::cerr << std::format("unrecognized instruction '{}', pc = {}",
 | 
			
		||||
                std::to_underlying(op), this->pc);
 | 
			
		||||
            std::exit(1);
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
    this->instruction_counter += 1;
 | 
			
		||||
}
 | 
			
		||||
@ -305,15 +300,9 @@ void VM::run_builtin(Builtin builtin_id)
 | 
			
		||||
            maybe_builtin_to_string(static_cast<uint32_t>(builtin_id)));
 | 
			
		||||
    }
 | 
			
		||||
    switch (builtin_id) {
 | 
			
		||||
        case Builtin::Exit: {
 | 
			
		||||
            assert_stack_has(1);
 | 
			
		||||
            auto status_code = stack_pop().as_int().value;
 | 
			
		||||
            std::exit(status_code);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case Builtin::IntToString: {
 | 
			
		||||
            assert_stack_has(1);
 | 
			
		||||
            auto number = stack_pop().as_int().value;
 | 
			
		||||
            auto number = static_cast<int32_t>(stack_pop().as_int().value);
 | 
			
		||||
            auto str = std::to_string(number);
 | 
			
		||||
            stack_push(String(str));
 | 
			
		||||
            break;
 | 
			
		||||
@ -336,11 +325,12 @@ void VM::run_builtin(Builtin builtin_id)
 | 
			
		||||
            run_array_builtin(builtin_id);
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case Builtin::StructNew:
 | 
			
		||||
        case Builtin::StructSet:
 | 
			
		||||
        case Builtin::StructAt:
 | 
			
		||||
            run_struct_builtin(builtin_id);
 | 
			
		||||
        case Builtin::StructSet: {
 | 
			
		||||
            assert_stack_has(2);
 | 
			
		||||
            std::cerr << std::format("not implemented\n");
 | 
			
		||||
            std::exit(1);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        case Builtin::Print:
 | 
			
		||||
        case Builtin::FileOpen:
 | 
			
		||||
@ -411,7 +401,6 @@ void VM::run_string_builtin(Builtin builtin_id)
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void VM::run_array_builtin(Builtin builtin_id)
 | 
			
		||||
{
 | 
			
		||||
    switch (builtin_id) {
 | 
			
		||||
@ -461,40 +450,6 @@ void VM::run_array_builtin(Builtin builtin_id)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void VM::run_struct_builtin(Builtin builtin_id)
 | 
			
		||||
{
 | 
			
		||||
    switch (builtin_id) {
 | 
			
		||||
        case Builtin::StructNew: {
 | 
			
		||||
            auto alloc_res = this->heap.alloc<heap::AllocType::Struct>();
 | 
			
		||||
            stack_push(Ptr(alloc_res.val()));
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case Builtin::StructSet: {
 | 
			
		||||
            assert_stack_has(2);
 | 
			
		||||
            auto field = stack_pop().as_string().value;
 | 
			
		||||
            auto struct_ptr = stack_pop().as_ptr().value;
 | 
			
		||||
            auto value = stack_pop();
 | 
			
		||||
 | 
			
		||||
            this->heap.at(struct_ptr)
 | 
			
		||||
                .val()
 | 
			
		||||
                ->as_struct()
 | 
			
		||||
                .assign(field, std::move(value));
 | 
			
		||||
            stack_push(Null());
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case Builtin::StructAt: {
 | 
			
		||||
            assert_stack_has(2);
 | 
			
		||||
            auto field = stack_pop().as_string().value;
 | 
			
		||||
            auto struct_ptr = stack_pop().as_ptr().value;
 | 
			
		||||
 | 
			
		||||
            stack_push(this->heap.at(struct_ptr).val()->as_struct().at(field));
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        default:
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void VM::run_file_builtin(Builtin builtin_id)
 | 
			
		||||
{
 | 
			
		||||
    switch (builtin_id) {
 | 
			
		||||
 | 
			
		||||
@ -199,7 +199,6 @@ private:
 | 
			
		||||
    void run_builtin(Builtin builtin_id);
 | 
			
		||||
    void run_string_builtin(Builtin builtin_id);
 | 
			
		||||
    void run_array_builtin(Builtin builtin_id);
 | 
			
		||||
    void run_struct_builtin(Builtin builtin_id);
 | 
			
		||||
    void run_file_builtin(Builtin builtin_id);
 | 
			
		||||
 | 
			
		||||
    inline void step() { this->pc += 1; }
 | 
			
		||||
 | 
			
		||||
@ -3,17 +3,11 @@
 | 
			
		||||
set -e
 | 
			
		||||
 | 
			
		||||
echo Text:
 | 
			
		||||
 | 
			
		||||
if command -v pygmentize 2>&1 >/dev/null
 | 
			
		||||
then
 | 
			
		||||
    pygmentize -l rust -Ostyle="gruvbox-dark",linenos=1 $1
 | 
			
		||||
else
 | 
			
		||||
cat $1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
echo Compiling $1...
 | 
			
		||||
 | 
			
		||||
deno run --allow-read --allow-write --check compiler/main.ts $1
 | 
			
		||||
deno run --allow-read --allow-write compiler/main.ts $1
 | 
			
		||||
 | 
			
		||||
echo Running out.slgbc...
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										184
									
								
								std/lib.slg
									
									
									
									
									
								
							
							
						
						
									
										184
									
								
								std/lib.slg
									
									
									
									
									
								
							@ -1,184 +0,0 @@
 | 
			
		||||
 | 
			
		||||
// stdlib.slg
 | 
			
		||||
 | 
			
		||||
#[builtin(Exit)]
 | 
			
		||||
pub fn exit(status_code: int) {}
 | 
			
		||||
 | 
			
		||||
#[builtin(Print)]
 | 
			
		||||
pub fn print(msg: string) {}
 | 
			
		||||
pub fn println(msg: string) { print(msg + "\n") }
 | 
			
		||||
 | 
			
		||||
#[builtin(IntToString)]
 | 
			
		||||
pub fn int_to_string(number: int) -> string {}
 | 
			
		||||
 | 
			
		||||
#[builtin(StringPushChar)]
 | 
			
		||||
pub fn string_push_char(str: string, value: int) -> string {}
 | 
			
		||||
#[builtin(StringCharAt)]
 | 
			
		||||
pub fn string_char_at(str: string, index: int) -> int {}
 | 
			
		||||
#[builtin(StringLength)]
 | 
			
		||||
pub fn string_length(str: string) -> int {}
 | 
			
		||||
#[builtin(StringToInt)]
 | 
			
		||||
pub fn string_to_int(str: string) -> int {}
 | 
			
		||||
 | 
			
		||||
#[builtin(ArrayNew)]
 | 
			
		||||
pub fn array_new<T>() -> [T] {}
 | 
			
		||||
#[builtin(ArrayPush)]
 | 
			
		||||
pub fn array_push<T>(array: [T], value: T) {}
 | 
			
		||||
#[builtin(ArrayLength)]
 | 
			
		||||
pub fn array_length<T>(array: [T]) -> int {}
 | 
			
		||||
#[builtin(ArrayAt)]
 | 
			
		||||
pub fn array_at<T>(array: [T], index: int) -> T {}
 | 
			
		||||
 | 
			
		||||
#[builtin(StructNew)]
 | 
			
		||||
pub fn struct_new<S>() -> S {}
 | 
			
		||||
#[builtin(StructSet)]
 | 
			
		||||
pub fn struct_set<S, T>(subject: S, value: T) {}
 | 
			
		||||
 | 
			
		||||
#[builtin(FileOpen)]
 | 
			
		||||
pub fn file_open(filename: string, mode: string) -> int {}
 | 
			
		||||
#[builtin(FileClose)]
 | 
			
		||||
pub fn file_close(file: int) {}
 | 
			
		||||
#[builtin(FileWriteString)]
 | 
			
		||||
pub fn file_write_string(file: int, content: string) -> int {}
 | 
			
		||||
#[builtin(FileReadChar)]
 | 
			
		||||
pub fn file_read_char(file: int) -> int {}
 | 
			
		||||
#[builtin(FileReadToString)]
 | 
			
		||||
pub fn file_read_to_string(file: int) -> string {}
 | 
			
		||||
#[builtin(FileFlush)]
 | 
			
		||||
pub fn file_flush(file: int) {}
 | 
			
		||||
#[builtin(FileEof)]
 | 
			
		||||
pub fn file_eof(file: int) -> bool {}
 | 
			
		||||
 | 
			
		||||
#[builtin(IntToString)]
 | 
			
		||||
pub fn itos(number: int) -> string {}
 | 
			
		||||
#[builtin(StringToInt)]
 | 
			
		||||
pub fn stoi(str: string) -> int {}
 | 
			
		||||
 | 
			
		||||
pub fn ctos(ch: int) -> string { string_push_char("", ch) }
 | 
			
		||||
 | 
			
		||||
pub fn stdin() -> int { 0 }
 | 
			
		||||
pub fn stdout() -> int { 1 }
 | 
			
		||||
pub fn stderr() -> int { 2 }
 | 
			
		||||
 | 
			
		||||
pub fn file_read_line(file: int) -> string {
 | 
			
		||||
    let line = "";
 | 
			
		||||
    loop {
 | 
			
		||||
        if file_eof(file) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        let ch = file_read_char(file);
 | 
			
		||||
        if ch == "\n"[0] {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        line = string_push_char(line, ch);
 | 
			
		||||
    }
 | 
			
		||||
    line
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn read_text_file(filename: string) -> string {
 | 
			
		||||
    let file = file_open(filename, "r");
 | 
			
		||||
    let text = file_read_to_string(file);
 | 
			
		||||
    file_close(file);
 | 
			
		||||
    text
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn input(prompt: string) -> string {
 | 
			
		||||
    print(prompt);
 | 
			
		||||
    file_flush(stdout());
 | 
			
		||||
    file_read_line(stdin())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn string_abs(number: int) -> int {
 | 
			
		||||
    let result = number;
 | 
			
		||||
    if number < 0 {
 | 
			
		||||
        result = number - (number * 2);
 | 
			
		||||
    }
 | 
			
		||||
    result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn string_split(str: string, seperator: int) -> [string] {
 | 
			
		||||
    let result = array_new::<string>();
 | 
			
		||||
 | 
			
		||||
    let i = 0;
 | 
			
		||||
    let current_str = "";
 | 
			
		||||
    loop {
 | 
			
		||||
        if i >= string_length(str) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        let char = str[i];
 | 
			
		||||
        if char == seperator {
 | 
			
		||||
            array_push(result, current_str);
 | 
			
		||||
            current_str = "";
 | 
			
		||||
        } else {
 | 
			
		||||
            current_str = string_push_char(current_str, char);
 | 
			
		||||
        }
 | 
			
		||||
        i = i + 1;
 | 
			
		||||
    }
 | 
			
		||||
    array_push(result, current_str);
 | 
			
		||||
    result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn string_slice(str: string, from: int, to: int) -> string {
 | 
			
		||||
    let result = "";
 | 
			
		||||
    let i = from;
 | 
			
		||||
    loop {
 | 
			
		||||
        if i >= string_length(str) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        if i >= to {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        result = string_push_char(result, str[i]);
 | 
			
		||||
        i = i + 1;
 | 
			
		||||
    }
 | 
			
		||||
    result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn string_contains(str: string, ch: int) -> bool {
 | 
			
		||||
    let len = string_length(str);
 | 
			
		||||
    for (let i = 0; i < len; i += 1) {
 | 
			
		||||
        if str[i] == ch {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn array_clone<T>(array: [T]) -> [T] {
 | 
			
		||||
    let len = array_length(array);
 | 
			
		||||
    let result = array_new::<T>();
 | 
			
		||||
    let i = 0;
 | 
			
		||||
    loop {
 | 
			
		||||
        if i >= len { break; }
 | 
			
		||||
        array_push(result, array[i]);
 | 
			
		||||
        i = 1 + 1;
 | 
			
		||||
    }
 | 
			
		||||
    result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn array_sort_mut(array: [int]) {
 | 
			
		||||
    let len = array_length(array);
 | 
			
		||||
    for (let i = 0; i < len; i += 1) {
 | 
			
		||||
        for (let j = i + 1; j < len; j += 1) {
 | 
			
		||||
            if array[j] < array[i] {
 | 
			
		||||
                let tmp = array[j];
 | 
			
		||||
                array[j] = array[i];
 | 
			
		||||
                array[i] = tmp;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn array_to_sorted(array: [int]) -> [int] {
 | 
			
		||||
    let cloned = array_clone(array);
 | 
			
		||||
    array_sort_mut(array);
 | 
			
		||||
    cloned
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn assert(value: bool, msg: string) {
 | 
			
		||||
    if not value {
 | 
			
		||||
        println("assertion failed: " + msg);
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										151
									
								
								stdlib.slg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								stdlib.slg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,151 @@
 | 
			
		||||
 | 
			
		||||
fn print(msg: string) #[builtin(Print)] {}
 | 
			
		||||
fn println(msg: string) { print(msg + "\n") }
 | 
			
		||||
 | 
			
		||||
fn int_to_string(number: int) -> string #[builtin(IntToString)] {}
 | 
			
		||||
 | 
			
		||||
fn string_push_char(str: string, value: int) -> string #[builtin(StringPushChar)] {}
 | 
			
		||||
fn string_char_at(str: string, index: int) -> int #[builtin(StringCharAt)] {}
 | 
			
		||||
fn string_length(str: string) -> int #[builtin(StringLength)] {}
 | 
			
		||||
fn string_to_int(str: string) -> int #[builtin(StringToInt)] {}
 | 
			
		||||
 | 
			
		||||
fn string_array_new() -> [string] #[builtin(ArrayNew)] {}
 | 
			
		||||
fn string_array_push(array: [string], value: string) #[builtin(ArrayPush)] {}
 | 
			
		||||
fn string_array_length(array: [string]) -> int #[builtin(ArrayLength)] {}
 | 
			
		||||
fn string_array_at(array: [string], index: int) -> string #[builtin(ArrayAt)] {}
 | 
			
		||||
 | 
			
		||||
fn int_array_new() -> [int] #[builtin(ArrayNew)] {}
 | 
			
		||||
fn int_array_push(array: [int], value: int) #[builtin(ArrayPush)] {}
 | 
			
		||||
fn int_array_length(array: [int]) -> int #[builtin(ArrayLength)] {}
 | 
			
		||||
fn int_array_at(array: [int], index: int) -> int #[builtin(ArrayAt)] {}
 | 
			
		||||
 | 
			
		||||
fn file_open(filename: string, mode: string) -> int #[builtin(FileOpen)] {}
 | 
			
		||||
fn file_close(file: int) #[builtin(FileClose)] {}
 | 
			
		||||
fn file_write_string(file: int, content: string) -> int #[builtin(FileWriteString)] {}
 | 
			
		||||
fn file_read_char(file: int) -> int #[builtin(FileReadChar)] {}
 | 
			
		||||
fn file_read_to_string(file: int) -> string #[builtin(FileReadToString)] {}
 | 
			
		||||
fn file_flush(file: int) #[builtin(FileFlush)] {}
 | 
			
		||||
fn file_eof(file: int) -> bool #[builtin(FileEof)] {}
 | 
			
		||||
 | 
			
		||||
fn itos(number: int) -> string #[builtin(IntToString)] {}
 | 
			
		||||
fn stoi(str: string) -> int #[builtin(StringToInt)] {}
 | 
			
		||||
 | 
			
		||||
fn stdin() -> int { 0 }
 | 
			
		||||
fn stdout() -> int { 1 }
 | 
			
		||||
fn stderr() -> int { 2 }
 | 
			
		||||
 | 
			
		||||
fn file_read_line(file: int) -> string {
 | 
			
		||||
    let line = "";
 | 
			
		||||
    loop {
 | 
			
		||||
        if file_eof(file) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        let ch = file_read_char(file);
 | 
			
		||||
        if ch == "\n"[0] {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        line = string_push_char(line, ch);
 | 
			
		||||
    }
 | 
			
		||||
    line
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn read_text_file(filename: string) -> string {
 | 
			
		||||
    let file = file_open(filename, "r");
 | 
			
		||||
    let text = file_read_to_string(file);
 | 
			
		||||
    file_close(file);
 | 
			
		||||
    text
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn input(prompt: string) -> string {
 | 
			
		||||
    print("> ");
 | 
			
		||||
    file_flush(stdout());
 | 
			
		||||
    file_read_line(stdin())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn string_abs(number: int) -> int {
 | 
			
		||||
    let result = number;
 | 
			
		||||
    if number < 0 {
 | 
			
		||||
        result = number - (number * 2);
 | 
			
		||||
    }
 | 
			
		||||
    result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn string_split(str: string, seperator: int) -> [string] {
 | 
			
		||||
    let result: [string] = string_array_new();
 | 
			
		||||
 | 
			
		||||
    let i = 0;
 | 
			
		||||
    let current_str = "";
 | 
			
		||||
    loop {
 | 
			
		||||
        if i >= string_length(str) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        let char = str[i];
 | 
			
		||||
        if char == seperator {
 | 
			
		||||
            string_array_push(result, current_str);
 | 
			
		||||
            current_str = "";
 | 
			
		||||
        } else {
 | 
			
		||||
            current_str = string_push_char(current_str, char);
 | 
			
		||||
        }
 | 
			
		||||
        i = i + 1;
 | 
			
		||||
    }
 | 
			
		||||
    string_array_push(result, current_str);
 | 
			
		||||
    result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn string_slice(str: string, from: int, to: int) -> string {
 | 
			
		||||
    let result = "";
 | 
			
		||||
    let i = from;
 | 
			
		||||
    loop {
 | 
			
		||||
        if i >= string_length(str) {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        if i >= to {
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        result = string_push_char(result, str[i]);
 | 
			
		||||
        i = i + 1;
 | 
			
		||||
    }
 | 
			
		||||
    result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn string_contains(str: string, ch: int) -> bool {
 | 
			
		||||
    let len = string_length(str);
 | 
			
		||||
    for (let i = 0; i < len; i += 1) {
 | 
			
		||||
        if str[i] == ch {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn array_clone(array: [int]) -> [int] {
 | 
			
		||||
    let len = int_array_length(array);
 | 
			
		||||
    let result = int_array_new();
 | 
			
		||||
    let i = 0;
 | 
			
		||||
    loop {
 | 
			
		||||
        if i >= len { break; }
 | 
			
		||||
        int_array_push(result, array[i]);
 | 
			
		||||
        i = 1 + 1;
 | 
			
		||||
    }
 | 
			
		||||
    result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn array_sort_mut(array: [int]) {
 | 
			
		||||
    let len = int_array_length(array);
 | 
			
		||||
    for (let i = 0; i < len; i += 1) {
 | 
			
		||||
        for (let j = i + 1; j < len; j += 1) {
 | 
			
		||||
            if array[j] < array[i] {
 | 
			
		||||
                let tmp = array[j];
 | 
			
		||||
                array[j] = array[i];
 | 
			
		||||
                array[i] = tmp;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn array_to_sorted(array: [int]) -> [int] {
 | 
			
		||||
    let cloned = array_clone(array);
 | 
			
		||||
    array_sort_mut(array);
 | 
			
		||||
    cloned
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,11 +0,0 @@
 | 
			
		||||
mod std;
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    let ints = [1, 2, 3];
 | 
			
		||||
    std::assert(ints[1] == 2, "test int array");
 | 
			
		||||
 | 
			
		||||
    let strings = ["foo", "bar", "baz"];
 | 
			
		||||
    std::assert(strings[1] == "bar", "test string array");
 | 
			
		||||
 | 
			
		||||
    std::println("tests ran successfully");
 | 
			
		||||
}
 | 
			
		||||
@ -1,10 +0,0 @@
 | 
			
		||||
 | 
			
		||||
fn exit(status_code: int) #[builtin(Exit)] {}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    if 'A' != 65 {
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
    exit(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,23 +0,0 @@
 | 
			
		||||
 | 
			
		||||
fn exit(status_code: int) #[builtin(Exit)] {}
 | 
			
		||||
 | 
			
		||||
fn print(msg: string) #[builtin(Print)] {}
 | 
			
		||||
fn println(msg: string) { print(msg + "\n") }
 | 
			
		||||
 | 
			
		||||
fn id<T>(v: T) -> T {
 | 
			
		||||
    v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    println("calling with int");
 | 
			
		||||
    if id::<int>(123) != 123 {
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
    println("calling with bool");
 | 
			
		||||
    if id::<bool>(true) != true {
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
    println("all tests ran successfully");
 | 
			
		||||
    exit(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,19 +0,0 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
fn exit(status_code: int) #[builtin(Exit)] {}
 | 
			
		||||
 | 
			
		||||
fn print(msg: string) #[builtin(Print)] {}
 | 
			
		||||
fn println(msg: string) { print(msg + "\n") }
 | 
			
		||||
 | 
			
		||||
mod inner "import_modules_inner.slg";
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    println("test function from module");
 | 
			
		||||
    let res = inner::inner_fn(32);
 | 
			
		||||
    if res != 64 {
 | 
			
		||||
        println("failed");
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
    println("all tests ran successfully");
 | 
			
		||||
    exit(0);
 | 
			
		||||
}
 | 
			
		||||
@ -1,5 +0,0 @@
 | 
			
		||||
 | 
			
		||||
fn inner_fn(a: int) -> int {
 | 
			
		||||
    a + 32
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,25 +0,0 @@
 | 
			
		||||
 | 
			
		||||
fn exit(status_code: int) #[builtin(Exit)] {}
 | 
			
		||||
 | 
			
		||||
fn print(msg: string) #[builtin(Print)] {}
 | 
			
		||||
fn println(msg: string) { print(msg + "\n") }
 | 
			
		||||
 | 
			
		||||
mod my_module {
 | 
			
		||||
    
 | 
			
		||||
    fn inner_fn(a: int) -> int {
 | 
			
		||||
        a + 32
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    println("test function from module");
 | 
			
		||||
    let res = my_module::inner_fn(32);
 | 
			
		||||
    if res != 64 {
 | 
			
		||||
        println("failed");
 | 
			
		||||
        exit(1);
 | 
			
		||||
    }
 | 
			
		||||
    println("all tests ran successfully");
 | 
			
		||||
    exit(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,20 +0,0 @@
 | 
			
		||||
mod std;
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    let d = true;
 | 
			
		||||
 | 
			
		||||
    let v = struct {
 | 
			
		||||
        a: 123,
 | 
			
		||||
        b: struct {
 | 
			
		||||
            c: "foo",
 | 
			
		||||
            d: d,
 | 
			
		||||
        },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    std::assert(v.a == 123, "test field");
 | 
			
		||||
    std::assert(v.b.c == "foo", "test nested field");
 | 
			
		||||
    std::assert(v.b.d == true, "test resolved field");
 | 
			
		||||
 | 
			
		||||
    std::println("tests ran successfully");
 | 
			
		||||
}   
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user