Compare commits
	
		
			28 Commits
		
	
	
		
			5d6b1abefc
			...
			629a821c9e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 629a821c9e | |||
| aa5e6bbaac | |||
| 99532d37d6 | |||
| 8367399a2c | |||
| 6351bc1e71 | |||
| 76fff577b1 | |||
| b402e981e7 | |||
| b6e6234ff6 | |||
| 1bbf6121b0 | |||
| eb2a6d90f3 | |||
| 1bbb759981 | |||
| c6906a65c4 | |||
| c45682a77d | |||
| ca1d67a54a | |||
| 7c68480a40 | |||
| 7b879d30be | |||
| 7ca1ff1e25 | |||
| 852df09ac9 | |||
| 4ac569fde3 | |||
| 9485cafbba | |||
| 0449a232c3 | |||
| 87d46b6647 | |||
| 34529d9cbb | |||
| 0da3d4b7b6 | |||
| 0eaa8fc60d | |||
| 027cf8dfe8 | |||
| 75b9b53fdd | |||
| 915b953df3 | 
@ -40,7 +40,8 @@ export const Ops = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export type Builtins = typeof Builtins;
 | 
					export type Builtins = typeof Builtins;
 | 
				
			||||||
export const Builtins = {
 | 
					export const Builtins = {
 | 
				
			||||||
    IntToString: 0x00,
 | 
					    Exit: 0x00,
 | 
				
			||||||
 | 
					    IntToString: 0x01,
 | 
				
			||||||
    StringConcat: 0x10,
 | 
					    StringConcat: 0x10,
 | 
				
			||||||
    StringEqual: 0x11,
 | 
					    StringEqual: 0x11,
 | 
				
			||||||
    StringCharAt: 0x12,
 | 
					    StringCharAt: 0x12,
 | 
				
			||||||
@ -52,7 +53,9 @@ export const Builtins = {
 | 
				
			|||||||
    ArrayPush: 0x22,
 | 
					    ArrayPush: 0x22,
 | 
				
			||||||
    ArrayAt: 0x23,
 | 
					    ArrayAt: 0x23,
 | 
				
			||||||
    ArrayLength: 0x24,
 | 
					    ArrayLength: 0x24,
 | 
				
			||||||
    StructSet: 0x30,
 | 
					    StructNew: 0x30,
 | 
				
			||||||
 | 
					    StructSet: 0x31,
 | 
				
			||||||
 | 
					    StructAt: 0x32,
 | 
				
			||||||
    Print: 0x40,
 | 
					    Print: 0x40,
 | 
				
			||||||
    FileOpen: 0x41,
 | 
					    FileOpen: 0x41,
 | 
				
			||||||
    FileClose: 0x42,
 | 
					    FileClose: 0x42,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										162
									
								
								compiler/ast.ts
									
									
									
									
									
								
							
							
						
						
									
										162
									
								
								compiler/ast.ts
									
									
									
									
									
								
							@ -1,32 +1,50 @@
 | 
				
			|||||||
 | 
					import type { Syms } from "./resolver_syms.ts";
 | 
				
			||||||
import { Pos } from "./token.ts";
 | 
					import { Pos } from "./token.ts";
 | 
				
			||||||
import { VType } from "./vtype.ts";
 | 
					import { GenericArgsMap, VType } from "./vtype.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Mod = {
 | 
				
			||||||
 | 
					    filePath: string;
 | 
				
			||||||
 | 
					    ast: Stmt[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type Stmt = {
 | 
					export type Stmt = {
 | 
				
			||||||
    kind: StmtKind;
 | 
					    kind: StmtKind;
 | 
				
			||||||
    pos: Pos;
 | 
					    pos: Pos;
 | 
				
			||||||
 | 
					    details?: StmtDetails;
 | 
				
			||||||
    id: number;
 | 
					    id: number;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type StmtKind =
 | 
					export type StmtKind =
 | 
				
			||||||
    | { type: "error" }
 | 
					    | { type: "error" }
 | 
				
			||||||
    | { type: "import"; path: Expr }
 | 
					    | { 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: "break"; expr?: Expr }
 | 
				
			||||||
    | { type: "return"; expr?: Expr }
 | 
					    | { type: "return"; expr?: Expr }
 | 
				
			||||||
    | {
 | 
					    | FnStmtKind
 | 
				
			||||||
        type: "fn";
 | 
					 | 
				
			||||||
        ident: string;
 | 
					 | 
				
			||||||
        params: Param[];
 | 
					 | 
				
			||||||
        returnType?: EType;
 | 
					 | 
				
			||||||
        body: Expr;
 | 
					 | 
				
			||||||
        anno?: Anno;
 | 
					 | 
				
			||||||
        vtype?: VType;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    | { type: "let"; param: Param; value: Expr }
 | 
					    | { type: "let"; param: Param; value: Expr }
 | 
				
			||||||
 | 
					    | { type: "type_alias"; param: Param }
 | 
				
			||||||
    | { type: "assign"; assignType: AssignType; subject: Expr; value: Expr }
 | 
					    | { type: "assign"; assignType: AssignType; subject: Expr; value: Expr }
 | 
				
			||||||
    | { type: "expr"; expr: 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 AssignType = "=" | "+=" | "-=";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type StmtDetails = {
 | 
				
			||||||
 | 
					    pub: boolean;
 | 
				
			||||||
 | 
					    annos: Anno[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type Expr = {
 | 
					export type Expr = {
 | 
				
			||||||
    kind: ExprKind;
 | 
					    kind: ExprKind;
 | 
				
			||||||
    pos: Pos;
 | 
					    pos: Pos;
 | 
				
			||||||
@ -38,11 +56,28 @@ export type ExprKind =
 | 
				
			|||||||
    | { type: "error" }
 | 
					    | { type: "error" }
 | 
				
			||||||
    | { type: "int"; value: number }
 | 
					    | { type: "int"; value: number }
 | 
				
			||||||
    | { type: "string"; value: string }
 | 
					    | { type: "string"; value: string }
 | 
				
			||||||
    | { type: "ident"; value: string }
 | 
					    | { type: "ident"; ident: string }
 | 
				
			||||||
 | 
					    | {
 | 
				
			||||||
 | 
					        type: "sym";
 | 
				
			||||||
 | 
					        ident: string;
 | 
				
			||||||
 | 
					        sym: Sym;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    | { type: "group"; expr: Expr }
 | 
					    | { type: "group"; expr: Expr }
 | 
				
			||||||
    | { type: "field"; subject: Expr; value: string }
 | 
					    | { 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: "index"; subject: Expr; value: Expr }
 | 
				
			||||||
    | { type: "call"; subject: Expr; args: 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: "unary"; unaryType: UnaryType; subject: Expr }
 | 
				
			||||||
    | { type: "binary"; binaryType: BinaryType; left: Expr; right: Expr }
 | 
					    | { type: "binary"; binaryType: BinaryType; left: Expr; right: Expr }
 | 
				
			||||||
    | { type: "if"; cond: Expr; truthy: Expr; falsy?: Expr; elsePos?: Pos }
 | 
					    | { type: "if"; cond: Expr; truthy: Expr; falsy?: Expr; elsePos?: Pos }
 | 
				
			||||||
@ -50,11 +85,6 @@ export type ExprKind =
 | 
				
			|||||||
    | { type: "null" }
 | 
					    | { type: "null" }
 | 
				
			||||||
    | { type: "loop"; body: Expr }
 | 
					    | { type: "loop"; body: Expr }
 | 
				
			||||||
    | { type: "block"; stmts: Stmt[]; expr?: Expr }
 | 
					    | { type: "block"; stmts: Stmt[]; expr?: Expr }
 | 
				
			||||||
    | {
 | 
					 | 
				
			||||||
        type: "sym";
 | 
					 | 
				
			||||||
        ident: string;
 | 
					 | 
				
			||||||
        sym: Sym;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    | { type: "while"; cond: Expr; body: Expr }
 | 
					    | { type: "while"; cond: Expr; body: Expr }
 | 
				
			||||||
    | { type: "for_in"; param: Param; value: Expr; body: Expr }
 | 
					    | { type: "for_in"; param: Param; value: Expr; body: Expr }
 | 
				
			||||||
    | {
 | 
					    | {
 | 
				
			||||||
@ -80,25 +110,38 @@ export type BinaryType =
 | 
				
			|||||||
    | "or"
 | 
					    | "or"
 | 
				
			||||||
    | "and";
 | 
					    | "and";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type Param = {
 | 
					export type Field = {
 | 
				
			||||||
    ident: string;
 | 
					    ident: string;
 | 
				
			||||||
 | 
					    expr: Expr;
 | 
				
			||||||
 | 
					    pos: Pos;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Param = {
 | 
				
			||||||
 | 
					    id: number;
 | 
				
			||||||
 | 
					    index?: number;
 | 
				
			||||||
 | 
					    ident: string;
 | 
				
			||||||
 | 
					    mut: boolean;
 | 
				
			||||||
    etype?: EType;
 | 
					    etype?: EType;
 | 
				
			||||||
    pos: Pos;
 | 
					    pos: Pos;
 | 
				
			||||||
 | 
					    sym?: Sym;
 | 
				
			||||||
    vtype?: VType;
 | 
					    vtype?: VType;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type Sym = {
 | 
					export type Sym = {
 | 
				
			||||||
    ident: string;
 | 
					    ident: string;
 | 
				
			||||||
 | 
					    fullPath: string;
 | 
				
			||||||
    pos?: Pos;
 | 
					    pos?: Pos;
 | 
				
			||||||
} & SymKind;
 | 
					} & SymKind;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type SymKind =
 | 
					export type SymKind =
 | 
				
			||||||
    | { type: "let"; stmt: Stmt; param: Param }
 | 
					    | { type: "let"; stmt: Stmt; param: Param }
 | 
				
			||||||
    | { type: "let_static"; stmt: Stmt; param: Param }
 | 
					    | { type: "let_static"; stmt: Stmt; param: Param }
 | 
				
			||||||
 | 
					    | { type: "type_alias"; stmt: Stmt; param: Param }
 | 
				
			||||||
    | { type: "fn"; stmt: Stmt }
 | 
					    | { type: "fn"; stmt: Stmt }
 | 
				
			||||||
    | { type: "fn_param"; param: Param }
 | 
					    | { type: "fn_param"; param: Param }
 | 
				
			||||||
    | { type: "closure"; inner: Sym }
 | 
					    | { type: "closure"; inner: Sym }
 | 
				
			||||||
    | { type: "builtin"; builtinId: number };
 | 
					    | { type: "generic"; stmt: Stmt; genericParam: GenericParam }
 | 
				
			||||||
 | 
					    | { type: "mod"; syms: Syms };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type EType = {
 | 
					export type EType = {
 | 
				
			||||||
    kind: ETypeKind;
 | 
					    kind: ETypeKind;
 | 
				
			||||||
@ -108,11 +151,27 @@ export type EType = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export type ETypeKind =
 | 
					export type ETypeKind =
 | 
				
			||||||
    | { type: "error" }
 | 
					    | { type: "error" }
 | 
				
			||||||
    | { type: "ident"; value: string }
 | 
					    | { type: "null" }
 | 
				
			||||||
    | { type: "array"; inner: EType }
 | 
					    | { type: "int" }
 | 
				
			||||||
    | { type: "struct"; fields: Param[] };
 | 
					    | { 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 ETypeParam = {
 | 
					export type GenericParam = {
 | 
				
			||||||
 | 
					    id: number;
 | 
				
			||||||
 | 
					    index: number;
 | 
				
			||||||
    ident: string;
 | 
					    ident: string;
 | 
				
			||||||
    pos: Pos;
 | 
					    pos: Pos;
 | 
				
			||||||
    vtype?: VType;
 | 
					    vtype?: VType;
 | 
				
			||||||
@ -120,28 +179,63 @@ export type ETypeParam = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export type Anno = {
 | 
					export type Anno = {
 | 
				
			||||||
    ident: string;
 | 
					    ident: string;
 | 
				
			||||||
    values: Expr[];
 | 
					    args: Expr[];
 | 
				
			||||||
    pos: Pos;
 | 
					    pos: Pos;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class AstCreator {
 | 
					export class AstCreator {
 | 
				
			||||||
    private nextNodeId = 0;
 | 
					    private nextNodeId = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public stmt(kind: StmtKind, pos: Pos): Stmt {
 | 
					    public stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt {
 | 
				
			||||||
        const id = this.nextNodeId;
 | 
					        const id = this.genId();
 | 
				
			||||||
        this.nextNodeId += 1;
 | 
					        return { kind, pos, details, id };
 | 
				
			||||||
        return { kind, pos, id };
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public expr(kind: ExprKind, pos: Pos): Expr {
 | 
					    public expr(kind: ExprKind, pos: Pos): Expr {
 | 
				
			||||||
        const id = this.nextNodeId;
 | 
					        const id = this.genId();
 | 
				
			||||||
        this.nextNodeId += 1;
 | 
					 | 
				
			||||||
        return { kind, pos, id };
 | 
					        return { kind, pos, id };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public etype(kind: ETypeKind, pos: Pos): EType {
 | 
					    public etype(kind: ETypeKind, pos: Pos): EType {
 | 
				
			||||||
        const id = this.nextNodeId;
 | 
					        const id = this.genId();
 | 
				
			||||||
        this.nextNodeId += 1;
 | 
					 | 
				
			||||||
        return { kind, pos, id };
 | 
					        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 };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { EType, Expr, Param, Stmt } from "./ast.ts";
 | 
					import { EType, Expr, Field, Param, Stmt } from "./ast.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type VisitRes = "stop" | void;
 | 
					export type VisitRes = "stop" | void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -6,11 +6,14 @@ export interface AstVisitor<Args extends unknown[] = []> {
 | 
				
			|||||||
    visitStmts?(stmts: Stmt[], ...args: Args): VisitRes;
 | 
					    visitStmts?(stmts: Stmt[], ...args: Args): VisitRes;
 | 
				
			||||||
    visitStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
					    visitStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
				
			||||||
    visitErrorStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
					    visitErrorStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
				
			||||||
    visitImportStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
					    visitModFileStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
				
			||||||
 | 
					    visitModBlockStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
				
			||||||
 | 
					    visitModStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
				
			||||||
    visitBreakStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
					    visitBreakStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
				
			||||||
    visitReturnStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
					    visitReturnStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
				
			||||||
    visitFnStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
					    visitFnStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
				
			||||||
    visitLetStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
					    visitLetStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
				
			||||||
 | 
					    visitTypeAliasStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
				
			||||||
    visitAssignStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
					    visitAssignStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
				
			||||||
    visitExprStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
					    visitExprStmt?(stmt: Stmt, ...args: Args): VisitRes;
 | 
				
			||||||
    visitExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
					    visitExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
				
			||||||
@ -19,9 +22,16 @@ export interface AstVisitor<Args extends unknown[] = []> {
 | 
				
			|||||||
    visitStringExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
					    visitStringExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
				
			||||||
    visitIdentExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
					    visitIdentExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
				
			||||||
    visitGroupExpr?(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;
 | 
					    visitFieldExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
				
			||||||
    visitIndexExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
					    visitIndexExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
				
			||||||
    visitCallExpr?(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;
 | 
					    visitUnaryExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
				
			||||||
    visitBinaryExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
					    visitBinaryExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
				
			||||||
    visitIfExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
					    visitIfExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
				
			||||||
@ -34,11 +44,22 @@ export interface AstVisitor<Args extends unknown[] = []> {
 | 
				
			|||||||
    visitBlockExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
					    visitBlockExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
				
			||||||
    visitSymExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
					    visitSymExpr?(expr: Expr, ...args: Args): VisitRes;
 | 
				
			||||||
    visitParam?(param: Param, ...args: Args): VisitRes;
 | 
					    visitParam?(param: Param, ...args: Args): VisitRes;
 | 
				
			||||||
 | 
					    visitField?(field: Field, ...args: Args): VisitRes;
 | 
				
			||||||
    visitEType?(etype: EType, ...args: Args): VisitRes;
 | 
					    visitEType?(etype: EType, ...args: Args): VisitRes;
 | 
				
			||||||
    visitErrorEType?(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;
 | 
					    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;
 | 
					    visitArrayEType?(etype: EType, ...args: Args): VisitRes;
 | 
				
			||||||
    visitStructEType?(etype: EType, ...args: Args): VisitRes;
 | 
					    visitStructEType?(etype: EType, ...args: Args): VisitRes;
 | 
				
			||||||
 | 
					    visitTypeOfEType?(etype: EType, ...args: Args): VisitRes;
 | 
				
			||||||
    visitAnno?(etype: EType, ...args: Args): VisitRes;
 | 
					    visitAnno?(etype: EType, ...args: Args): VisitRes;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -61,9 +82,16 @@ export function visitStmt<Args extends unknown[] = []>(
 | 
				
			|||||||
        case "error":
 | 
					        case "error":
 | 
				
			||||||
            if (v.visitErrorStmt?.(stmt, ...args) == "stop") return;
 | 
					            if (v.visitErrorStmt?.(stmt, ...args) == "stop") return;
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        case "import":
 | 
					        case "mod_file":
 | 
				
			||||||
            if (v.visitImportStmt?.(stmt, ...args) == "stop") return;
 | 
					            if (v.visitModFileStmt?.(stmt, ...args) == "stop") return;
 | 
				
			||||||
            visitExpr(stmt.kind.path, v, ...args);
 | 
					            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);
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        case "break":
 | 
					        case "break":
 | 
				
			||||||
            if (v.visitBreakStmt?.(stmt, ...args) == "stop") return;
 | 
					            if (v.visitBreakStmt?.(stmt, ...args) == "stop") return;
 | 
				
			||||||
@ -86,6 +114,10 @@ export function visitStmt<Args extends unknown[] = []>(
 | 
				
			|||||||
            visitParam(stmt.kind.param, v, ...args);
 | 
					            visitParam(stmt.kind.param, v, ...args);
 | 
				
			||||||
            visitExpr(stmt.kind.value, v, ...args);
 | 
					            visitExpr(stmt.kind.value, v, ...args);
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
 | 
					        case "type_alias":
 | 
				
			||||||
 | 
					            if (v.visitTypeAliasStmt?.(stmt, ...args) == "stop") return;
 | 
				
			||||||
 | 
					            visitParam(stmt.kind.param, v, ...args);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
        case "assign":
 | 
					        case "assign":
 | 
				
			||||||
            if (v.visitAssignStmt?.(stmt, ...args) == "stop") return;
 | 
					            if (v.visitAssignStmt?.(stmt, ...args) == "stop") return;
 | 
				
			||||||
            visitExpr(stmt.kind.subject, v, ...args);
 | 
					            visitExpr(stmt.kind.subject, v, ...args);
 | 
				
			||||||
@ -95,6 +127,12 @@ export function visitStmt<Args extends unknown[] = []>(
 | 
				
			|||||||
            if (v.visitExprStmt?.(stmt, ...args) == "stop") return;
 | 
					            if (v.visitExprStmt?.(stmt, ...args) == "stop") return;
 | 
				
			||||||
            visitExpr(stmt.kind.expr, v, ...args);
 | 
					            visitExpr(stmt.kind.expr, v, ...args);
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            throw new Error(
 | 
				
			||||||
 | 
					                `statement '${
 | 
				
			||||||
 | 
					                    (stmt.kind as { type: string }).type
 | 
				
			||||||
 | 
					                }' not implemented`,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -121,6 +159,18 @@ export function visitExpr<Args extends unknown[] = []>(
 | 
				
			|||||||
            if (v.visitGroupExpr?.(expr, ...args) == "stop") return;
 | 
					            if (v.visitGroupExpr?.(expr, ...args) == "stop") return;
 | 
				
			||||||
            visitExpr(expr.kind.expr, v, ...args);
 | 
					            visitExpr(expr.kind.expr, v, ...args);
 | 
				
			||||||
            break;
 | 
					            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":
 | 
					        case "field":
 | 
				
			||||||
            if (v.visitFieldExpr?.(expr, ...args) == "stop") return;
 | 
					            if (v.visitFieldExpr?.(expr, ...args) == "stop") return;
 | 
				
			||||||
            visitExpr(expr.kind.subject, v, ...args);
 | 
					            visitExpr(expr.kind.subject, v, ...args);
 | 
				
			||||||
@ -135,6 +185,15 @@ export function visitExpr<Args extends unknown[] = []>(
 | 
				
			|||||||
            visitExpr(expr.kind.subject, v, ...args);
 | 
					            visitExpr(expr.kind.subject, v, ...args);
 | 
				
			||||||
            expr.kind.args.map((arg) => visitExpr(arg, v, ...args));
 | 
					            expr.kind.args.map((arg) => visitExpr(arg, v, ...args));
 | 
				
			||||||
            break;
 | 
					            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":
 | 
					        case "unary":
 | 
				
			||||||
            if (v.visitUnaryExpr?.(expr, ...args) == "stop") return;
 | 
					            if (v.visitUnaryExpr?.(expr, ...args) == "stop") return;
 | 
				
			||||||
            visitExpr(expr.kind.subject, v, ...args);
 | 
					            visitExpr(expr.kind.subject, v, ...args);
 | 
				
			||||||
@ -144,6 +203,14 @@ export function visitExpr<Args extends unknown[] = []>(
 | 
				
			|||||||
            visitExpr(expr.kind.left, v, ...args);
 | 
					            visitExpr(expr.kind.left, v, ...args);
 | 
				
			||||||
            visitExpr(expr.kind.right, v, ...args);
 | 
					            visitExpr(expr.kind.right, v, ...args);
 | 
				
			||||||
            break;
 | 
					            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":
 | 
					        case "if":
 | 
				
			||||||
            if (v.visitIfExpr?.(expr, ...args) == "stop") return;
 | 
					            if (v.visitIfExpr?.(expr, ...args) == "stop") return;
 | 
				
			||||||
            visitExpr(expr.kind.cond, v, ...args);
 | 
					            visitExpr(expr.kind.cond, v, ...args);
 | 
				
			||||||
@ -186,6 +253,12 @@ export function visitExpr<Args extends unknown[] = []>(
 | 
				
			|||||||
        case "sym":
 | 
					        case "sym":
 | 
				
			||||||
            if (v.visitSymExpr?.(expr, ...args) == "stop") return;
 | 
					            if (v.visitSymExpr?.(expr, ...args) == "stop") return;
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            throw new Error(
 | 
				
			||||||
 | 
					                `expression '${
 | 
				
			||||||
 | 
					                    (expr.kind as { type: string }).type
 | 
				
			||||||
 | 
					                }' not implemented`,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -198,6 +271,15 @@ export function visitParam<Args extends unknown[] = []>(
 | 
				
			|||||||
    if (param.etype) visitEType(param.etype, v, ...args);
 | 
					    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[] = []>(
 | 
					export function visitEType<Args extends unknown[] = []>(
 | 
				
			||||||
    etype: EType,
 | 
					    etype: EType,
 | 
				
			||||||
    v: AstVisitor<Args>,
 | 
					    v: AstVisitor<Args>,
 | 
				
			||||||
@ -208,17 +290,58 @@ export function visitEType<Args extends unknown[] = []>(
 | 
				
			|||||||
        case "error":
 | 
					        case "error":
 | 
				
			||||||
            if (v.visitErrorEType?.(etype, ...args) == "stop") return;
 | 
					            if (v.visitErrorEType?.(etype, ...args) == "stop") return;
 | 
				
			||||||
            break;
 | 
					            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":
 | 
					        case "ident":
 | 
				
			||||||
            if (v.visitIdentEType?.(etype, ...args) == "stop") return;
 | 
					            if (v.visitIdentEType?.(etype, ...args) == "stop") return;
 | 
				
			||||||
            break;
 | 
					            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":
 | 
					        case "array":
 | 
				
			||||||
            if (v.visitArrayEType?.(etype, ...args) == "stop") return;
 | 
					            if (v.visitArrayEType?.(etype, ...args) == "stop") return;
 | 
				
			||||||
            if (etype.kind.inner) visitEType(etype.kind.inner, v, ...args);
 | 
					            visitEType(etype.kind.subject, v, ...args);
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        case "struct":
 | 
					        case "struct":
 | 
				
			||||||
            if (v.visitStructEType?.(etype, ...args) == "stop") return;
 | 
					            if (v.visitStructEType?.(etype, ...args) == "stop") return;
 | 
				
			||||||
            etype.kind.fields.map((field) => visitParam(field, v, ...args));
 | 
					            etype.kind.fields.map((field) => visitParam(field, v, ...args));
 | 
				
			||||||
            break;
 | 
					            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`,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,40 +1,75 @@
 | 
				
			|||||||
import { EType, Expr, Stmt } from "./ast.ts";
 | 
					import { AnnoView, EType, Expr, forceType, Stmt, Sym } from "./ast.ts";
 | 
				
			||||||
import { printStackTrace, Reporter } from "./info.ts";
 | 
					import { printStackTrace, Reporter } from "./info.ts";
 | 
				
			||||||
import { Pos } from "./token.ts";
 | 
					import { Pos } from "./token.ts";
 | 
				
			||||||
import { VType, VTypeParam, vtypesEqual, vtypeToString } from "./vtype.ts";
 | 
					import {
 | 
				
			||||||
 | 
					    extractGenericType,
 | 
				
			||||||
 | 
					    GenericArgsMap,
 | 
				
			||||||
 | 
					    VType,
 | 
				
			||||||
 | 
					    VTypeGenericParam,
 | 
				
			||||||
 | 
					    VTypeParam,
 | 
				
			||||||
 | 
					    vtypesEqual,
 | 
				
			||||||
 | 
					    vtypeToString,
 | 
				
			||||||
 | 
					} from "./vtype.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Checker {
 | 
					export class Checker {
 | 
				
			||||||
    private fnReturnStack: VType[] = [];
 | 
					    private fnReturnStack: VType[] = [];
 | 
				
			||||||
    private loopBreakStack: VType[][] = [];
 | 
					    private loopBreakStack: VType[][] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private globalIdToGenericParamMap = new Map<number, VTypeGenericParam>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public constructor(private reporter: Reporter) {}
 | 
					    public constructor(private reporter: Reporter) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public check(stmts: Stmt[]) {
 | 
					    public check(stmts: Stmt[]) {
 | 
				
			||||||
        this.checkFnHeaders(stmts);
 | 
					        this.scout(stmts);
 | 
				
			||||||
        for (const stmt of stmts) {
 | 
					        for (const stmt of stmts) {
 | 
				
			||||||
            this.checkStmt(stmt);
 | 
					            this.checkStmt(stmt);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private checkFnHeaders(stmts: Stmt[]) {
 | 
					    private scout(stmts: Stmt[]) {
 | 
				
			||||||
        for (const stmt of stmts) {
 | 
					        for (const stmt of stmts) {
 | 
				
			||||||
            if (stmt.kind.type !== "fn") {
 | 
					            if (stmt.kind.type === "fn") {
 | 
				
			||||||
                continue;
 | 
					                let genericParams: VTypeGenericParam[] | undefined;
 | 
				
			||||||
 | 
					                if (stmt.kind.genericParams !== undefined) {
 | 
				
			||||||
 | 
					                    genericParams = [];
 | 
				
			||||||
 | 
					                    for (const etypeParam of stmt.kind.genericParams) {
 | 
				
			||||||
 | 
					                        const id = genericParams.length;
 | 
				
			||||||
 | 
					                        const globalId = etypeParam.id;
 | 
				
			||||||
 | 
					                        const param = { id, ident: etypeParam.ident };
 | 
				
			||||||
 | 
					                        genericParams.push(param);
 | 
				
			||||||
 | 
					                        this.globalIdToGenericParamMap.set(globalId, param);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            const returnType: VType = stmt.kind.returnType
 | 
					 | 
				
			||||||
                ? this.checkEType(stmt.kind.returnType)
 | 
					 | 
				
			||||||
                : { type: "null" };
 | 
					 | 
				
			||||||
                const params: VTypeParam[] = [];
 | 
					                const params: VTypeParam[] = [];
 | 
				
			||||||
                for (const param of stmt.kind.params) {
 | 
					                for (const param of stmt.kind.params) {
 | 
				
			||||||
                    if (param.etype === undefined) {
 | 
					                    if (param.etype === undefined) {
 | 
				
			||||||
                    this.report("parameter types must be defined", param.pos);
 | 
					                        this.report(
 | 
				
			||||||
 | 
					                            "parameter types must be defined",
 | 
				
			||||||
 | 
					                            param.pos,
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
                        stmt.kind.vtype = { type: "error" };
 | 
					                        stmt.kind.vtype = { type: "error" };
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    const vtype = this.checkEType(param.etype!);
 | 
					                    const vtype = this.checkEType(param.etype!);
 | 
				
			||||||
                    param.vtype = vtype;
 | 
					                    param.vtype = vtype;
 | 
				
			||||||
                params.push({ ident: param.ident, vtype });
 | 
					                    params.push({ ident: param.ident, mut: true, vtype });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                const returnType: VType = stmt.kind.returnType
 | 
				
			||||||
 | 
					                    ? this.checkEType(stmt.kind.returnType)
 | 
				
			||||||
 | 
					                    : { type: "null" };
 | 
				
			||||||
 | 
					                stmt.kind.vtype = {
 | 
				
			||||||
 | 
					                    type: "fn",
 | 
				
			||||||
 | 
					                    genericParams,
 | 
				
			||||||
 | 
					                    params,
 | 
				
			||||||
 | 
					                    returnType,
 | 
				
			||||||
 | 
					                    stmtId: stmt.id,
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            } else if (stmt.kind.type === "type_alias") {
 | 
				
			||||||
 | 
					                if (!stmt.kind.param.etype) {
 | 
				
			||||||
 | 
					                    this.report("no type specified", stmt.pos);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                stmt.kind.param.vtype = this.checkEType(stmt.kind.param.etype);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            stmt.kind.vtype = { type: "fn", params, returnType };
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -42,6 +77,11 @@ export class Checker {
 | 
				
			|||||||
        switch (stmt.kind.type) {
 | 
					        switch (stmt.kind.type) {
 | 
				
			||||||
            case "error":
 | 
					            case "error":
 | 
				
			||||||
                return { type: "error" };
 | 
					                return { type: "error" };
 | 
				
			||||||
 | 
					            case "mod_block":
 | 
				
			||||||
 | 
					            case "mod_file":
 | 
				
			||||||
 | 
					                throw new Error("mod declaration in ast, should be resolved");
 | 
				
			||||||
 | 
					            case "mod":
 | 
				
			||||||
 | 
					                return this.checkModStmt(stmt);
 | 
				
			||||||
            case "break":
 | 
					            case "break":
 | 
				
			||||||
                return this.checkBreakStmt(stmt);
 | 
					                return this.checkBreakStmt(stmt);
 | 
				
			||||||
            case "return":
 | 
					            case "return":
 | 
				
			||||||
@ -50,6 +90,8 @@ export class Checker {
 | 
				
			|||||||
                return this.checkFnStmt(stmt);
 | 
					                return this.checkFnStmt(stmt);
 | 
				
			||||||
            case "let":
 | 
					            case "let":
 | 
				
			||||||
                return this.checkLetStmt(stmt);
 | 
					                return this.checkLetStmt(stmt);
 | 
				
			||||||
 | 
					            case "type_alias":
 | 
				
			||||||
 | 
					                return this.checkTypeAliasStmt(stmt);
 | 
				
			||||||
            case "assign":
 | 
					            case "assign":
 | 
				
			||||||
                return this.checkAssignStmt(stmt);
 | 
					                return this.checkAssignStmt(stmt);
 | 
				
			||||||
            case "expr":
 | 
					            case "expr":
 | 
				
			||||||
@ -57,6 +99,17 @@ export class Checker {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public checkModStmt(stmt: Stmt) {
 | 
				
			||||||
 | 
					        if (stmt.kind.type !== "mod") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const { ast } = stmt.kind.mod;
 | 
				
			||||||
 | 
					        this.scout(ast);
 | 
				
			||||||
 | 
					        for (const stmt of ast) {
 | 
				
			||||||
 | 
					            this.checkStmt(stmt);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public checkBreakStmt(stmt: Stmt) {
 | 
					    public checkBreakStmt(stmt: Stmt) {
 | 
				
			||||||
        if (stmt.kind.type !== "break") {
 | 
					        if (stmt.kind.type !== "break") {
 | 
				
			||||||
            throw new Error();
 | 
					            throw new Error();
 | 
				
			||||||
@ -103,8 +156,8 @@ export class Checker {
 | 
				
			|||||||
        if (!vtypesEqual(exprType, returnType)) {
 | 
					        if (!vtypesEqual(exprType, returnType)) {
 | 
				
			||||||
            this.report(
 | 
					            this.report(
 | 
				
			||||||
                `incompatible return type` +
 | 
					                `incompatible return type` +
 | 
				
			||||||
                    `, got ${exprType}` +
 | 
					                    `, expected ${vtypeToString(returnType)}` +
 | 
				
			||||||
                    `, expected ${returnType}`,
 | 
					                    `, got ${vtypeToString(exprType)}`,
 | 
				
			||||||
                pos,
 | 
					                pos,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -119,16 +172,17 @@ export class Checker {
 | 
				
			|||||||
            throw new Error();
 | 
					            throw new Error();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (
 | 
					        const annos = new AnnoView(stmt.details);
 | 
				
			||||||
            stmt.kind.anno?.ident === "remainder" ||
 | 
					        if (annos.has("builtin", "remainder")) {
 | 
				
			||||||
            stmt.kind.anno?.ident === "builtin"
 | 
					            // NOTE: handled in lowerer
 | 
				
			||||||
        ) {
 | 
					 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const { returnType } = stmt.kind.vtype!;
 | 
					        const { returnType } = stmt.kind.vtype!;
 | 
				
			||||||
 | 
					        if (returnType.type === "error") return returnType;
 | 
				
			||||||
        this.fnReturnStack.push(returnType);
 | 
					        this.fnReturnStack.push(returnType);
 | 
				
			||||||
        const body = this.checkExpr(stmt.kind.body);
 | 
					        const body = this.checkExpr(stmt.kind.body);
 | 
				
			||||||
 | 
					        if (body.type === "error") return body;
 | 
				
			||||||
        this.fnReturnStack.pop();
 | 
					        this.fnReturnStack.pop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!vtypesEqual(returnType, body)) {
 | 
					        if (!vtypesEqual(returnType, body)) {
 | 
				
			||||||
@ -147,13 +201,17 @@ export class Checker {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        const pos = stmt.pos;
 | 
					        const pos = stmt.pos;
 | 
				
			||||||
        const value = this.checkExpr(stmt.kind.value);
 | 
					        const value = this.checkExpr(stmt.kind.value);
 | 
				
			||||||
 | 
					        if (value.type === "error") {
 | 
				
			||||||
 | 
					            return stmt.kind.param.vtype = value;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        if (stmt.kind.param.etype) {
 | 
					        if (stmt.kind.param.etype) {
 | 
				
			||||||
            const paramVtype = this.checkEType(stmt.kind.param.etype);
 | 
					            const paramVType = this.checkEType(stmt.kind.param.etype);
 | 
				
			||||||
            if (!vtypesEqual(value, paramVtype)) {
 | 
					            if (paramVType.type === "error") return paramVType;
 | 
				
			||||||
 | 
					            if (!vtypesEqual(value, paramVType)) {
 | 
				
			||||||
                this.report(
 | 
					                this.report(
 | 
				
			||||||
                    `incompatible value type` +
 | 
					                    `incompatible value type` +
 | 
				
			||||||
                        `, got '${vtypeToString(value)}'` +
 | 
					                        `, got '${vtypeToString(value)}'` +
 | 
				
			||||||
                        `, expected '${vtypeToString(paramVtype)}'`,
 | 
					                        `, expected '${vtypeToString(paramVType)}'`,
 | 
				
			||||||
                    pos,
 | 
					                    pos,
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
@ -162,6 +220,18 @@ export class Checker {
 | 
				
			|||||||
        stmt.kind.param.vtype = value;
 | 
					        stmt.kind.param.vtype = value;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public checkTypeAliasStmt(stmt: Stmt) {
 | 
				
			||||||
 | 
					        if (stmt.kind.type !== "type_alias") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const pos = stmt.pos;
 | 
				
			||||||
 | 
					        if (!stmt.kind.param.etype) {
 | 
				
			||||||
 | 
					            this.report("no type specified", pos);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        stmt.kind.param.vtype = this.checkEType(stmt.kind.param.etype);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public checkAssignStmt(stmt: Stmt) {
 | 
					    public checkAssignStmt(stmt: Stmt) {
 | 
				
			||||||
        if (stmt.kind.type !== "assign") {
 | 
					        if (stmt.kind.type !== "assign") {
 | 
				
			||||||
            throw new Error();
 | 
					            throw new Error();
 | 
				
			||||||
@ -178,13 +248,13 @@ export class Checker {
 | 
				
			|||||||
                    this.report("cannot use field on non-struct", pos);
 | 
					                    this.report("cannot use field on non-struct", pos);
 | 
				
			||||||
                    return { type: "error" };
 | 
					                    return { type: "error" };
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                const fieldValue = stmt.kind.subject.kind.value;
 | 
					                const fieldValue = stmt.kind.subject.kind.ident;
 | 
				
			||||||
                const found = subject.fields.find((param) =>
 | 
					                const found = subject.fields.find((param) =>
 | 
				
			||||||
                    param.ident === fieldValue
 | 
					                    param.ident === fieldValue
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
                if (!found) {
 | 
					                if (!found) {
 | 
				
			||||||
                    this.report(
 | 
					                    this.report(
 | 
				
			||||||
                        `no field named '${stmt.kind.subject.kind.value}' on struct`,
 | 
					                        `no field named '${stmt.kind.subject.kind.ident}' on struct`,
 | 
				
			||||||
                        pos,
 | 
					                        pos,
 | 
				
			||||||
                    );
 | 
					                    );
 | 
				
			||||||
                    return { type: "error" };
 | 
					                    return { type: "error" };
 | 
				
			||||||
@ -216,7 +286,7 @@ export class Checker {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                if (
 | 
					                if (
 | 
				
			||||||
                    subject.type == "array" &&
 | 
					                    subject.type == "array" &&
 | 
				
			||||||
                    !vtypesEqual(subject.inner, value)
 | 
					                    !vtypesEqual(subject.subject, value)
 | 
				
			||||||
                ) {
 | 
					                ) {
 | 
				
			||||||
                    this.report(
 | 
					                    this.report(
 | 
				
			||||||
                        `cannot assign incompatible type to array ` +
 | 
					                        `cannot assign incompatible type to array ` +
 | 
				
			||||||
@ -262,6 +332,9 @@ export class Checker {
 | 
				
			|||||||
                case "error":
 | 
					                case "error":
 | 
				
			||||||
                    throw new Error("error in AST");
 | 
					                    throw new Error("error in AST");
 | 
				
			||||||
                case "ident":
 | 
					                case "ident":
 | 
				
			||||||
 | 
					                    if (this.reporter.errorOccured()) {
 | 
				
			||||||
 | 
					                        return { type: "error" };
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                    throw new Error("ident expr in AST");
 | 
					                    throw new Error("ident expr in AST");
 | 
				
			||||||
                case "sym":
 | 
					                case "sym":
 | 
				
			||||||
                    return this.checkSymExpr(expr);
 | 
					                    return this.checkSymExpr(expr);
 | 
				
			||||||
@ -275,12 +348,26 @@ export class Checker {
 | 
				
			|||||||
                    return { type: "string" };
 | 
					                    return { type: "string" };
 | 
				
			||||||
                case "group":
 | 
					                case "group":
 | 
				
			||||||
                    return this.checkExpr(expr.kind.expr);
 | 
					                    return this.checkExpr(expr.kind.expr);
 | 
				
			||||||
 | 
					                case "ref":
 | 
				
			||||||
 | 
					                    return this.checkRefExpr(expr);
 | 
				
			||||||
 | 
					                case "ref_mut":
 | 
				
			||||||
 | 
					                    return this.checkRefMutExpr(expr);
 | 
				
			||||||
 | 
					                case "deref":
 | 
				
			||||||
 | 
					                    return this.checkDerefExpr(expr);
 | 
				
			||||||
 | 
					                case "array":
 | 
				
			||||||
 | 
					                    throw new Error("should have been desugared");
 | 
				
			||||||
 | 
					                case "struct":
 | 
				
			||||||
 | 
					                    return this.checkStructExpr(expr);
 | 
				
			||||||
                case "field":
 | 
					                case "field":
 | 
				
			||||||
                    return this.checkFieldExpr(expr);
 | 
					                    return this.checkFieldExpr(expr);
 | 
				
			||||||
                case "index":
 | 
					                case "index":
 | 
				
			||||||
                    return this.checkIndexExpr(expr);
 | 
					                    return this.checkIndexExpr(expr);
 | 
				
			||||||
                case "call":
 | 
					                case "call":
 | 
				
			||||||
                    return this.checkCallExpr(expr);
 | 
					                    return this.checkCallExpr(expr);
 | 
				
			||||||
 | 
					                case "path":
 | 
				
			||||||
 | 
					                    return this.checkPathExpr(expr);
 | 
				
			||||||
 | 
					                case "etype_args":
 | 
				
			||||||
 | 
					                    return this.checkETypeArgsExpr(expr);
 | 
				
			||||||
                case "unary":
 | 
					                case "unary":
 | 
				
			||||||
                    return this.checkUnaryExpr(expr);
 | 
					                    return this.checkUnaryExpr(expr);
 | 
				
			||||||
                case "binary":
 | 
					                case "binary":
 | 
				
			||||||
@ -307,11 +394,17 @@ export class Checker {
 | 
				
			|||||||
        if (expr.kind.type !== "sym") {
 | 
					        if (expr.kind.type !== "sym") {
 | 
				
			||||||
            throw new Error();
 | 
					            throw new Error();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        switch (expr.kind.sym.type) {
 | 
					        return this.checkSym(expr.kind.sym);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private checkSym(sym: Sym): VType {
 | 
				
			||||||
 | 
					        switch (sym.type) {
 | 
				
			||||||
            case "let":
 | 
					            case "let":
 | 
				
			||||||
                return expr.kind.sym.param.vtype!;
 | 
					                return sym.param.vtype!;
 | 
				
			||||||
 | 
					            case "type_alias":
 | 
				
			||||||
 | 
					                return sym.param.vtype!;
 | 
				
			||||||
            case "fn": {
 | 
					            case "fn": {
 | 
				
			||||||
                const fnStmt = expr.kind.sym.stmt!;
 | 
					                const fnStmt = sym.stmt!;
 | 
				
			||||||
                if (fnStmt.kind.type !== "fn") {
 | 
					                if (fnStmt.kind.type !== "fn") {
 | 
				
			||||||
                    throw new Error();
 | 
					                    throw new Error();
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -319,20 +412,117 @@ export class Checker {
 | 
				
			|||||||
                if (vtype.type !== "fn") {
 | 
					                if (vtype.type !== "fn") {
 | 
				
			||||||
                    throw new Error();
 | 
					                    throw new Error();
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                const { params, returnType } = vtype;
 | 
					                return vtype;
 | 
				
			||||||
                return { type: "fn", params, returnType };
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            case "fn_param":
 | 
					            case "fn_param":
 | 
				
			||||||
                return expr.kind.sym.param.vtype!;
 | 
					                return sym.param.vtype!;
 | 
				
			||||||
            case "builtin":
 | 
					 | 
				
			||||||
            case "let_static":
 | 
					            case "let_static":
 | 
				
			||||||
            case "closure":
 | 
					            case "closure":
 | 
				
			||||||
 | 
					            case "generic":
 | 
				
			||||||
                throw new Error(
 | 
					                throw new Error(
 | 
				
			||||||
                    `not implemented, sym type '${expr.kind.sym.type}'`,
 | 
					                    `not implemented, sym type '${sym.type}'`,
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
 | 
					            case "mod":
 | 
				
			||||||
 | 
					                throw new Error("should already be resolved");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public checkRefExpr(expr: Expr): VType {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "ref") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const subject = this.checkExpr(expr.kind.subject);
 | 
				
			||||||
 | 
					        if (expr.kind.subject.kind.type === "sym") {
 | 
				
			||||||
 | 
					            const sym = expr.kind.subject.kind.sym;
 | 
				
			||||||
 | 
					            if (sym.type === "let" || sym.type === "fn_param") {
 | 
				
			||||||
 | 
					                return { type: "ref", subject };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            this.report(
 | 
				
			||||||
 | 
					                `taking reference to symbol type '${sym.type}' not supported`,
 | 
				
			||||||
 | 
					                expr.pos,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            return { type: "error" };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.report(
 | 
				
			||||||
 | 
					            `taking reference to expression type '${
 | 
				
			||||||
 | 
					                forceType(expr.kind.subject.kind).type
 | 
				
			||||||
 | 
					            }' not supported`,
 | 
				
			||||||
 | 
					            expr.pos,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        return { type: "error" };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public checkRefMutExpr(expr: Expr): VType {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "ref_mut") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const subject = this.checkExpr(expr.kind.subject);
 | 
				
			||||||
 | 
					        if (expr.kind.subject.kind.type === "sym") {
 | 
				
			||||||
 | 
					            const sym = expr.kind.subject.kind.sym;
 | 
				
			||||||
 | 
					            if (sym.type === "let" || sym.type === "fn_param") {
 | 
				
			||||||
 | 
					                if (!sym.param.mut) {
 | 
				
			||||||
 | 
					                    this.report(
 | 
				
			||||||
 | 
					                        `symbol '${sym.ident}' it not declared mutable`,
 | 
				
			||||||
 | 
					                        expr.pos,
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    this.reporter.addNote({
 | 
				
			||||||
 | 
					                        reporter: "checker",
 | 
				
			||||||
 | 
					                        msg: "symbol defined here",
 | 
				
			||||||
 | 
					                        pos: sym.param.pos,
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                    return { type: "error" };
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return { type: "ref_mut", subject };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            this.report(
 | 
				
			||||||
 | 
					                `taking reference to symbol type '${sym.type}' not supported`,
 | 
				
			||||||
 | 
					                expr.pos,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            return { type: "error" };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.report(
 | 
				
			||||||
 | 
					            `taking mutable reference to expression type '${
 | 
				
			||||||
 | 
					                forceType(expr.kind.subject.kind).type
 | 
				
			||||||
 | 
					            }' not supported`,
 | 
				
			||||||
 | 
					            expr.pos,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        return { type: "error" };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public checkDerefExpr(expr: Expr): VType {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "deref") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const subject = this.checkExpr(expr.kind.subject);
 | 
				
			||||||
 | 
					        switch (subject.type) {
 | 
				
			||||||
 | 
					            case "ref":
 | 
				
			||||||
 | 
					                return subject.subject;
 | 
				
			||||||
 | 
					            case "ref_mut":
 | 
				
			||||||
 | 
					                return subject.subject;
 | 
				
			||||||
 | 
					            case "ptr":
 | 
				
			||||||
 | 
					                return subject.subject;
 | 
				
			||||||
 | 
					            case "ptr_mut":
 | 
				
			||||||
 | 
					                return subject.subject;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.report(
 | 
				
			||||||
 | 
					            `dereferenced type is neither a reference nor a pointer`,
 | 
				
			||||||
 | 
					            expr.pos,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        return { type: "error" };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public checkStructExpr(expr: Expr): VType {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "struct") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const fields: VTypeParam[] = expr.kind.fields
 | 
				
			||||||
 | 
					            .map(({ ident, expr }): VTypeParam => ({
 | 
				
			||||||
 | 
					                ident,
 | 
				
			||||||
 | 
					                mut: true,
 | 
				
			||||||
 | 
					                vtype: this.checkExpr(expr),
 | 
				
			||||||
 | 
					            }));
 | 
				
			||||||
 | 
					        return { type: "struct", fields };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public checkFieldExpr(expr: Expr): VType {
 | 
					    public checkFieldExpr(expr: Expr): VType {
 | 
				
			||||||
        if (expr.kind.type !== "field") {
 | 
					        if (expr.kind.type !== "field") {
 | 
				
			||||||
            throw new Error();
 | 
					            throw new Error();
 | 
				
			||||||
@ -343,11 +533,11 @@ export class Checker {
 | 
				
			|||||||
            this.report("cannot use field on non-struct", pos);
 | 
					            this.report("cannot use field on non-struct", pos);
 | 
				
			||||||
            return { type: "error" };
 | 
					            return { type: "error" };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const value = expr.kind.value;
 | 
					        const value = expr.kind.ident;
 | 
				
			||||||
        const found = subject.fields.find((param) => param.ident === value);
 | 
					        const found = subject.fields.find((param) => param.ident === value);
 | 
				
			||||||
        if (!found) {
 | 
					        if (!found) {
 | 
				
			||||||
            this.report(
 | 
					            this.report(
 | 
				
			||||||
                `no field named '${expr.kind.value}' on struct`,
 | 
					                `no field named '${expr.kind.ident}' on struct`,
 | 
				
			||||||
                pos,
 | 
					                pos,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
            return { type: "error" };
 | 
					            return { type: "error" };
 | 
				
			||||||
@ -371,7 +561,7 @@ export class Checker {
 | 
				
			|||||||
            return { type: "error" };
 | 
					            return { type: "error" };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (subject.type === "array") {
 | 
					        if (subject.type === "array") {
 | 
				
			||||||
            return subject.inner;
 | 
					            return subject.subject;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return { type: "int" };
 | 
					        return { type: "int" };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -382,18 +572,62 @@ export class Checker {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        const pos = expr.pos;
 | 
					        const pos = expr.pos;
 | 
				
			||||||
        const subject = this.checkExpr(expr.kind.subject);
 | 
					        const subject = this.checkExpr(expr.kind.subject);
 | 
				
			||||||
        if (subject.type !== "fn") {
 | 
					        if (subject.type === "error") return subject;
 | 
				
			||||||
            this.report("cannot call non-fn", pos);
 | 
					        if (subject.type === "fn") {
 | 
				
			||||||
            return { type: "error" };
 | 
					            if (expr.kind.args.length !== subject.params.length) {
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        const args = expr.kind.args.map((arg) => this.checkExpr(arg));
 | 
					 | 
				
			||||||
        if (args.length !== subject.params.length) {
 | 
					 | 
				
			||||||
                this.report(
 | 
					                this.report(
 | 
				
			||||||
                `incorrect number of arguments` +
 | 
					                    `expected ${subject.params.length} arguments` +
 | 
				
			||||||
                    `, expected ${subject.params.length}`,
 | 
					                        `, got ${expr.kind.args.length}`,
 | 
				
			||||||
                    pos,
 | 
					                    pos,
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            const args = expr.kind.args.map((arg) => this.checkExpr(arg));
 | 
				
			||||||
 | 
					            if (args.some((arg) => arg.type === "error")) {
 | 
				
			||||||
 | 
					                return { type: "error" };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (subject.genericParams === undefined) {
 | 
				
			||||||
 | 
					                return this.checkCallExprNoGenericsTail(
 | 
				
			||||||
 | 
					                    expr,
 | 
				
			||||||
 | 
					                    subject,
 | 
				
			||||||
 | 
					                    args,
 | 
				
			||||||
 | 
					                    pos,
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return this.checkCallExprInferredGenericsTail(
 | 
				
			||||||
 | 
					                expr,
 | 
				
			||||||
 | 
					                subject,
 | 
				
			||||||
 | 
					                args,
 | 
				
			||||||
 | 
					                pos,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (subject.type === "generic_spec" && subject.subject.type === "fn") {
 | 
				
			||||||
 | 
					            return this.checkCallExprExplicitGenericsTail(expr, subject);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.report("cannot call non-fn", pos);
 | 
				
			||||||
 | 
					        return { type: "error" };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private checkCallExprNoGenericsTail(
 | 
				
			||||||
 | 
					        expr: Expr,
 | 
				
			||||||
 | 
					        subject: VType,
 | 
				
			||||||
 | 
					        args: VType[],
 | 
				
			||||||
 | 
					        pos: Pos,
 | 
				
			||||||
 | 
					    ): VType {
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            expr.kind.type !== "call" || subject.type !== "fn"
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (let i = 0; i < args.length; ++i) {
 | 
				
			||||||
 | 
					            if (this.vtypeContainsGeneric(args[i])) {
 | 
				
			||||||
 | 
					                this.report(
 | 
				
			||||||
 | 
					                    `amfibious generic parameter for argument ${i}, please specify generic types explicitly`,
 | 
				
			||||||
 | 
					                    pos,
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                return { type: "error" };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (let i = 0; i < args.length; ++i) {
 | 
					        for (let i = 0; i < args.length; ++i) {
 | 
				
			||||||
            if (!vtypesEqual(args[i], subject.params[i].vtype)) {
 | 
					            if (!vtypesEqual(args[i], subject.params[i].vtype)) {
 | 
				
			||||||
                this.report(
 | 
					                this.report(
 | 
				
			||||||
@ -405,15 +639,283 @@ export class Checker {
 | 
				
			|||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return subject.returnType;
 | 
					        return subject.returnType;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private checkCallExprInferredGenericsTail(
 | 
				
			||||||
 | 
					        expr: Expr,
 | 
				
			||||||
 | 
					        subject: VType,
 | 
				
			||||||
 | 
					        args: VType[],
 | 
				
			||||||
 | 
					        pos: Pos,
 | 
				
			||||||
 | 
					    ): VType {
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            expr.kind.type !== "call" || subject.type !== "fn" ||
 | 
				
			||||||
 | 
					            subject.genericParams === undefined
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const genericArgsRes = this.inferGenericArgs(
 | 
				
			||||||
 | 
					            subject.genericParams,
 | 
				
			||||||
 | 
					            subject.params,
 | 
				
			||||||
 | 
					            args,
 | 
				
			||||||
 | 
					            pos,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        if (!genericArgsRes.ok) {
 | 
				
			||||||
 | 
					            return { type: "error" };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const genericArgs = genericArgsRes.value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (let i = 0; i < args.length; ++i) {
 | 
				
			||||||
 | 
					            const vtypeCompatible = vtypesEqual(
 | 
				
			||||||
 | 
					                args[i],
 | 
				
			||||||
 | 
					                subject.params[i].vtype,
 | 
				
			||||||
 | 
					                genericArgs,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            if (!vtypeCompatible) {
 | 
				
			||||||
 | 
					                this.report(
 | 
				
			||||||
 | 
					                    `incorrect argument ${i} '${subject.params[i].ident}'` +
 | 
				
			||||||
 | 
					                        `, expected ${
 | 
				
			||||||
 | 
					                            vtypeToString(
 | 
				
			||||||
 | 
					                                extractGenericType(
 | 
				
			||||||
 | 
					                                    subject.params[i].vtype,
 | 
				
			||||||
 | 
					                                    genericArgs,
 | 
				
			||||||
 | 
					                                ),
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                        }` +
 | 
				
			||||||
 | 
					                        `, got ${vtypeToString(args[i])}`,
 | 
				
			||||||
 | 
					                    pos,
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expr.kind.genericArgs = genericArgs;
 | 
				
			||||||
 | 
					        return this.concretizeVType(subject.returnType, genericArgs);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private inferGenericArgs(
 | 
				
			||||||
 | 
					        genericParams: VTypeGenericParam[],
 | 
				
			||||||
 | 
					        params: VTypeParam[],
 | 
				
			||||||
 | 
					        args: VType[],
 | 
				
			||||||
 | 
					        pos: Pos,
 | 
				
			||||||
 | 
					    ): { ok: true; value: GenericArgsMap } | { ok: false } {
 | 
				
			||||||
 | 
					        const genericArgs: GenericArgsMap = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (let i = 0; i < params.length; ++i) {
 | 
				
			||||||
 | 
					            if (!this.vtypeContainsGeneric(params[i].vtype)) {
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const {
 | 
				
			||||||
 | 
					                a: generic,
 | 
				
			||||||
 | 
					                b: concrete,
 | 
				
			||||||
 | 
					            } = this.reduceToSignificant(params[i].vtype, args[i]);
 | 
				
			||||||
 | 
					            if (generic.type !== "generic") {
 | 
				
			||||||
 | 
					                throw new Error();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const paramId = generic.param.id;
 | 
				
			||||||
 | 
					            if (
 | 
				
			||||||
 | 
					                paramId in genericArgs &&
 | 
				
			||||||
 | 
					                !vtypesEqual(genericArgs[paramId], concrete)
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                this.report(
 | 
				
			||||||
 | 
					                    `according to inferrence, argument ${i} has a conflicting type`,
 | 
				
			||||||
 | 
					                    pos,
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                return { ok: false };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            genericArgs[paramId] = concrete;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const param of genericParams) {
 | 
				
			||||||
 | 
					            if (!(param.id in genericArgs)) {
 | 
				
			||||||
 | 
					                this.report(`could not infer generic type ${param.ident}`, pos);
 | 
				
			||||||
 | 
					                return { ok: false };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return { ok: true, value: genericArgs };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private reduceToSignificant(a: VType, b: VType): { a: VType; b: VType } {
 | 
				
			||||||
 | 
					        if (a.type !== b.type) {
 | 
				
			||||||
 | 
					            return { a, b };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (a.type === "array" && b.type === "array") {
 | 
				
			||||||
 | 
					            return this.reduceToSignificant(a.subject, b.subject);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (a.type === "generic" && b.type === "generic") {
 | 
				
			||||||
 | 
					            return { a, b };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw new Error("idk what to do here");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private vtypeContainsGeneric(vtype: VType): boolean {
 | 
				
			||||||
 | 
					        switch (vtype.type) {
 | 
				
			||||||
 | 
					            case "error":
 | 
				
			||||||
 | 
					            case "string":
 | 
				
			||||||
 | 
					            case "unknown":
 | 
				
			||||||
 | 
					            case "null":
 | 
				
			||||||
 | 
					            case "int":
 | 
				
			||||||
 | 
					            case "bool":
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            case "ref":
 | 
				
			||||||
 | 
					            case "ref_mut":
 | 
				
			||||||
 | 
					            case "ptr":
 | 
				
			||||||
 | 
					            case "ptr_mut":
 | 
				
			||||||
 | 
					                return this.vtypeContainsGeneric(vtype.subject);
 | 
				
			||||||
 | 
					            case "array":
 | 
				
			||||||
 | 
					                return this.vtypeContainsGeneric(vtype.subject);
 | 
				
			||||||
 | 
					            case "struct":
 | 
				
			||||||
 | 
					                return vtype.fields.some((field) =>
 | 
				
			||||||
 | 
					                    this.vtypeContainsGeneric(field.vtype)
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            case "fn":
 | 
				
			||||||
 | 
					                throw new Error("not implemented");
 | 
				
			||||||
 | 
					            case "generic":
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            case "generic_spec":
 | 
				
			||||||
 | 
					                throw new Error("listen kid, grrrrrrrr");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private checkCallExprExplicitGenericsTail(
 | 
				
			||||||
 | 
					        expr: Expr,
 | 
				
			||||||
 | 
					        subject: VType,
 | 
				
			||||||
 | 
					    ): VType {
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            expr.kind.type !== "call" || subject.type !== "generic_spec" ||
 | 
				
			||||||
 | 
					            subject.subject.type !== "fn"
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const pos = expr.pos;
 | 
				
			||||||
 | 
					        const inner = subject.subject;
 | 
				
			||||||
 | 
					        const params = inner.params;
 | 
				
			||||||
 | 
					        const args = expr.kind.args.map((arg) => this.checkExpr(arg));
 | 
				
			||||||
 | 
					        if (args.length !== params.length) {
 | 
				
			||||||
 | 
					            this.report(
 | 
				
			||||||
 | 
					                `expected ${params.length} arguments` +
 | 
				
			||||||
 | 
					                    `, got ${args.length}`,
 | 
				
			||||||
 | 
					                pos,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (let i = 0; i < args.length; ++i) {
 | 
				
			||||||
 | 
					            const vtypeCompatible = vtypesEqual(
 | 
				
			||||||
 | 
					                args[i],
 | 
				
			||||||
 | 
					                params[i].vtype,
 | 
				
			||||||
 | 
					                subject.genericArgs,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            if (!vtypeCompatible) {
 | 
				
			||||||
 | 
					                this.report(
 | 
				
			||||||
 | 
					                    `incorrect argument ${i} '${inner.params[i].ident}'` +
 | 
				
			||||||
 | 
					                        `, expected ${
 | 
				
			||||||
 | 
					                            vtypeToString(
 | 
				
			||||||
 | 
					                                extractGenericType(
 | 
				
			||||||
 | 
					                                    params[i].vtype,
 | 
				
			||||||
 | 
					                                    subject.genericArgs,
 | 
				
			||||||
 | 
					                                ),
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                        }` +
 | 
				
			||||||
 | 
					                        `, got ${vtypeToString(args[i])}`,
 | 
				
			||||||
 | 
					                    pos,
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expr.kind.genericArgs = subject.genericArgs;
 | 
				
			||||||
 | 
					        return this.concretizeVType(
 | 
				
			||||||
 | 
					            subject.subject.returnType,
 | 
				
			||||||
 | 
					            subject.genericArgs,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private concretizeVType(
 | 
				
			||||||
 | 
					        vtype: VType,
 | 
				
			||||||
 | 
					        generics: GenericArgsMap,
 | 
				
			||||||
 | 
					    ): VType {
 | 
				
			||||||
 | 
					        switch (vtype.type) {
 | 
				
			||||||
 | 
					            case "error":
 | 
				
			||||||
 | 
					            case "unknown":
 | 
				
			||||||
 | 
					            case "string":
 | 
				
			||||||
 | 
					            case "null":
 | 
				
			||||||
 | 
					            case "int":
 | 
				
			||||||
 | 
					            case "bool":
 | 
				
			||||||
 | 
					                return vtype;
 | 
				
			||||||
 | 
					            case "ref":
 | 
				
			||||||
 | 
					            case "ref_mut":
 | 
				
			||||||
 | 
					            case "ptr":
 | 
				
			||||||
 | 
					            case "ptr_mut":
 | 
				
			||||||
 | 
					            case "array":
 | 
				
			||||||
 | 
					                return {
 | 
				
			||||||
 | 
					                    type: vtype.type,
 | 
				
			||||||
 | 
					                    subject: this.concretizeVType(vtype.subject, generics),
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            case "struct":
 | 
				
			||||||
 | 
					                return {
 | 
				
			||||||
 | 
					                    type: "struct",
 | 
				
			||||||
 | 
					                    fields: vtype.fields.map((field) => ({
 | 
				
			||||||
 | 
					                        ...field,
 | 
				
			||||||
 | 
					                        vtype: this.concretizeVType(field.vtype, generics),
 | 
				
			||||||
 | 
					                    })),
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            case "fn":
 | 
				
			||||||
 | 
					                throw new Error("not implemented");
 | 
				
			||||||
 | 
					            case "generic":
 | 
				
			||||||
 | 
					                return generics[vtype.param.id];
 | 
				
			||||||
 | 
					            case "generic_spec":
 | 
				
			||||||
 | 
					                throw new Error("not implemented");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public checkPathExpr(expr: Expr): VType {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "path") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw new Error("should already be resolved");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public checkETypeArgsExpr(expr: Expr): VType {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "etype_args") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const pos = expr.pos;
 | 
				
			||||||
 | 
					        const subject = this.checkExpr(expr.kind.subject);
 | 
				
			||||||
 | 
					        if (subject.type !== "fn" || subject.genericParams === undefined) {
 | 
				
			||||||
 | 
					            this.report(
 | 
				
			||||||
 | 
					                "etype arguments must only be applied to generic functions",
 | 
				
			||||||
 | 
					                expr.pos,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            return { type: "error" };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const args = expr.kind.etypeArgs;
 | 
				
			||||||
 | 
					        if (args.length !== subject.genericParams.length) {
 | 
				
			||||||
 | 
					            this.report(
 | 
				
			||||||
 | 
					                `incorrect number of arguments` +
 | 
				
			||||||
 | 
					                    `, expected ${subject.params.length}`,
 | 
				
			||||||
 | 
					                pos,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const genericArgs: GenericArgsMap = {};
 | 
				
			||||||
 | 
					        for (let i = 0; i < args.length; ++i) {
 | 
				
			||||||
 | 
					            const etype = this.checkEType(args[i]);
 | 
				
			||||||
 | 
					            genericArgs[subject.genericParams[i].id] = etype;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            type: "generic_spec",
 | 
				
			||||||
 | 
					            subject,
 | 
				
			||||||
 | 
					            genericArgs,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public checkUnaryExpr(expr: Expr): VType {
 | 
					    public checkUnaryExpr(expr: Expr): VType {
 | 
				
			||||||
        if (expr.kind.type !== "unary") {
 | 
					        if (expr.kind.type !== "unary") {
 | 
				
			||||||
            throw new Error();
 | 
					            throw new Error();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const pos = expr.pos;
 | 
					        const pos = expr.pos;
 | 
				
			||||||
        const subject = this.checkExpr(expr.kind.subject);
 | 
					        const subject = this.checkExpr(expr.kind.subject);
 | 
				
			||||||
 | 
					        if (subject.type === "error") return subject;
 | 
				
			||||||
        for (const operation of simpleUnaryOperations) {
 | 
					        for (const operation of simpleUnaryOperations) {
 | 
				
			||||||
            if (operation.unaryType !== expr.kind.unaryType) {
 | 
					            if (operation.unaryType !== expr.kind.unaryType) {
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
@ -437,7 +939,9 @@ export class Checker {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        const pos = expr.pos;
 | 
					        const pos = expr.pos;
 | 
				
			||||||
        const left = this.checkExpr(expr.kind.left);
 | 
					        const left = this.checkExpr(expr.kind.left);
 | 
				
			||||||
 | 
					        if (left.type === "error") return left;
 | 
				
			||||||
        const right = this.checkExpr(expr.kind.right);
 | 
					        const right = this.checkExpr(expr.kind.right);
 | 
				
			||||||
 | 
					        if (right.type === "error") return right;
 | 
				
			||||||
        for (const operation of simpleBinaryOperations) {
 | 
					        for (const operation of simpleBinaryOperations) {
 | 
				
			||||||
            if (operation.binaryType !== expr.kind.binaryType) {
 | 
					            if (operation.binaryType !== expr.kind.binaryType) {
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
@ -466,10 +970,13 @@ export class Checker {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        const pos = expr.pos;
 | 
					        const pos = expr.pos;
 | 
				
			||||||
        const cond = this.checkExpr(expr.kind.cond);
 | 
					        const cond = this.checkExpr(expr.kind.cond);
 | 
				
			||||||
 | 
					        if (cond.type === "error") return cond;
 | 
				
			||||||
        const truthy = this.checkExpr(expr.kind.truthy);
 | 
					        const truthy = this.checkExpr(expr.kind.truthy);
 | 
				
			||||||
 | 
					        if (truthy.type === "error") return truthy;
 | 
				
			||||||
        const falsy = expr.kind.falsy
 | 
					        const falsy = expr.kind.falsy
 | 
				
			||||||
            ? this.checkExpr(expr.kind.falsy)
 | 
					            ? this.checkExpr(expr.kind.falsy)
 | 
				
			||||||
            : undefined;
 | 
					            : undefined;
 | 
				
			||||||
 | 
					        if (falsy?.type === "error") return falsy;
 | 
				
			||||||
        if (cond.type !== "bool") {
 | 
					        if (cond.type !== "bool") {
 | 
				
			||||||
            this.report(
 | 
					            this.report(
 | 
				
			||||||
                `if condition should be 'bool', got '${vtypeToString(cond)}'`,
 | 
					                `if condition should be 'bool', got '${vtypeToString(cond)}'`,
 | 
				
			||||||
@ -545,7 +1052,7 @@ export class Checker {
 | 
				
			|||||||
        if (expr.kind.type !== "block") {
 | 
					        if (expr.kind.type !== "block") {
 | 
				
			||||||
            throw new Error();
 | 
					            throw new Error();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.checkFnHeaders(expr.kind.stmts);
 | 
					        this.scout(expr.kind.stmts);
 | 
				
			||||||
        for (const stmt of expr.kind.stmts) {
 | 
					        for (const stmt of expr.kind.stmts) {
 | 
				
			||||||
            this.checkStmt(stmt);
 | 
					            this.checkStmt(stmt);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -556,25 +1063,54 @@ export class Checker {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public checkEType(etype: EType): VType {
 | 
					    public checkEType(etype: EType): VType {
 | 
				
			||||||
        const pos = etype.pos;
 | 
					        const pos = etype.pos;
 | 
				
			||||||
        if (etype.kind.type === "ident") {
 | 
					        switch (etype.kind.type) {
 | 
				
			||||||
            if (etype.kind.value === "null") {
 | 
					            case "null":
 | 
				
			||||||
                return { type: "null" };
 | 
					                return { type: "null" };
 | 
				
			||||||
            }
 | 
					            case "int":
 | 
				
			||||||
            if (etype.kind.value === "int") {
 | 
					 | 
				
			||||||
                return { type: "int" };
 | 
					                return { type: "int" };
 | 
				
			||||||
            }
 | 
					            case "bool":
 | 
				
			||||||
            if (etype.kind.value === "bool") {
 | 
					 | 
				
			||||||
                return { type: "bool" };
 | 
					                return { type: "bool" };
 | 
				
			||||||
            }
 | 
					            case "string":
 | 
				
			||||||
            if (etype.kind.value === "string") {
 | 
					 | 
				
			||||||
                return { type: "string" };
 | 
					                return { type: "string" };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
            this.report(`undefined type '${etype.kind.value}'`, pos);
 | 
					        if (etype.kind.type === "ident") {
 | 
				
			||||||
 | 
					            this.report(`undefined type '${etype.kind.ident}'`, pos);
 | 
				
			||||||
            return { type: "error" };
 | 
					            return { type: "error" };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (etype.kind.type === "sym") {
 | 
				
			||||||
 | 
					            if (etype.kind.sym.type === "type_alias") {
 | 
				
			||||||
 | 
					                return etype.kind.sym.param.vtype!;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (etype.kind.sym.type === "generic") {
 | 
				
			||||||
 | 
					                const { id: globalId, ident } = etype.kind.sym.genericParam;
 | 
				
			||||||
 | 
					                if (!this.globalIdToGenericParamMap.has(globalId)) {
 | 
				
			||||||
 | 
					                    throw new Error();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                const { id } = this.globalIdToGenericParamMap.get(globalId)!;
 | 
				
			||||||
 | 
					                return { type: "generic", param: { id, ident } };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            this.report(`sym type '${etype.kind.sym.type}' used as type`, pos);
 | 
				
			||||||
 | 
					            return { type: "error" };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (etype.kind.type === "ref") {
 | 
				
			||||||
 | 
					            const subject = this.checkEType(etype.kind.subject);
 | 
				
			||||||
 | 
					            return { type: "ref", subject };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (etype.kind.type === "ref_mut") {
 | 
				
			||||||
 | 
					            const subject = this.checkEType(etype.kind.subject);
 | 
				
			||||||
 | 
					            return { type: "ref", subject };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (etype.kind.type === "ptr") {
 | 
				
			||||||
 | 
					            const subject = this.checkEType(etype.kind.subject);
 | 
				
			||||||
 | 
					            return { type: "ptr", subject };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (etype.kind.type === "ptr_mut") {
 | 
				
			||||||
 | 
					            const subject = this.checkEType(etype.kind.subject);
 | 
				
			||||||
 | 
					            return { type: "ptr_mut", subject };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        if (etype.kind.type === "array") {
 | 
					        if (etype.kind.type === "array") {
 | 
				
			||||||
            const inner = this.checkEType(etype.kind.inner);
 | 
					            const subject = this.checkEType(etype.kind.subject);
 | 
				
			||||||
            return { type: "array", inner };
 | 
					            return { type: "array", subject };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (etype.kind.type === "struct") {
 | 
					        if (etype.kind.type === "struct") {
 | 
				
			||||||
            const noTypeTest = etype.kind.fields.reduce(
 | 
					            const noTypeTest = etype.kind.fields.reduce(
 | 
				
			||||||
@ -608,13 +1144,19 @@ export class Checker {
 | 
				
			|||||||
                this.report(`field ${declaredTwiceTest[2]} defined twice`, pos);
 | 
					                this.report(`field ${declaredTwiceTest[2]} defined twice`, pos);
 | 
				
			||||||
                return { type: "error" };
 | 
					                return { type: "error" };
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            const fields = etype.kind.fields.map((param): VTypeParam => ({
 | 
					            const fields = etype.kind.fields
 | 
				
			||||||
 | 
					                .map((param): VTypeParam => ({
 | 
				
			||||||
                    ident: param.ident,
 | 
					                    ident: param.ident,
 | 
				
			||||||
 | 
					                    mut: true,
 | 
				
			||||||
                    vtype: this.checkEType(param.etype!),
 | 
					                    vtype: this.checkEType(param.etype!),
 | 
				
			||||||
                }));
 | 
					                }));
 | 
				
			||||||
            return { type: "struct", fields };
 | 
					            return { type: "struct", fields };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        throw new Error(`unknown explicit type ${etype.kind.type}`);
 | 
					        if (etype.kind.type === "type_of") {
 | 
				
			||||||
 | 
					            const exprVType = this.checkExpr(etype.kind.expr);
 | 
				
			||||||
 | 
					            return exprVType;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw new Error(`unknown explicit type '${etype.kind.type}'`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private report(msg: string, pos: Pos) {
 | 
					    private report(msg: string, pos: Pos) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,17 +1,28 @@
 | 
				
			|||||||
import { AstCreator } from "./ast.ts";
 | 
					import { AstCreator, Mod, Stmt } from "./ast.ts";
 | 
				
			||||||
import { Checker } from "./checker.ts";
 | 
					import { Checker } from "./checker.ts";
 | 
				
			||||||
import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts";
 | 
					import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts";
 | 
				
			||||||
 | 
					import { StructLiteralDesugarer } from "./desugar/struct_literal.ts";
 | 
				
			||||||
import { SpecialLoopDesugarer } from "./desugar/special_loop.ts";
 | 
					import { SpecialLoopDesugarer } from "./desugar/special_loop.ts";
 | 
				
			||||||
import { Reporter } from "./info.ts";
 | 
					import { Reporter } from "./info.ts";
 | 
				
			||||||
import { Lexer } from "./lexer.ts";
 | 
					import { Lexer } from "./lexer.ts";
 | 
				
			||||||
 | 
					import { Monomorphizer } from "./mono.ts";
 | 
				
			||||||
import { FnNamesMap, Lowerer } from "./lowerer.ts";
 | 
					import { FnNamesMap, Lowerer } from "./lowerer.ts";
 | 
				
			||||||
import { Parser } from "./parser.ts";
 | 
					import { Parser } from "./parser.ts";
 | 
				
			||||||
import { Resolver } from "./resolver.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";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type CompiledFile = {
 | 
					import * as path from "jsr:@std/path";
 | 
				
			||||||
    filepath: string;
 | 
					import { lowerAst } from "./middle/lower_ast.ts";
 | 
				
			||||||
    program: number[];
 | 
					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 = {
 | 
					export type CompileResult = {
 | 
				
			||||||
    program: number[];
 | 
					    program: number[];
 | 
				
			||||||
@ -20,19 +31,22 @@ export type CompileResult = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export class Compiler {
 | 
					export class Compiler {
 | 
				
			||||||
    private astCreator = new AstCreator();
 | 
					    private astCreator = new AstCreator();
 | 
				
			||||||
    private reporter = new Reporter();
 | 
					    private reporter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public constructor(private startFilePath: string) {}
 | 
					    public constructor(private startFilePath: string) {
 | 
				
			||||||
 | 
					        this.reporter = new Reporter(this.startFilePath);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public async compile(): Promise<CompileResult> {
 | 
					    public async compile(): Promise<CompileResult> {
 | 
				
			||||||
        const text = await Deno.readTextFile(this.startFilePath);
 | 
					        const { ast } = new ModTree(
 | 
				
			||||||
 | 
					            this.startFilePath,
 | 
				
			||||||
        const lexer = new Lexer(text, this.reporter);
 | 
					            this.astCreator,
 | 
				
			||||||
 | 
					            this.reporter,
 | 
				
			||||||
        const parser = new Parser(lexer, this.astCreator, this.reporter);
 | 
					        ).resolve();
 | 
				
			||||||
        const ast = parser.parse();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        new SpecialLoopDesugarer(this.astCreator).desugar(ast);
 | 
					        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 Resolver(this.reporter).resolve(ast);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -40,16 +54,130 @@ export class Compiler {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        new Checker(this.reporter).check(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()) {
 | 
					        if (this.reporter.errorOccured()) {
 | 
				
			||||||
            console.error("Errors occurred, stopping compilation.");
 | 
					            console.error("Errors occurred, stopping compilation.");
 | 
				
			||||||
            Deno.exit(1);
 | 
					            Deno.exit(1);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const lowerer = new Lowerer(lexer.currentPos());
 | 
					        const mir = lowerAst(ast);
 | 
				
			||||||
        lowerer.lower(ast);
 | 
					 | 
				
			||||||
        // lowerer.printProgram();
 | 
					 | 
				
			||||||
        const { program, fnNames } = lowerer.finish();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return { program, fnNames };
 | 
					        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 };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										6
									
								
								compiler/deno.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								compiler/deno.lock
									
									
									
										generated
									
									
									
								
							@ -1,8 +1,14 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "version": "4",
 | 
					  "version": "4",
 | 
				
			||||||
  "specifiers": {
 | 
					  "specifiers": {
 | 
				
			||||||
 | 
					    "jsr:@std/path@*": "1.0.8",
 | 
				
			||||||
    "npm:@types/node@*": "22.5.4"
 | 
					    "npm:@types/node@*": "22.5.4"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  "jsr": {
 | 
				
			||||||
 | 
					    "@std/path@1.0.8": {
 | 
				
			||||||
 | 
					      "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  "npm": {
 | 
					  "npm": {
 | 
				
			||||||
    "@types/node@22.5.4": {
 | 
					    "@types/node@22.5.4": {
 | 
				
			||||||
      "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
 | 
					      "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										94
									
								
								compiler/desugar/array_literal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								compiler/desugar/array_literal.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,94 @@
 | 
				
			|||||||
 | 
					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";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -70,29 +70,45 @@ export class SpecialLoopDesugarer implements AstVisitor {
 | 
				
			|||||||
            stmts: [
 | 
					            stmts: [
 | 
				
			||||||
                Stmt({
 | 
					                Stmt({
 | 
				
			||||||
                    type: "let",
 | 
					                    type: "let",
 | 
				
			||||||
                    param: { ident: "::values", pos: npos },
 | 
					                    param: this.astCreator.param({
 | 
				
			||||||
 | 
					                        ident: "::values",
 | 
				
			||||||
 | 
					                        mut: true,
 | 
				
			||||||
 | 
					                        pos: npos,
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
                    value: expr.kind.value,
 | 
					                    value: expr.kind.value,
 | 
				
			||||||
                }),
 | 
					                }),
 | 
				
			||||||
                Stmt({
 | 
					                Stmt({
 | 
				
			||||||
                    type: "let",
 | 
					                    type: "let",
 | 
				
			||||||
                    param: { ident: "::length", pos: npos },
 | 
					                    param: this.astCreator.param({
 | 
				
			||||||
 | 
					                        ident: "::length",
 | 
				
			||||||
 | 
					                        mut: false,
 | 
				
			||||||
 | 
					                        pos: npos,
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
                    value: Expr({
 | 
					                    value: Expr({
 | 
				
			||||||
                        type: "call",
 | 
					                        type: "call",
 | 
				
			||||||
 | 
					                        subject: Expr({
 | 
				
			||||||
 | 
					                            type: "path",
 | 
				
			||||||
                            subject: Expr({
 | 
					                            subject: Expr({
 | 
				
			||||||
                                type: "ident",
 | 
					                                type: "ident",
 | 
				
			||||||
                            value: "int_array_length",
 | 
					                                ident: "std",
 | 
				
			||||||
 | 
					                            }),
 | 
				
			||||||
 | 
					                            ident: "array_length",
 | 
				
			||||||
                        }),
 | 
					                        }),
 | 
				
			||||||
                        args: [
 | 
					                        args: [
 | 
				
			||||||
                            Expr({
 | 
					                            Expr({
 | 
				
			||||||
                                type: "ident",
 | 
					                                type: "ident",
 | 
				
			||||||
                                value: "::values",
 | 
					                                ident: "::values",
 | 
				
			||||||
                            }),
 | 
					                            }),
 | 
				
			||||||
                        ],
 | 
					                        ],
 | 
				
			||||||
                    }),
 | 
					                    }),
 | 
				
			||||||
                }),
 | 
					                }),
 | 
				
			||||||
                Stmt({
 | 
					                Stmt({
 | 
				
			||||||
                    type: "let",
 | 
					                    type: "let",
 | 
				
			||||||
                    param: { ident: "::index", pos: npos },
 | 
					                    param: this.astCreator.param({
 | 
				
			||||||
 | 
					                        ident: "::index",
 | 
				
			||||||
 | 
					                        mut: true,
 | 
				
			||||||
 | 
					                        pos: npos,
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
                    value: Expr({ type: "int", value: 0 }),
 | 
					                    value: Expr({ type: "int", value: 0 }),
 | 
				
			||||||
                }, expr.pos),
 | 
					                }, expr.pos),
 | 
				
			||||||
                Stmt({
 | 
					                Stmt({
 | 
				
			||||||
@ -114,11 +130,11 @@ export class SpecialLoopDesugarer implements AstVisitor {
 | 
				
			|||||||
                                                binaryType: "<",
 | 
					                                                binaryType: "<",
 | 
				
			||||||
                                                left: Expr({
 | 
					                                                left: Expr({
 | 
				
			||||||
                                                    type: "ident",
 | 
					                                                    type: "ident",
 | 
				
			||||||
                                                    value: "::index",
 | 
					                                                    ident: "::index",
 | 
				
			||||||
                                                }),
 | 
					                                                }),
 | 
				
			||||||
                                                right: Expr({
 | 
					                                                right: Expr({
 | 
				
			||||||
                                                    type: "ident",
 | 
					                                                    type: "ident",
 | 
				
			||||||
                                                    value: "::length",
 | 
					                                                    ident: "::length",
 | 
				
			||||||
                                                }),
 | 
					                                                }),
 | 
				
			||||||
                                            }),
 | 
					                                            }),
 | 
				
			||||||
                                        }),
 | 
					                                        }),
 | 
				
			||||||
@ -139,11 +155,11 @@ export class SpecialLoopDesugarer implements AstVisitor {
 | 
				
			|||||||
                                        type: "index",
 | 
					                                        type: "index",
 | 
				
			||||||
                                        subject: Expr({
 | 
					                                        subject: Expr({
 | 
				
			||||||
                                            type: "ident",
 | 
					                                            type: "ident",
 | 
				
			||||||
                                            value: "::values",
 | 
					                                            ident: "::values",
 | 
				
			||||||
                                        }),
 | 
					                                        }),
 | 
				
			||||||
                                        value: Expr({
 | 
					                                        value: Expr({
 | 
				
			||||||
                                            type: "ident",
 | 
					                                            type: "ident",
 | 
				
			||||||
                                            value: "::index",
 | 
					                                            ident: "::index",
 | 
				
			||||||
                                        }),
 | 
					                                        }),
 | 
				
			||||||
                                    }),
 | 
					                                    }),
 | 
				
			||||||
                                }, expr.pos),
 | 
					                                }, expr.pos),
 | 
				
			||||||
@ -156,7 +172,7 @@ export class SpecialLoopDesugarer implements AstVisitor {
 | 
				
			|||||||
                                    assignType: "+=",
 | 
					                                    assignType: "+=",
 | 
				
			||||||
                                    subject: Expr({
 | 
					                                    subject: Expr({
 | 
				
			||||||
                                        type: "ident",
 | 
					                                        type: "ident",
 | 
				
			||||||
                                        value: "::index",
 | 
					                                        ident: "::index",
 | 
				
			||||||
                                    }),
 | 
					                                    }),
 | 
				
			||||||
                                    value: Expr({
 | 
					                                    value: Expr({
 | 
				
			||||||
                                        type: "int",
 | 
					                                        type: "int",
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										101
									
								
								compiler/desugar/struct_literal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								compiler/desugar/struct_literal.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,101 @@
 | 
				
			|||||||
 | 
					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,7 +1,7 @@
 | 
				
			|||||||
import { Pos } from "./token.ts";
 | 
					import { Pos } from "./token.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type Report = {
 | 
					export type Report = {
 | 
				
			||||||
    type: "error" | "note";
 | 
					    type: "error" | "warning" | "note";
 | 
				
			||||||
    reporter: string;
 | 
					    reporter: string;
 | 
				
			||||||
    pos?: Pos;
 | 
					    pos?: Pos;
 | 
				
			||||||
    msg: string;
 | 
					    msg: string;
 | 
				
			||||||
@ -11,16 +11,27 @@ export class Reporter {
 | 
				
			|||||||
    private reports: Report[] = [];
 | 
					    private reports: Report[] = [];
 | 
				
			||||||
    private errorSet = false;
 | 
					    private errorSet = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public constructor(private filePath: string) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public setFilePath(filePath: string) {
 | 
				
			||||||
 | 
					        this.filePath = filePath;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public reportError(report: Omit<Report, "type">) {
 | 
					    public reportError(report: Omit<Report, "type">) {
 | 
				
			||||||
        this.reports.push({ ...report, type: "error" });
 | 
					        this.reports.push({ ...report, type: "error" });
 | 
				
			||||||
        this.printReport({ ...report, type: "error" });
 | 
					        this.printReport({ ...report, type: "error" });
 | 
				
			||||||
        this.errorSet = true;
 | 
					        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) {
 | 
					    private printReport({ reporter, type, pos, msg }: Report) {
 | 
				
			||||||
        console.error(
 | 
					        console.error(
 | 
				
			||||||
            `${reporter} ${type}: ${msg}${
 | 
					            `${reporter} ${type}: ${msg}${
 | 
				
			||||||
                pos ? ` at ${pos.line}:${pos.col}` : ""
 | 
					                pos ? `\n    at ${this.filePath}:${pos.line}:${pos.col}` : ""
 | 
				
			||||||
            }`,
 | 
					            }`,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -27,24 +27,31 @@ export class Lexer {
 | 
				
			|||||||
                this.step();
 | 
					                this.step();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            const keywords = [
 | 
					            const keywords = [
 | 
				
			||||||
 | 
					                "false",
 | 
				
			||||||
 | 
					                "true",
 | 
				
			||||||
 | 
					                "null",
 | 
				
			||||||
 | 
					                "int",
 | 
				
			||||||
 | 
					                "bool",
 | 
				
			||||||
 | 
					                "string",
 | 
				
			||||||
                "break",
 | 
					                "break",
 | 
				
			||||||
                "return",
 | 
					                "return",
 | 
				
			||||||
                "let",
 | 
					                "let",
 | 
				
			||||||
 | 
					                "mut",
 | 
				
			||||||
                "fn",
 | 
					                "fn",
 | 
				
			||||||
                "loop",
 | 
					                "loop",
 | 
				
			||||||
                "if",
 | 
					                "if",
 | 
				
			||||||
                "else",
 | 
					                "else",
 | 
				
			||||||
                "struct",
 | 
					                "struct",
 | 
				
			||||||
                "import",
 | 
					 | 
				
			||||||
                "false",
 | 
					 | 
				
			||||||
                "true",
 | 
					 | 
				
			||||||
                "null",
 | 
					 | 
				
			||||||
                "or",
 | 
					                "or",
 | 
				
			||||||
                "and",
 | 
					                "and",
 | 
				
			||||||
                "not",
 | 
					                "not",
 | 
				
			||||||
                "while",
 | 
					                "while",
 | 
				
			||||||
                "for",
 | 
					                "for",
 | 
				
			||||||
                "in",
 | 
					                "in",
 | 
				
			||||||
 | 
					                "mod",
 | 
				
			||||||
 | 
					                "pub",
 | 
				
			||||||
 | 
					                "use",
 | 
				
			||||||
 | 
					                "type_alias",
 | 
				
			||||||
            ];
 | 
					            ];
 | 
				
			||||||
            if (keywords.includes(value)) {
 | 
					            if (keywords.includes(value)) {
 | 
				
			||||||
                return this.token(value, pos);
 | 
					                return this.token(value, pos);
 | 
				
			||||||
@ -70,6 +77,32 @@ export class Lexer {
 | 
				
			|||||||
            return { ...this.token("int", pos), intValue: 0 };
 | 
					            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('"')) {
 | 
					        if (this.test('"')) {
 | 
				
			||||||
            this.step();
 | 
					            this.step();
 | 
				
			||||||
            let value = "";
 | 
					            let value = "";
 | 
				
			||||||
@ -96,7 +129,7 @@ export class Lexer {
 | 
				
			|||||||
            this.step();
 | 
					            this.step();
 | 
				
			||||||
            return { ...this.token("string", pos), stringValue: value };
 | 
					            return { ...this.token("string", pos), stringValue: value };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (this.test(/[\+\{\};=\-\*\(\)\.,:;\[\]><!0#]/)) {
 | 
					        if (this.test(/[\+\{\};=\-\*\(\)\.,:;\[\]><!0#&]/)) {
 | 
				
			||||||
            const first = this.current();
 | 
					            const first = this.current();
 | 
				
			||||||
            this.step();
 | 
					            this.step();
 | 
				
			||||||
            if (first === "=" && !this.done() && this.test("=")) {
 | 
					            if (first === "=" && !this.done() && this.test("=")) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,48 +1,65 @@
 | 
				
			|||||||
import { Builtins, Ops } from "./arch.ts";
 | 
					import { Builtins, Ops } from "./arch.ts";
 | 
				
			||||||
import { Expr, Stmt } from "./ast.ts";
 | 
					 | 
				
			||||||
import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts";
 | 
					 | 
				
			||||||
import { Assembler, Label } from "./assembler.ts";
 | 
					import { Assembler, Label } from "./assembler.ts";
 | 
				
			||||||
import { vtypeToString } from "./vtype.ts";
 | 
					import { AnnoView, 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 { Pos } from "./token.ts";
 | 
				
			||||||
 | 
					import { vtypeToString } from "./vtype.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type FnNamesMap = { [pc: number]: string };
 | 
					export type FnNamesMap = { [pc: number]: string };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Lowerer {
 | 
					export class Lowerer {
 | 
				
			||||||
    private program = Assembler.newRoot();
 | 
					    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 lastPos: Pos) {}
 | 
					    public constructor(
 | 
				
			||||||
 | 
					        private monoFns: MonoFnsMap,
 | 
				
			||||||
 | 
					        private callMap: MonoCallNameGenMap,
 | 
				
			||||||
 | 
					        private lastPos: Pos,
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public lower(stmts: Stmt[]) {
 | 
					    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() {
 | 
				
			||||||
        this.addClearingSourceMap();
 | 
					        this.addClearingSourceMap();
 | 
				
			||||||
        this.program.add(Ops.PushPtr, { label: "main" });
 | 
					        this.program.add(Ops.PushPtr, { label: "main" });
 | 
				
			||||||
        this.program.add(Ops.Call, 0);
 | 
					        this.program.add(Ops.Call, 0);
 | 
				
			||||||
        this.program.add(Ops.PushPtr, { label: "_exit" });
 | 
					        this.program.add(Ops.PushPtr, { label: "_exit" });
 | 
				
			||||||
        this.program.add(Ops.Jump);
 | 
					        this.program.add(Ops.Jump);
 | 
				
			||||||
        this.scoutFnHeaders(stmts);
 | 
					 | 
				
			||||||
        for (const stmt of stmts) {
 | 
					 | 
				
			||||||
            this.lowerStaticStmt(stmt);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private addConcluding() {
 | 
				
			||||||
        this.program.setLabel({ label: "_exit" });
 | 
					        this.program.setLabel({ label: "_exit" });
 | 
				
			||||||
        this.addSourceMap(this.lastPos);
 | 
					        this.addSourceMap(this.lastPos);
 | 
				
			||||||
        this.program.add(Ops.Pop);
 | 
					        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) {
 | 
					    private addSourceMap({ index, line, col }: Pos) {
 | 
				
			||||||
        this.program.add(Ops.SourceMap, index, line, col);
 | 
					        this.program.add(Ops.SourceMap, index, line, col);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -51,30 +68,84 @@ export class Lowerer {
 | 
				
			|||||||
        this.program.add(Ops.SourceMap, 0, 1, 1);
 | 
					        this.program.add(Ops.SourceMap, 0, 1, 1);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private scoutFnHeaders(stmts: Stmt[]) {
 | 
					    public printProgram() {
 | 
				
			||||||
        for (const stmt of stmts) {
 | 
					        this.program.printProgram();
 | 
				
			||||||
            if (stmt.kind.type !== "fn") {
 | 
					 | 
				
			||||||
                continue;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            const label = stmt.kind.ident === "main"
 | 
					 | 
				
			||||||
                ? "main"
 | 
					 | 
				
			||||||
                : `${stmt.kind.ident}_${stmt.id}`;
 | 
					 | 
				
			||||||
            this.fnStmtIdLabelMap[stmt.id] = label;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 lowerStaticStmt(stmt: Stmt) {
 | 
					    private lowerFnStmt(stmt: Stmt) {
 | 
				
			||||||
        switch (stmt.kind.type) {
 | 
					        if (stmt.kind.type !== "fn") {
 | 
				
			||||||
            case "fn":
 | 
					            throw new Error();
 | 
				
			||||||
                return this.lowerFnStmt(stmt);
 | 
					 | 
				
			||||||
            case "error":
 | 
					 | 
				
			||||||
            case "break":
 | 
					 | 
				
			||||||
            case "return":
 | 
					 | 
				
			||||||
            case "let":
 | 
					 | 
				
			||||||
            case "assign":
 | 
					 | 
				
			||||||
            case "expr":
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        throw new Error(`unhandled static statement '${stmt.kind.type}'`);
 | 
					        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();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            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);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private lowerStmt(stmt: Stmt) {
 | 
					    private lowerStmt(stmt: Stmt) {
 | 
				
			||||||
@ -107,7 +178,7 @@ export class Lowerer {
 | 
				
			|||||||
        switch (stmt.kind.subject.kind.type) {
 | 
					        switch (stmt.kind.subject.kind.type) {
 | 
				
			||||||
            case "field": {
 | 
					            case "field": {
 | 
				
			||||||
                this.lowerExpr(stmt.kind.subject.kind.subject);
 | 
					                this.lowerExpr(stmt.kind.subject.kind.subject);
 | 
				
			||||||
                this.program.add(Ops.PushString, stmt.kind.subject.kind.value);
 | 
					                this.program.add(Ops.PushString, stmt.kind.subject.kind.ident);
 | 
				
			||||||
                this.program.add(Ops.Builtin, Builtins.StructSet);
 | 
					                this.program.add(Ops.Builtin, Builtins.StructSet);
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -153,52 +224,6 @@ export class Lowerer {
 | 
				
			|||||||
        this.program.add(Ops.Jump);
 | 
					        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[]) {
 | 
					    private lowerFnBuiltinBody(annoArgs: Expr[]) {
 | 
				
			||||||
        if (annoArgs.length !== 1) {
 | 
					        if (annoArgs.length !== 1) {
 | 
				
			||||||
            throw new Error("invalid # of arguments to builtin annotation");
 | 
					            throw new Error("invalid # of arguments to builtin annotation");
 | 
				
			||||||
@ -209,7 +234,7 @@ export class Lowerer {
 | 
				
			|||||||
                `unexpected argument type '${anno.kind.type}' expected 'ident'`,
 | 
					                `unexpected argument type '${anno.kind.type}' expected 'ident'`,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const value = anno.kind.value;
 | 
					        const value = anno.kind.ident;
 | 
				
			||||||
        const builtin = Object.entries(Builtins).find((entry) =>
 | 
					        const builtin = Object.entries(Builtins).find((entry) =>
 | 
				
			||||||
            entry[0] === value
 | 
					            entry[0] === value
 | 
				
			||||||
        )?.[1];
 | 
					        )?.[1];
 | 
				
			||||||
@ -252,11 +277,13 @@ export class Lowerer {
 | 
				
			|||||||
            case "group":
 | 
					            case "group":
 | 
				
			||||||
                return void this.lowerExpr(expr.kind.expr);
 | 
					                return void this.lowerExpr(expr.kind.expr);
 | 
				
			||||||
            case "field":
 | 
					            case "field":
 | 
				
			||||||
                break;
 | 
					                return this.lowerFieldExpr(expr);
 | 
				
			||||||
            case "index":
 | 
					            case "index":
 | 
				
			||||||
                return this.lowerIndexExpr(expr);
 | 
					                return this.lowerIndexExpr(expr);
 | 
				
			||||||
            case "call":
 | 
					            case "call":
 | 
				
			||||||
                return this.lowerCallExpr(expr);
 | 
					                return this.lowerCallExpr(expr);
 | 
				
			||||||
 | 
					            case "etype_args":
 | 
				
			||||||
 | 
					                return this.lowerETypeArgsExpr(expr);
 | 
				
			||||||
            case "unary":
 | 
					            case "unary":
 | 
				
			||||||
                return this.lowerUnaryExpr(expr);
 | 
					                return this.lowerUnaryExpr(expr);
 | 
				
			||||||
            case "binary":
 | 
					            case "binary":
 | 
				
			||||||
@ -271,6 +298,20 @@ export class Lowerer {
 | 
				
			|||||||
        throw new Error(`unhandled expr '${expr.kind.type}'`);
 | 
					        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) {
 | 
					    private lowerIndexExpr(expr: Expr) {
 | 
				
			||||||
        if (expr.kind.type !== "index") {
 | 
					        if (expr.kind.type !== "index") {
 | 
				
			||||||
            throw new Error();
 | 
					            throw new Error();
 | 
				
			||||||
@ -306,8 +347,42 @@ export class Lowerer {
 | 
				
			|||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (expr.kind.sym.type === "fn") {
 | 
					        if (expr.kind.sym.type === "fn") {
 | 
				
			||||||
            const label = this.fnStmtIdLabelMap[expr.kind.sym.stmt.id];
 | 
					            // Is this smart? Well, my presumption is
 | 
				
			||||||
            this.program.add(Ops.PushPtr, { label });
 | 
					            // 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);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        throw new Error(`unhandled sym type '${expr.kind.sym.type}'`);
 | 
					        throw new Error(`unhandled sym type '${expr.kind.sym.type}'`);
 | 
				
			||||||
@ -430,6 +505,18 @@ export class Lowerer {
 | 
				
			|||||||
                default:
 | 
					                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 (vtype.type === "string") {
 | 
				
			||||||
            if (expr.kind.binaryType === "+") {
 | 
					            if (expr.kind.binaryType === "+") {
 | 
				
			||||||
                this.program.add(Ops.Builtin, Builtins.StringConcat);
 | 
					                this.program.add(Ops.Builtin, Builtins.StringConcat);
 | 
				
			||||||
@ -462,9 +549,18 @@ export class Lowerer {
 | 
				
			|||||||
            this.lowerExpr(arg);
 | 
					            this.lowerExpr(arg);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.lowerExpr(expr.kind.subject);
 | 
					        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);
 | 
					        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) {
 | 
					    private lowerIfExpr(expr: Expr) {
 | 
				
			||||||
        if (expr.kind.type !== "if") {
 | 
					        if (expr.kind.type !== "if") {
 | 
				
			||||||
            throw new Error();
 | 
					            throw new Error();
 | 
				
			||||||
@ -528,7 +624,6 @@ export class Lowerer {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        const outerLocals = this.locals;
 | 
					        const outerLocals = this.locals;
 | 
				
			||||||
        this.locals = new LocalLeaf(this.locals);
 | 
					        this.locals = new LocalLeaf(this.locals);
 | 
				
			||||||
        this.scoutFnHeaders(expr.kind.stmts);
 | 
					 | 
				
			||||||
        for (const stmt of expr.kind.stmts) {
 | 
					        for (const stmt of expr.kind.stmts) {
 | 
				
			||||||
            this.addSourceMap(stmt.pos);
 | 
					            this.addSourceMap(stmt.pos);
 | 
				
			||||||
            this.lowerStmt(stmt);
 | 
					            this.lowerStmt(stmt);
 | 
				
			||||||
@ -541,8 +636,4 @@ export class Lowerer {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        this.locals = outerLocals;
 | 
					        this.locals = outerLocals;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public printProgram() {
 | 
					 | 
				
			||||||
        this.program.printProgram();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										240
									
								
								compiler/middle/borrow_checker.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								compiler/middle/borrow_checker.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,240 @@
 | 
				
			|||||||
 | 
					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 === "local") {
 | 
				
			||||||
 | 
					            throw new Error("should be 'copy' or 'move'");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        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,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										107
									
								
								compiler/middle/cfg.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								compiler/middle/cfg.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					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]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										59
									
								
								compiler/middle/elim_blocks.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								compiler/middle/elim_blocks.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					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);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										81
									
								
								compiler/middle/elim_unused_local.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								compiler/middle/elim_unused_local.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					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 !== "local") {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.locals = this.locals.filter((lid) => lid !== local.id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										58
									
								
								compiler/middle/explicit_move_copy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								compiler/middle/explicit_move_copy.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					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(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 !== "local") {
 | 
				
			||||||
 | 
					            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":
 | 
				
			||||||
 | 
					        case "ref":
 | 
				
			||||||
 | 
					        case "ref_mut":
 | 
				
			||||||
 | 
					        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();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										513
									
								
								compiler/middle/lower_ast.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										513
									
								
								compiler/middle/lower_ast.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,513 @@
 | 
				
			|||||||
 | 
					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: "local", id };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										397
									
								
								compiler/middle/mir.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										397
									
								
								compiler/middle/mir.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,397 @@
 | 
				
			|||||||
 | 
					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: "drop"; 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: "local"; id: BlockId }
 | 
				
			||||||
 | 
					    | { 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":
 | 
				
			||||||
 | 
					            case "drop":
 | 
				
			||||||
 | 
					                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":
 | 
				
			||||||
 | 
					            case "drop":
 | 
				
			||||||
 | 
					                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 "drop":
 | 
				
			||||||
 | 
					                        l(`drop _${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 "local":
 | 
				
			||||||
 | 
					            return `_${rvalue.id}`;
 | 
				
			||||||
 | 
					        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;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										315
									
								
								compiler/mono.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								compiler/mono.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,315 @@
 | 
				
			|||||||
 | 
					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,5 +1,4 @@
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
    Anno,
 | 
					 | 
				
			||||||
    AssignType,
 | 
					    AssignType,
 | 
				
			||||||
    AstCreator,
 | 
					    AstCreator,
 | 
				
			||||||
    BinaryType,
 | 
					    BinaryType,
 | 
				
			||||||
@ -7,8 +6,11 @@ import {
 | 
				
			|||||||
    ETypeKind,
 | 
					    ETypeKind,
 | 
				
			||||||
    Expr,
 | 
					    Expr,
 | 
				
			||||||
    ExprKind,
 | 
					    ExprKind,
 | 
				
			||||||
 | 
					    Field,
 | 
				
			||||||
 | 
					    GenericParam,
 | 
				
			||||||
    Param,
 | 
					    Param,
 | 
				
			||||||
    Stmt,
 | 
					    Stmt,
 | 
				
			||||||
 | 
					    StmtDetails,
 | 
				
			||||||
    StmtKind,
 | 
					    StmtKind,
 | 
				
			||||||
    UnaryType,
 | 
					    UnaryType,
 | 
				
			||||||
} from "./ast.ts";
 | 
					} from "./ast.ts";
 | 
				
			||||||
@ -16,6 +18,8 @@ import { printStackTrace, Reporter } from "./info.ts";
 | 
				
			|||||||
import { Lexer } from "./lexer.ts";
 | 
					import { Lexer } from "./lexer.ts";
 | 
				
			||||||
import { Pos, Token } from "./token.ts";
 | 
					import { Pos, Token } from "./token.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Res<T> = { ok: true; value: T } | { ok: false; pos?: Pos };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Parser {
 | 
					export class Parser {
 | 
				
			||||||
    private currentToken: Token | null;
 | 
					    private currentToken: Token | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -34,25 +38,33 @@ export class Parser {
 | 
				
			|||||||
    private parseStmts(): Stmt[] {
 | 
					    private parseStmts(): Stmt[] {
 | 
				
			||||||
        const stmts: Stmt[] = [];
 | 
					        const stmts: Stmt[] = [];
 | 
				
			||||||
        while (!this.done()) {
 | 
					        while (!this.done()) {
 | 
				
			||||||
            if (this.test("fn")) {
 | 
					            stmts.push(this.parseStmt());
 | 
				
			||||||
                stmts.push(this.parseFn());
 | 
					        }
 | 
				
			||||||
            } else if (
 | 
					        return stmts;
 | 
				
			||||||
                this.test("let") || this.test("return") || this.test("break")
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseStmt(): Stmt {
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            ["#", "pub", "mod", "fn"].some((tt) => this.test(tt))
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
                stmts.push(this.parseSingleLineBlockStmt());
 | 
					            return this.parseItemStmt();
 | 
				
			||||||
 | 
					        } else if (
 | 
				
			||||||
 | 
					            ["let", "type_alias", "return", "break"].some((tt) => this.test(tt))
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            const expr = this.parseSingleLineBlockStmt();
 | 
				
			||||||
            this.eatSemicolon();
 | 
					            this.eatSemicolon();
 | 
				
			||||||
 | 
					            return expr;
 | 
				
			||||||
        } else if (
 | 
					        } else if (
 | 
				
			||||||
            ["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
 | 
					            ["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            const expr = this.parseMultiLineBlockExpr();
 | 
					            const expr = this.parseMultiLineBlockExpr();
 | 
				
			||||||
                stmts.push(this.stmt({ type: "expr", expr }, expr.pos));
 | 
					            return (this.stmt({ type: "expr", expr }, expr.pos));
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
                stmts.push(this.parseAssign());
 | 
					            const expr = this.parseAssign();
 | 
				
			||||||
            this.eatSemicolon();
 | 
					            this.eatSemicolon();
 | 
				
			||||||
 | 
					            return expr;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
        return stmts;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private parseMultiLineBlockExpr(): Expr {
 | 
					    private parseMultiLineBlockExpr(): Expr {
 | 
				
			||||||
        const pos = this.pos();
 | 
					        const pos = this.pos();
 | 
				
			||||||
@ -80,6 +92,9 @@ export class Parser {
 | 
				
			|||||||
        if (this.test("let")) {
 | 
					        if (this.test("let")) {
 | 
				
			||||||
            return this.parseLet();
 | 
					            return this.parseLet();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (this.test("type_alias")) {
 | 
				
			||||||
 | 
					            return this.parseTypeAlias();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        if (this.test("return")) {
 | 
					        if (this.test("return")) {
 | 
				
			||||||
            return this.parseReturn();
 | 
					            return this.parseReturn();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -107,19 +122,21 @@ export class Parser {
 | 
				
			|||||||
    private parseBlock(): Expr {
 | 
					    private parseBlock(): Expr {
 | 
				
			||||||
        const pos = this.pos();
 | 
					        const pos = this.pos();
 | 
				
			||||||
        this.step();
 | 
					        this.step();
 | 
				
			||||||
        let stmts: Stmt[] = [];
 | 
					        const stmts: Stmt[] = [];
 | 
				
			||||||
        while (!this.done()) {
 | 
					        while (!this.done()) {
 | 
				
			||||||
            if (this.test("}")) {
 | 
					            if (this.test("}")) {
 | 
				
			||||||
                this.step();
 | 
					                this.step();
 | 
				
			||||||
                return this.expr({ type: "block", stmts }, pos);
 | 
					                return this.expr({ type: "block", stmts }, pos);
 | 
				
			||||||
            } else if (
 | 
					            } else if (
 | 
				
			||||||
                this.test("return") || this.test("break") || this.test("let")
 | 
					                ["#", "pub", "mod", "fn"].some((tt) => this.test(tt))
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                stmts.push(this.parseItemStmt());
 | 
				
			||||||
 | 
					            } else if (
 | 
				
			||||||
 | 
					                ["let", "type_alias", "return", "break"]
 | 
				
			||||||
 | 
					                    .some((tt) => this.test(tt))
 | 
				
			||||||
            ) {
 | 
					            ) {
 | 
				
			||||||
                stmts.push(this.parseSingleLineBlockStmt());
 | 
					                stmts.push(this.parseSingleLineBlockStmt());
 | 
				
			||||||
                this.eatSemicolon();
 | 
					                this.eatSemicolon();
 | 
				
			||||||
            } else if (this.test("fn")) {
 | 
					 | 
				
			||||||
                stmts.push(this.parseSingleLineBlockStmt());
 | 
					 | 
				
			||||||
                stmts.push(this.parseFn());
 | 
					 | 
				
			||||||
            } else if (
 | 
					            } else if (
 | 
				
			||||||
                ["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
 | 
					                ["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
 | 
				
			||||||
            ) {
 | 
					            ) {
 | 
				
			||||||
@ -163,7 +180,110 @@ export class Parser {
 | 
				
			|||||||
        return this.expr({ type: "error" }, pos);
 | 
					        return this.expr({ type: "error" }, pos);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private parseFn(): Stmt {
 | 
					    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 {
 | 
				
			||||||
        const pos = this.pos();
 | 
					        const pos = this.pos();
 | 
				
			||||||
        this.step();
 | 
					        this.step();
 | 
				
			||||||
        if (!this.test("ident")) {
 | 
					        if (!this.test("ident")) {
 | 
				
			||||||
@ -172,21 +292,21 @@ export class Parser {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        const ident = this.current().identValue!;
 | 
					        const ident = this.current().identValue!;
 | 
				
			||||||
        this.step();
 | 
					        this.step();
 | 
				
			||||||
 | 
					        let genericParams: GenericParam[] | undefined;
 | 
				
			||||||
 | 
					        if (this.test("<")) {
 | 
				
			||||||
 | 
					            genericParams = this.parseFnETypeParams();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        if (!this.test("(")) {
 | 
					        if (!this.test("(")) {
 | 
				
			||||||
            this.report("expected '('");
 | 
					            this.report("expected '('");
 | 
				
			||||||
            return this.stmt({ type: "error" }, pos);
 | 
					            return this.stmt({ type: "error" }, pos);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const params = this.parseFnParams();
 | 
					        const params = this.parseFnParams();
 | 
				
			||||||
        let returnType: EType | null = null;
 | 
					        let returnType: EType | undefined;
 | 
				
			||||||
        if (this.test("->")) {
 | 
					        if (this.test("->")) {
 | 
				
			||||||
            this.step();
 | 
					            this.step();
 | 
				
			||||||
            returnType = this.parseEType();
 | 
					            returnType = this.parseEType();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let anno: Anno | null = null;
 | 
					 | 
				
			||||||
        if (this.test("#")) {
 | 
					 | 
				
			||||||
            anno = this.parseAnno();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (!this.test("{")) {
 | 
					        if (!this.test("{")) {
 | 
				
			||||||
            this.report("expected block");
 | 
					            this.report("expected block");
 | 
				
			||||||
            return this.stmt({ type: "error" }, pos);
 | 
					            return this.stmt({ type: "error" }, pos);
 | 
				
			||||||
@ -196,105 +316,104 @@ export class Parser {
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                type: "fn",
 | 
					                type: "fn",
 | 
				
			||||||
                ident,
 | 
					                ident,
 | 
				
			||||||
 | 
					                genericParams,
 | 
				
			||||||
                params,
 | 
					                params,
 | 
				
			||||||
                returnType: returnType !== null ? returnType : undefined,
 | 
					                returnType,
 | 
				
			||||||
                body,
 | 
					                body,
 | 
				
			||||||
                anno: anno != null ? anno : undefined,
 | 
					 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            pos,
 | 
					            pos,
 | 
				
			||||||
 | 
					            details,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private parseAnnoArgs(): Expr[] {
 | 
					    private parseFnETypeParams(): GenericParam[] {
 | 
				
			||||||
        this.step();
 | 
					        return this.parseDelimitedList(this.parseETypeParam, ">", ",");
 | 
				
			||||||
        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 parseAnno(): Anno | null {
 | 
					    private parseETypeParam(index: number): Res<GenericParam> {
 | 
				
			||||||
        const pos = this.pos();
 | 
					        const pos = this.pos();
 | 
				
			||||||
        this.step();
 | 
					        if (this.test("ident")) {
 | 
				
			||||||
        if (!this.test("[")) {
 | 
					 | 
				
			||||||
            this.report("expected '['");
 | 
					 | 
				
			||||||
            return null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        this.step();
 | 
					 | 
				
			||||||
        if (!this.test("ident")) {
 | 
					 | 
				
			||||||
            this.report("expected identifier");
 | 
					 | 
				
			||||||
            return null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
            const ident = this.current().identValue!;
 | 
					            const ident = this.current().identValue!;
 | 
				
			||||||
        const values = this.parseAnnoArgs();
 | 
					 | 
				
			||||||
        if (!this.test("]")) {
 | 
					 | 
				
			||||||
            this.report("expected ']'");
 | 
					 | 
				
			||||||
            return null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
            this.step();
 | 
					            this.step();
 | 
				
			||||||
        return { ident, pos, values };
 | 
					            return {
 | 
				
			||||||
 | 
					                ok: true,
 | 
				
			||||||
 | 
					                value: this.astCreator.genericParam({ index, ident, pos }),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.report("expected generic parameter");
 | 
				
			||||||
 | 
					        return { ok: false };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private parseFnParams(): Param[] {
 | 
					    private parseFnParams(): Param[] {
 | 
				
			||||||
        this.step();
 | 
					        return this.parseDelimitedList(this.parseParam, ")", ",");
 | 
				
			||||||
        if (this.test(")")) {
 | 
					 | 
				
			||||||
            this.step();
 | 
					 | 
				
			||||||
            return [];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        const params: Param[] = [];
 | 
					 | 
				
			||||||
        const paramResult = this.parseParam();
 | 
					 | 
				
			||||||
        if (!paramResult.ok) {
 | 
					 | 
				
			||||||
            return [];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        params.push(paramResult.value);
 | 
					 | 
				
			||||||
        while (this.test(",")) {
 | 
					 | 
				
			||||||
            this.step();
 | 
					 | 
				
			||||||
            if (this.test(")")) {
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            const paramResult = this.parseParam();
 | 
					 | 
				
			||||||
            if (!paramResult.ok) {
 | 
					 | 
				
			||||||
                return [];
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            params.push(paramResult.value);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (!this.test(")")) {
 | 
					 | 
				
			||||||
            this.report("expected ')'");
 | 
					 | 
				
			||||||
            return params;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        this.step();
 | 
					 | 
				
			||||||
        return params;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private parseParam(): { ok: true; value: Param } | { ok: false } {
 | 
					    private parseDelimitedList<T>(
 | 
				
			||||||
 | 
					        parseElem: (this: Parser, index: number) => Res<T>,
 | 
				
			||||||
 | 
					        endToken: string,
 | 
				
			||||||
 | 
					        delimiter: string,
 | 
				
			||||||
 | 
					    ): T[] {
 | 
				
			||||||
 | 
					        this.step();
 | 
				
			||||||
 | 
					        if (this.test(endToken)) {
 | 
				
			||||||
 | 
					            this.step();
 | 
				
			||||||
 | 
					            return [];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let i = 0;
 | 
				
			||||||
 | 
					        const elems: T[] = [];
 | 
				
			||||||
 | 
					        const elemRes = parseElem.call(this, i);
 | 
				
			||||||
 | 
					        if (!elemRes.ok) {
 | 
				
			||||||
 | 
					            return [];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        elems.push(elemRes.value);
 | 
				
			||||||
 | 
					        i += 1;
 | 
				
			||||||
 | 
					        while (this.test(delimiter)) {
 | 
				
			||||||
 | 
					            this.step();
 | 
				
			||||||
 | 
					            if (this.test(endToken)) {
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const elemRes = parseElem.call(this, i);
 | 
				
			||||||
 | 
					            if (!elemRes.ok) {
 | 
				
			||||||
 | 
					                return [];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            elems.push(elemRes.value);
 | 
				
			||||||
 | 
					            i += 1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!this.test(endToken)) {
 | 
				
			||||||
 | 
					            this.report(`expected '${endToken}'`);
 | 
				
			||||||
 | 
					            return elems;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.step();
 | 
				
			||||||
 | 
					        return elems;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseParam(index?: number): Res<Param> {
 | 
				
			||||||
        const pos = this.pos();
 | 
					        const pos = this.pos();
 | 
				
			||||||
        if (this.test("ident")) {
 | 
					        if (this.test("ident") || this.test("mut")) {
 | 
				
			||||||
 | 
					            let mut = false;
 | 
				
			||||||
 | 
					            if (this.test("mut")) {
 | 
				
			||||||
 | 
					                mut = true;
 | 
				
			||||||
 | 
					                this.step();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            const ident = this.current().identValue!;
 | 
					            const ident = this.current().identValue!;
 | 
				
			||||||
            this.step();
 | 
					            this.step();
 | 
				
			||||||
            if (this.test(":")) {
 | 
					            if (this.test(":")) {
 | 
				
			||||||
                this.step();
 | 
					                this.step();
 | 
				
			||||||
                const etype = this.parseEType();
 | 
					                const etype = this.parseEType();
 | 
				
			||||||
                return { ok: true, value: { ident, etype, pos } };
 | 
					                return {
 | 
				
			||||||
 | 
					                    ok: true,
 | 
				
			||||||
 | 
					                    value: this.astCreator.param({
 | 
				
			||||||
 | 
					                        index,
 | 
				
			||||||
 | 
					                        ident,
 | 
				
			||||||
 | 
					                        mut,
 | 
				
			||||||
 | 
					                        etype,
 | 
				
			||||||
 | 
					                        pos,
 | 
				
			||||||
 | 
					                    }),
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return { ok: true, value: { ident, pos } };
 | 
					            return {
 | 
				
			||||||
 | 
					                ok: true,
 | 
				
			||||||
 | 
					                value: this.astCreator.param({ index, ident, mut, pos }),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.report("expected param");
 | 
					        this.report("expected param");
 | 
				
			||||||
        return { ok: false };
 | 
					        return { ok: false };
 | 
				
			||||||
@ -317,6 +436,17 @@ export class Parser {
 | 
				
			|||||||
        return this.stmt({ type: "let", param, value }, pos);
 | 
					        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 {
 | 
					    private parseAssign(): Stmt {
 | 
				
			||||||
        const pos = this.pos();
 | 
					        const pos = this.pos();
 | 
				
			||||||
        const subject = this.parseExpr();
 | 
					        const subject = this.parseExpr();
 | 
				
			||||||
@ -444,6 +574,80 @@ export class Parser {
 | 
				
			|||||||
        return this.expr({ type: "for", decl, cond, incr, body }, pos);
 | 
					        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 {
 | 
					    private parseIf(): Expr {
 | 
				
			||||||
        const pos = this.pos();
 | 
					        const pos = this.pos();
 | 
				
			||||||
        this.step();
 | 
					        this.step();
 | 
				
			||||||
@ -586,54 +790,45 @@ export class Parser {
 | 
				
			|||||||
            const subject = this.parsePrefix();
 | 
					            const subject = this.parsePrefix();
 | 
				
			||||||
            return this.expr({ type: "unary", unaryType, subject }, pos);
 | 
					            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();
 | 
					        return this.parsePostfix();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private parsePostfix(): Expr {
 | 
					    private parsePostfix(): Expr {
 | 
				
			||||||
        let subject = this.parseOperand();
 | 
					        let subject = this.parseOperand();
 | 
				
			||||||
        while (true) {
 | 
					        while (true) {
 | 
				
			||||||
            const pos = this.pos();
 | 
					 | 
				
			||||||
            if (this.test(".")) {
 | 
					            if (this.test(".")) {
 | 
				
			||||||
                this.step();
 | 
					                subject = this.parseFieldTail(subject);
 | 
				
			||||||
                if (!this.test("ident")) {
 | 
					 | 
				
			||||||
                    this.report("expected ident");
 | 
					 | 
				
			||||||
                    return this.expr({ type: "error" }, pos);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                const value = this.current().identValue!;
 | 
					 | 
				
			||||||
                this.step();
 | 
					 | 
				
			||||||
                subject = this.expr({ type: "field", subject, value }, pos);
 | 
					 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (this.test("[")) {
 | 
					            if (this.test("[")) {
 | 
				
			||||||
                this.step();
 | 
					                subject = this.parseIndexTail(subject);
 | 
				
			||||||
                const value = this.parseExpr();
 | 
					 | 
				
			||||||
                if (!this.test("]")) {
 | 
					 | 
				
			||||||
                    this.report("expected ']'");
 | 
					 | 
				
			||||||
                    return this.expr({ type: "error" }, pos);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                this.step();
 | 
					 | 
				
			||||||
                subject = this.expr({ type: "index", subject, value }, pos);
 | 
					 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (this.test("(")) {
 | 
					            if (this.test("(")) {
 | 
				
			||||||
                this.step();
 | 
					                subject = this.parseCallTail(subject);
 | 
				
			||||||
                let args: Expr[] = [];
 | 
					                continue;
 | 
				
			||||||
                if (!this.test(")")) {
 | 
					 | 
				
			||||||
                    args.push(this.parseExpr());
 | 
					 | 
				
			||||||
                    while (this.test(",")) {
 | 
					 | 
				
			||||||
                        this.step();
 | 
					 | 
				
			||||||
                        if (this.test(")")) {
 | 
					 | 
				
			||||||
                            break;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
                        args.push(this.parseExpr());
 | 
					            if (this.test("::")) {
 | 
				
			||||||
 | 
					                subject = this.parsePathTail(subject);
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
                }
 | 
					            if (this.test("::<")) {
 | 
				
			||||||
                if (!this.test(")")) {
 | 
					                subject = this.parseETypeArgsTail(subject);
 | 
				
			||||||
                    this.report("expected ')'");
 | 
					 | 
				
			||||||
                    return this.expr({ type: "error" }, pos);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                this.step();
 | 
					 | 
				
			||||||
                subject = this.expr({ type: "call", subject, args }, pos);
 | 
					 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
@ -641,12 +836,79 @@ export class Parser {
 | 
				
			|||||||
        return subject;
 | 
					        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!;
 | 
				
			||||||
 | 
					        this.step();
 | 
				
			||||||
 | 
					        return this.expr({ type: "field", subject, ident }, pos);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseIndexTail(subject: Expr): Expr {
 | 
				
			||||||
 | 
					        const pos = this.pos();
 | 
				
			||||||
 | 
					        this.step();
 | 
				
			||||||
 | 
					        const value = this.parseExpr();
 | 
				
			||||||
 | 
					        if (!this.test("]")) {
 | 
				
			||||||
 | 
					            this.report("expected ']'");
 | 
				
			||||||
 | 
					            return this.expr({ type: "error" }, pos);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.step();
 | 
				
			||||||
 | 
					        return this.expr({ type: "index", subject, value }, pos);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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();
 | 
				
			||||||
 | 
					        this.step();
 | 
				
			||||||
 | 
					        if (!this.test("ident")) {
 | 
				
			||||||
 | 
					            this.report("expected ident");
 | 
				
			||||||
 | 
					            return this.expr({ type: "error" }, pos);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const ident = this.current().identValue!;
 | 
				
			||||||
 | 
					        this.step();
 | 
				
			||||||
 | 
					        return this.expr({ type: "path", subject, ident }, pos);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseExprArg(): Res<Expr> {
 | 
				
			||||||
 | 
					        return { ok: true, value: this.parseExpr() };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseETypeArg(): Res<EType> {
 | 
				
			||||||
 | 
					        return { ok: true, value: this.parseEType() };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private parseOperand(): Expr {
 | 
					    private parseOperand(): Expr {
 | 
				
			||||||
        const pos = this.pos();
 | 
					        const pos = this.pos();
 | 
				
			||||||
        if (this.test("ident")) {
 | 
					        if (this.test("ident")) {
 | 
				
			||||||
            const value = this.current().identValue!;
 | 
					            const ident = this.current().identValue!;
 | 
				
			||||||
            this.step();
 | 
					            this.step();
 | 
				
			||||||
            return this.expr({ type: "ident", value }, pos);
 | 
					            return this.expr({ type: "ident", ident }, pos);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (this.test("int")) {
 | 
					        if (this.test("int")) {
 | 
				
			||||||
            const value = this.current().intValue!;
 | 
					            const value = this.current().intValue!;
 | 
				
			||||||
@ -680,6 +942,12 @@ export class Parser {
 | 
				
			|||||||
            this.step();
 | 
					            this.step();
 | 
				
			||||||
            return this.expr({ type: "group", expr }, pos);
 | 
					            return this.expr({ type: "group", expr }, pos);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (this.test("[")) {
 | 
				
			||||||
 | 
					            return this.parseArray();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (this.test("struct")) {
 | 
				
			||||||
 | 
					            return this.parseStruct();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        if (this.test("{")) {
 | 
					        if (this.test("{")) {
 | 
				
			||||||
            return this.parseBlock();
 | 
					            return this.parseBlock();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -690,27 +958,36 @@ export class Parser {
 | 
				
			|||||||
            return this.parseLoop();
 | 
					            return this.parseLoop();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.report("expected expr", pos);
 | 
					        this.report(`expected expr, got '${this.current().type}'`, pos);
 | 
				
			||||||
        this.step();
 | 
					        this.step();
 | 
				
			||||||
        return this.expr({ type: "error" }, pos);
 | 
					        return this.expr({ type: "error" }, pos);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private parseEType(): EType {
 | 
					    private parseEType(): EType {
 | 
				
			||||||
        const pos = this.pos();
 | 
					        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")) {
 | 
					        if (this.test("ident")) {
 | 
				
			||||||
            const ident = this.current().identValue!;
 | 
					            const ident = this.current().identValue!;
 | 
				
			||||||
            this.step();
 | 
					            this.step();
 | 
				
			||||||
            return this.etype({ type: "ident", value: ident }, pos);
 | 
					            return this.etype({ type: "ident", ident: ident }, pos);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (this.test("[")) {
 | 
					        if (this.test("[")) {
 | 
				
			||||||
            this.step();
 | 
					            this.step();
 | 
				
			||||||
            const inner = this.parseEType();
 | 
					            const subject = this.parseEType();
 | 
				
			||||||
            if (!this.test("]")) {
 | 
					            if (!this.test("]")) {
 | 
				
			||||||
                this.report("expected ']'", pos);
 | 
					                this.report("expected ']'", pos);
 | 
				
			||||||
                return this.etype({ type: "error" }, pos);
 | 
					                return this.etype({ type: "error" }, pos);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            this.step();
 | 
					            this.step();
 | 
				
			||||||
            return this.etype({ type: "array", inner }, pos);
 | 
					            return this.etype({ type: "array", subject }, pos);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (this.test("struct")) {
 | 
					        if (this.test("struct")) {
 | 
				
			||||||
            this.step();
 | 
					            this.step();
 | 
				
			||||||
@ -721,6 +998,26 @@ export class Parser {
 | 
				
			|||||||
            const fields = this.parseETypeStructFields();
 | 
					            const fields = this.parseETypeStructFields();
 | 
				
			||||||
            return this.etype({ type: "struct", fields }, pos);
 | 
					            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");
 | 
					        this.report("expected type");
 | 
				
			||||||
        return this.etype({ type: "error" }, pos);
 | 
					        return this.etype({ type: "error" }, pos);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -777,7 +1074,6 @@ export class Parser {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private report(msg: string, pos = this.pos()) {
 | 
					    private report(msg: string, pos = this.pos()) {
 | 
				
			||||||
        console.log(`Parser: ${msg} at ${pos.line}:${pos.col}`);
 | 
					 | 
				
			||||||
        this.reporter.reportError({
 | 
					        this.reporter.reportError({
 | 
				
			||||||
            msg,
 | 
					            msg,
 | 
				
			||||||
            pos,
 | 
					            pos,
 | 
				
			||||||
@ -786,8 +1082,8 @@ export class Parser {
 | 
				
			|||||||
        printStackTrace();
 | 
					        printStackTrace();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private stmt(kind: StmtKind, pos: Pos): Stmt {
 | 
					    private stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt {
 | 
				
			||||||
        return this.astCreator.stmt(kind, pos);
 | 
					        return this.astCreator.stmt(kind, pos, details);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private expr(kind: ExprKind, pos: Pos): Expr {
 | 
					    private expr(kind: ExprKind, pos: Pos): Expr {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,31 +1,88 @@
 | 
				
			|||||||
import { Expr, Stmt } from "./ast.ts";
 | 
					import { EType, Expr, Stmt } from "./ast.ts";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    AstVisitor,
 | 
					    AstVisitor,
 | 
				
			||||||
 | 
					    visitEType,
 | 
				
			||||||
    visitExpr,
 | 
					    visitExpr,
 | 
				
			||||||
 | 
					    visitParam,
 | 
				
			||||||
    VisitRes,
 | 
					    VisitRes,
 | 
				
			||||||
    visitStmt,
 | 
					    visitStmt,
 | 
				
			||||||
    visitStmts,
 | 
					    visitStmts,
 | 
				
			||||||
} from "./ast_visitor.ts";
 | 
					} from "./ast_visitor.ts";
 | 
				
			||||||
import { printStackTrace, Reporter } from "./info.ts";
 | 
					import { printStackTrace, Reporter } from "./info.ts";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					    EntryModSyms,
 | 
				
			||||||
    FnSyms,
 | 
					    FnSyms,
 | 
				
			||||||
    GlobalSyms,
 | 
					 | 
				
			||||||
    LeafSyms,
 | 
					    LeafSyms,
 | 
				
			||||||
    StaticSyms,
 | 
					    ModSyms,
 | 
				
			||||||
    Syms,
 | 
					    Syms,
 | 
				
			||||||
} from "./resolver_syms.ts";
 | 
					} from "./resolver_syms.ts";
 | 
				
			||||||
import { Pos } from "./token.ts";
 | 
					import { Pos } from "./token.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Resolver implements AstVisitor<[Syms]> {
 | 
					export class Resolver implements AstVisitor<[Syms]> {
 | 
				
			||||||
    private root = new GlobalSyms();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public constructor(private reporter: Reporter) {
 | 
					    public constructor(private reporter: Reporter) {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public resolve(stmts: Stmt[]): VisitRes {
 | 
					    public resolve(stmts: Stmt[]): VisitRes {
 | 
				
			||||||
        const scopeSyms = new StaticSyms(this.root);
 | 
					        const syms = new EntryModSyms("root");
 | 
				
			||||||
        this.scoutFnStmts(stmts, scopeSyms);
 | 
					        this.scout(stmts, syms);
 | 
				
			||||||
        visitStmts(stmts, this, scopeSyms);
 | 
					        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";
 | 
					        return "stop";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -39,9 +96,10 @@ export class Resolver implements AstVisitor<[Syms]> {
 | 
				
			|||||||
            this.reportAlreadyDefined(ident, stmt.pos, syms);
 | 
					            this.reportAlreadyDefined(ident, stmt.pos, syms);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        syms.define(ident, {
 | 
					        stmt.kind.param.sym = syms.define(ident, {
 | 
				
			||||||
            ident,
 | 
					            ident,
 | 
				
			||||||
            type: "let",
 | 
					            type: "let",
 | 
				
			||||||
 | 
					            fullPath: ident,
 | 
				
			||||||
            pos: stmt.kind.param.pos,
 | 
					            pos: stmt.kind.param.pos,
 | 
				
			||||||
            stmt,
 | 
					            stmt,
 | 
				
			||||||
            param: stmt.kind.param,
 | 
					            param: stmt.kind.param,
 | 
				
			||||||
@ -49,23 +107,11 @@ export class Resolver implements AstVisitor<[Syms]> {
 | 
				
			|||||||
        return "stop";
 | 
					        return "stop";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private scoutFnStmts(stmts: Stmt[], syms: Syms) {
 | 
					    visitTypeAliasStmt(stmt: Stmt, _syms: Syms): VisitRes {
 | 
				
			||||||
        for (const stmt of stmts) {
 | 
					        if (stmt.kind.type !== "type_alias") {
 | 
				
			||||||
            if (stmt.kind.type !== "fn") {
 | 
					            throw new Error("expected type_alias statement");
 | 
				
			||||||
                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,
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        // nothing to do here
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    visitFnStmt(stmt: Stmt, syms: Syms): VisitRes {
 | 
					    visitFnStmt(stmt: Stmt, syms: Syms): VisitRes {
 | 
				
			||||||
@ -73,18 +119,37 @@ export class Resolver implements AstVisitor<[Syms]> {
 | 
				
			|||||||
            throw new Error("expected fn statement");
 | 
					            throw new Error("expected fn statement");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const fnScopeSyms = new FnSyms(syms);
 | 
					        const fnScopeSyms = new FnSyms(syms);
 | 
				
			||||||
        for (const param of stmt.kind.params) {
 | 
					        for (const param of stmt.kind.genericParams ?? []) {
 | 
				
			||||||
            if (fnScopeSyms.definedLocally(param.ident)) {
 | 
					            if (fnScopeSyms.definedLocally(param.ident)) {
 | 
				
			||||||
                this.reportAlreadyDefined(param.ident, param.pos, syms);
 | 
					                this.reportAlreadyDefined(param.ident, param.pos, syms);
 | 
				
			||||||
                continue;
 | 
					                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, {
 | 
					            fnScopeSyms.define(param.ident, {
 | 
				
			||||||
                ident: param.ident,
 | 
					                ident: param.ident,
 | 
				
			||||||
                type: "fn_param",
 | 
					                type: "fn_param",
 | 
				
			||||||
 | 
					                fullPath: param.ident,
 | 
				
			||||||
                pos: param.pos,
 | 
					                pos: param.pos,
 | 
				
			||||||
                param,
 | 
					                param,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (stmt.kind.returnType) {
 | 
				
			||||||
 | 
					            visitEType(stmt.kind.returnType, this, fnScopeSyms);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        visitExpr(stmt.kind.body, this, fnScopeSyms);
 | 
					        visitExpr(stmt.kind.body, this, fnScopeSyms);
 | 
				
			||||||
        return "stop";
 | 
					        return "stop";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -93,18 +158,51 @@ export class Resolver implements AstVisitor<[Syms]> {
 | 
				
			|||||||
        if (expr.kind.type !== "ident") {
 | 
					        if (expr.kind.type !== "ident") {
 | 
				
			||||||
            throw new Error("expected ident");
 | 
					            throw new Error("expected ident");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const ident = expr.kind;
 | 
					        const ident = expr.kind.ident;
 | 
				
			||||||
        const symResult = syms.get(ident.value);
 | 
					        const symResult = syms.get(ident);
 | 
				
			||||||
        if (!symResult.ok) {
 | 
					        if (!symResult.ok) {
 | 
				
			||||||
            this.reportUseOfUndefined(ident.value, expr.pos, syms);
 | 
					            this.reportUseOfUndefined(ident, expr.pos, syms);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const sym = symResult.sym;
 | 
					        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 = {
 | 
					        expr.kind = {
 | 
				
			||||||
            type: "sym",
 | 
					            type: "sym",
 | 
				
			||||||
            ident: ident.value,
 | 
					            ident: expr.kind.ident,
 | 
				
			||||||
            sym,
 | 
					            sym: getRes.sym,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return "stop";
 | 
					        return "stop";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -113,7 +211,7 @@ export class Resolver implements AstVisitor<[Syms]> {
 | 
				
			|||||||
            throw new Error();
 | 
					            throw new Error();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const childSyms = new LeafSyms(syms);
 | 
					        const childSyms = new LeafSyms(syms);
 | 
				
			||||||
        this.scoutFnStmts(expr.kind.stmts, childSyms);
 | 
					        this.scout(expr.kind.stmts, childSyms);
 | 
				
			||||||
        visitStmts(expr.kind.stmts, this, childSyms);
 | 
					        visitStmts(expr.kind.stmts, this, childSyms);
 | 
				
			||||||
        if (expr.kind.expr) {
 | 
					        if (expr.kind.expr) {
 | 
				
			||||||
            visitExpr(expr.kind.expr, this, childSyms);
 | 
					            visitExpr(expr.kind.expr, this, childSyms);
 | 
				
			||||||
@ -134,6 +232,21 @@ export class Resolver implements AstVisitor<[Syms]> {
 | 
				
			|||||||
        return "stop";
 | 
					        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) {
 | 
					    private reportUseOfUndefined(ident: string, pos: Pos, _syms: Syms) {
 | 
				
			||||||
        this.reporter.reportError({
 | 
					        this.reporter.reportError({
 | 
				
			||||||
            reporter: "Resolver",
 | 
					            reporter: "Resolver",
 | 
				
			||||||
 | 
				
			|||||||
@ -1,66 +1,112 @@
 | 
				
			|||||||
import { Sym } from "./ast.ts";
 | 
					import type { Sym } from "./ast.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type SymMap = { [ident: string]: Sym };
 | 
					export type SymMap = { [ident: string]: Sym };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type GetRes = { ok: true; sym: Sym } | { ok: false };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface Syms {
 | 
					export interface Syms {
 | 
				
			||||||
    define(ident: string, sym: Sym): void;
 | 
					    define(ident: string, sym: Sym): Sym;
 | 
				
			||||||
    definedLocally(ident: string): boolean;
 | 
					    definedLocally(ident: string): boolean;
 | 
				
			||||||
    get(ident: string): { ok: true; sym: Sym } | { ok: false };
 | 
					    get(ident: string): GetRes;
 | 
				
			||||||
 | 
					    getPub(ident: string): GetRes;
 | 
				
			||||||
 | 
					    rootMod(): Sym;
 | 
				
			||||||
 | 
					    pathString(): string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class GlobalSyms implements Syms {
 | 
					export class EntryModSyms implements Syms {
 | 
				
			||||||
    private syms: SymMap = {};
 | 
					    private syms: SymMap = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public constructor() {}
 | 
					    public constructor(private modName: string) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public define(ident: string, sym: Sym) {
 | 
					    public define(ident: string, sym: Sym): Sym {
 | 
				
			||||||
        if (sym.type === "let") {
 | 
					        if (sym.type === "let") {
 | 
				
			||||||
            this.define(ident, {
 | 
					            return this.define(ident, {
 | 
				
			||||||
                ...sym,
 | 
					                ...sym,
 | 
				
			||||||
                type: "let_static",
 | 
					                type: "let_static",
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.syms[ident] = sym;
 | 
					        this.syms[ident] = sym;
 | 
				
			||||||
 | 
					        return sym;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public definedLocally(ident: string): boolean {
 | 
					    public definedLocally(ident: string): boolean {
 | 
				
			||||||
        return ident in this.syms;
 | 
					        return ident in this.syms;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
 | 
					    public get(ident: string): GetRes {
 | 
				
			||||||
        if (ident in this.syms) {
 | 
					        if (ident in this.syms) {
 | 
				
			||||||
            return { ok: true, sym: this.syms[ident] };
 | 
					            return { ok: true, sym: this.syms[ident] };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return { ok: false };
 | 
					        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 StaticSyms implements Syms {
 | 
					export class ModSyms implements Syms {
 | 
				
			||||||
    private syms: SymMap = {};
 | 
					    private syms: SymMap = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public constructor(private parent: GlobalSyms) {}
 | 
					    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) {
 | 
					    public define(ident: string, sym: Sym): Sym {
 | 
				
			||||||
        if (sym.type === "let") {
 | 
					        if (sym.type === "let") {
 | 
				
			||||||
            this.define(ident, {
 | 
					            return this.define(ident, {
 | 
				
			||||||
                ...sym,
 | 
					                ...sym,
 | 
				
			||||||
                type: "let_static",
 | 
					                type: "let_static",
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.syms[ident] = sym;
 | 
					        return this.syms[ident] = sym;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public definedLocally(ident: string): boolean {
 | 
					    public definedLocally(ident: string): boolean {
 | 
				
			||||||
        return ident in this.syms;
 | 
					        return ident in this.syms;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
 | 
					    public get(ident: string): GetRes {
 | 
				
			||||||
        if (ident in this.syms) {
 | 
					        if (ident in this.syms) {
 | 
				
			||||||
            return { ok: true, sym: this.syms[ident] };
 | 
					            return { ok: true, sym: this.syms[ident] };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return this.parent.get(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}`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -69,28 +115,43 @@ export class FnSyms implements Syms {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public constructor(private parent: Syms) {}
 | 
					    public constructor(private parent: Syms) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public define(ident: string, sym: Sym) {
 | 
					    public define(ident: string, sym: Sym): Sym {
 | 
				
			||||||
        if (sym.type === "let") {
 | 
					        if (sym.type === "let") {
 | 
				
			||||||
            this.define(ident, {
 | 
					            return this.define(ident, {
 | 
				
			||||||
                ...sym,
 | 
					                ...sym,
 | 
				
			||||||
                type: "closure",
 | 
					                type: "closure",
 | 
				
			||||||
                inner: sym,
 | 
					                inner: sym,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.syms[ident] = sym;
 | 
					        this.syms[ident] = sym;
 | 
				
			||||||
 | 
					        return sym;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public definedLocally(ident: string): boolean {
 | 
					    public definedLocally(ident: string): boolean {
 | 
				
			||||||
        return ident in this.syms;
 | 
					        return ident in this.syms;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
 | 
					    public get(ident: string): GetRes {
 | 
				
			||||||
        if (ident in this.syms) {
 | 
					        if (ident in this.syms) {
 | 
				
			||||||
            return { ok: true, sym: this.syms[ident] };
 | 
					            return { ok: true, sym: this.syms[ident] };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return this.parent.get(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 {
 | 
					export class LeafSyms implements Syms {
 | 
				
			||||||
@ -98,18 +159,34 @@ export class LeafSyms implements Syms {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public constructor(private parent: Syms) {}
 | 
					    public constructor(private parent: Syms) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public define(ident: string, sym: Sym) {
 | 
					    public define(ident: string, sym: Sym): Sym {
 | 
				
			||||||
        this.syms[ident] = sym;
 | 
					        this.syms[ident] = sym;
 | 
				
			||||||
 | 
					        return sym;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public definedLocally(ident: string): boolean {
 | 
					    public definedLocally(ident: string): boolean {
 | 
				
			||||||
        return ident in this.syms;
 | 
					        return ident in this.syms;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
 | 
					    public get(ident: string): GetRes {
 | 
				
			||||||
        if (ident in this.syms) {
 | 
					        if (ident in this.syms) {
 | 
				
			||||||
            return { ok: true, sym: this.syms[ident] };
 | 
					            return { ok: true, sym: this.syms[ident] };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return this.parent.get(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();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -5,27 +5,82 @@ export type VType =
 | 
				
			|||||||
    | { type: "int" }
 | 
					    | { type: "int" }
 | 
				
			||||||
    | { type: "string" }
 | 
					    | { type: "string" }
 | 
				
			||||||
    | { type: "bool" }
 | 
					    | { type: "bool" }
 | 
				
			||||||
    | { type: "array"; inner: VType }
 | 
					    | { 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: "struct"; fields: VTypeParam[] }
 | 
				
			||||||
    | { type: "fn"; params: VTypeParam[]; returnType: VType };
 | 
					    | {
 | 
				
			||||||
 | 
					        type: "fn";
 | 
				
			||||||
 | 
					        genericParams?: VTypeGenericParam[];
 | 
				
			||||||
 | 
					        params: VTypeParam[];
 | 
				
			||||||
 | 
					        returnType: VType;
 | 
				
			||||||
 | 
					        stmtId: number;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    | { type: "generic"; param: VTypeGenericParam }
 | 
				
			||||||
 | 
					    | {
 | 
				
			||||||
 | 
					        type: "generic_spec";
 | 
				
			||||||
 | 
					        subject: VType;
 | 
				
			||||||
 | 
					        genericArgs: GenericArgsMap;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type VTypeParam = {
 | 
					export type VTypeParam = {
 | 
				
			||||||
    ident: string;
 | 
					    ident: string;
 | 
				
			||||||
 | 
					    mut: boolean;
 | 
				
			||||||
    vtype: VType;
 | 
					    vtype: VType;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function vtypesEqual(a: VType, b: VType): boolean {
 | 
					export type VTypeGenericParam = {
 | 
				
			||||||
    if (a.type !== b.type) {
 | 
					    id: number;
 | 
				
			||||||
        return false;
 | 
					    ident: string;
 | 
				
			||||||
    }
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type GenericArgsMap = { [id: number]: VType };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function vtypesEqual(
 | 
				
			||||||
 | 
					    a: VType,
 | 
				
			||||||
 | 
					    b: VType,
 | 
				
			||||||
 | 
					    generics?: GenericArgsMap,
 | 
				
			||||||
 | 
					): boolean {
 | 
				
			||||||
    if (
 | 
					    if (
 | 
				
			||||||
        ["error", "unknown", "null", "int", "string", "bool"]
 | 
					        ["error", "unknown", "null", "int", "string", "bool"]
 | 
				
			||||||
            .includes(a.type)
 | 
					            .includes(a.type) && a.type === b.type
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        return true;
 | 
					        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") {
 | 
					    if (a.type === "array" && b.type === "array") {
 | 
				
			||||||
        return vtypesEqual(a.inner, b.inner);
 | 
					        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.type === "fn" && b.type === "fn") {
 | 
				
			||||||
        if (a.params.length !== b.params.length) {
 | 
					        if (a.params.length !== b.params.length) {
 | 
				
			||||||
@ -36,11 +91,41 @@ export function vtypesEqual(a: VType, b: VType): boolean {
 | 
				
			|||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return vtypesEqual(a.returnType, b.returnType);
 | 
					        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;
 | 
					    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 {
 | 
					export function vtypeToString(vtype: VType): string {
 | 
				
			||||||
    if (
 | 
					    if (
 | 
				
			||||||
        ["error", "unknown", "null", "int", "string", "bool"]
 | 
					        ["error", "unknown", "null", "int", "string", "bool"]
 | 
				
			||||||
@ -48,15 +133,36 @@ export function vtypeToString(vtype: VType): string {
 | 
				
			|||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        return 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") {
 | 
					    if (vtype.type === "array") {
 | 
				
			||||||
        return `[${vtypeToString(vtype.inner)}]`;
 | 
					        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") {
 | 
					    if (vtype.type === "fn") {
 | 
				
			||||||
        const paramString = vtype.params.map((param) =>
 | 
					        const paramString = vtype.params.map((param) =>
 | 
				
			||||||
            `${param.ident}: ${vtypeToString(param.vtype)}`
 | 
					            `${param.ident}: ${vtypeToString(param.vtype)}`
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
            .join(", ");
 | 
					            .join(", ");
 | 
				
			||||||
        return `fn (${paramString}) -> ${vtypeToString(vtype.returnType)}`;
 | 
					        return `fn(${paramString}) -> ${vtypeToString(vtype.returnType)}`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (vtype.type === "generic") {
 | 
				
			||||||
 | 
					        return `generic`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    throw new Error(`unhandled vtype '${vtype.type}'`);
 | 
					    throw new Error(`unhandled vtype '${vtype.type}'`);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,8 @@ if exists("b:current_syntax")
 | 
				
			|||||||
  finish
 | 
					  finish
 | 
				
			||||||
endif
 | 
					endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
syn keyword Keyword break return let fn loop if else struct import or and not while for in
 | 
					
 | 
				
			||||||
 | 
					syn keyword Keyword break return let fn loop if else struct import or and not while for in mod pub
 | 
				
			||||||
syn keyword Special null
 | 
					syn keyword Special null
 | 
				
			||||||
syn keyword Type int string bool
 | 
					syn keyword Type int string bool
 | 
				
			||||||
syn keyword Boolean true false
 | 
					syn keyword Boolean true false
 | 
				
			||||||
@ -37,6 +38,9 @@ syn match Number '0[0-7]\+'
 | 
				
			|||||||
syn match Number '0x[0-9a-fA-F]\+'
 | 
					syn match Number '0x[0-9a-fA-F]\+'
 | 
				
			||||||
syn match Number '0b[01]\+'
 | 
					syn match Number '0b[01]\+'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					syn match Character "'[^\\]'"
 | 
				
			||||||
 | 
					syn match Character "'\\.'"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
syn region String   start=+"+  skip=+\\"+  end=+"+
 | 
					syn region String   start=+"+  skip=+\\"+  end=+"+
 | 
				
			||||||
 | 
					
 | 
				
			||||||
syn keyword Todo contained TODO FIXME XXX NOTE
 | 
					syn keyword Todo contained TODO FIXME XXX NOTE
 | 
				
			||||||
@ -44,11 +48,23 @@ syn match Comment "//.*$" contains=Todo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
syn region Comment start=+/\*+ end=+\*/+ contains=Todo
 | 
					syn region Comment start=+/\*+ end=+\*/+ contains=Todo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
syn match Identifier '[a-z_]\w*'
 | 
					syn match Identifier '[a-z_]\w*'
 | 
				
			||||||
syn match Type '[A-Z]\w*'
 | 
					syn match Type '[A-Z]\w*'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
syn match Function '[a-zA-Z_]\w*\ze('
 | 
					
 | 
				
			||||||
 | 
					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 region sligeBlock start="{" end="}" transparent fold
 | 
					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"
 | 
					let b:current_syntax = "slige"
 | 
				
			||||||
 | 
				
			|||||||
@ -53,6 +53,17 @@
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "chars": {
 | 
				
			||||||
 | 
					            "name": "string.quoted.double.slige",
 | 
				
			||||||
 | 
					            "begin": "'",
 | 
				
			||||||
 | 
					            "end": "'",
 | 
				
			||||||
 | 
					            "patterns": [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "name": "constant.character.escape.slige",
 | 
				
			||||||
 | 
					                    "match": "\\\\."
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "numbers": {
 | 
					        "numbers": {
 | 
				
			||||||
            "patterns": [
 | 
					            "patterns": [
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,7 @@
 | 
				
			|||||||
fn print(msg: string) #[builtin(print)] {
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[builtin(Print)]
 | 
				
			||||||
 | 
					fn print(msg: string) {
 | 
				
			||||||
    "hello" + 0    
 | 
					    "hello" + 0    
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,14 @@ fn add(a: int, b: int) -> int {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
fn main() -> int {
 | 
					fn main() -> int {
 | 
				
			||||||
    let result = 0;
 | 
					    let result = 0;
 | 
				
			||||||
    let i = 0;
 | 
					
 | 
				
			||||||
 | 
					    let a  = 0;
 | 
				
			||||||
 | 
					    let b  = a;
 | 
				
			||||||
 | 
					    let c  = b;
 | 
				
			||||||
 | 
					    let d  = c;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let i = c;
 | 
				
			||||||
    loop {
 | 
					    loop {
 | 
				
			||||||
        if i >= 10 {
 | 
					        if i >= 10 {
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										106
									
								
								examples/example_performance.slg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								examples/example_performance.slg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,106 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					    for (let i = 0; i < 100; i += 1) {
 | 
				
			||||||
 | 
					        string_to_int_impl("497");
 | 
				
			||||||
 | 
					        string_to_int_impl("0b1001100000");
 | 
				
			||||||
 | 
					        string_to_int_impl("0");
 | 
				
			||||||
 | 
					        string_to_int_impl("0x9f");
 | 
				
			||||||
 | 
					        string_to_int_impl("055");
 | 
				
			||||||
 | 
					        string_to_int_impl("0b1000000");
 | 
				
			||||||
 | 
					        string_to_int_impl("0b101111101");
 | 
				
			||||||
 | 
					        string_to_int_impl("271");
 | 
				
			||||||
 | 
					        string_to_int_impl("0x2bd");
 | 
				
			||||||
 | 
					        string_to_int_impl("0x31e");
 | 
				
			||||||
 | 
					        string_to_int_impl("0b1001101111");
 | 
				
			||||||
 | 
					        string_to_int_impl("0535");
 | 
				
			||||||
 | 
					        string_to_int_impl("0x233");
 | 
				
			||||||
 | 
					        string_to_int_impl("01622");
 | 
				
			||||||
 | 
					        string_to_int_impl("0");
 | 
				
			||||||
 | 
					        string_to_int_impl("0b10001010");
 | 
				
			||||||
 | 
					        string_to_int_impl("0b1111001101");
 | 
				
			||||||
 | 
					        string_to_int_impl("0x93");
 | 
				
			||||||
 | 
					        string_to_int_impl("0x23e");
 | 
				
			||||||
 | 
					        string_to_int_impl("0316");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn string_to_int_impl(text: string) -> int {
 | 
				
			||||||
 | 
					    let base_2_digits = "01";
 | 
				
			||||||
 | 
					    let base_8_digits = base_2_digits + "234567";
 | 
				
			||||||
 | 
					    let base_10_digits = base_8_digits + "89";
 | 
				
			||||||
 | 
					    let base_16_digits = base_10_digits + "abcdef";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let len = string_length(text);
 | 
				
			||||||
 | 
					    if len == 0 {
 | 
				
			||||||
 | 
					        return -1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if text[0] == "0"[0] {
 | 
				
			||||||
 | 
					        if len == 1 {
 | 
				
			||||||
 | 
					            0
 | 
				
			||||||
 | 
					        } else if text[1] == "b"[0] {
 | 
				
			||||||
 | 
					            parse_digits(string_slice(text, 2, -1), 2, base_2_digits)
 | 
				
			||||||
 | 
					        } else if text[1] == "x"[0] {
 | 
				
			||||||
 | 
					            parse_digits(string_slice(text, 2, -1), 16, base_16_digits)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            parse_digits(string_slice(text, 1, -1), 8, base_8_digits)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        parse_digits(text, 10, base_10_digits)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_digits(text: string, base: int, digit_set: string) -> int {
 | 
				
			||||||
 | 
					    let val = 0;
 | 
				
			||||||
 | 
					    let len = string_length(text);
 | 
				
			||||||
 | 
					    for (let i = 0; i < len; i += 1) {
 | 
				
			||||||
 | 
					        let ch = text[i];
 | 
				
			||||||
 | 
					        if not string_contains(digit_set, ch) {
 | 
				
			||||||
 | 
					            return -1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        val = val * base;
 | 
				
			||||||
 | 
					        val += char_val(ch);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    val
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn char_val(ch: int) -> int {
 | 
				
			||||||
 | 
					    if ch >= "0"[0] and ch <= "9"[0] {
 | 
				
			||||||
 | 
					        ch - "0"[0]
 | 
				
			||||||
 | 
					    } else if ch >= "a"[0] and ch <= "f"[0] {
 | 
				
			||||||
 | 
					        ch - "a"[0] + 10
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        -1
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn string_slice(str: string, from: int, to: int) -> string {
 | 
				
			||||||
 | 
					    let result = "";
 | 
				
			||||||
 | 
					    let len = string_length(str);
 | 
				
			||||||
 | 
					    let abs_to =
 | 
				
			||||||
 | 
					        if to >= len { len }
 | 
				
			||||||
 | 
					        else if to < 0 { len + to + 1 }
 | 
				
			||||||
 | 
					        else { to };
 | 
				
			||||||
 | 
					    for (let i = from; i < abs_to; i += 1) {
 | 
				
			||||||
 | 
					        result = string_push_char(result, str[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    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 print(msg: string) #[builtin(Print)] {}
 | 
				
			||||||
 | 
					fn println(msg: string) { print(msg + "\n") }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn string_push_char(str: string, value: int) -> string #[builtin(StringPushChar)] {}
 | 
				
			||||||
 | 
					fn string_length(str: string) -> int #[builtin(StringLength)] {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn itos(number: int) -> string #[builtin(IntToString)] {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										111
									
								
								examples/example_test_coverage.slg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								examples/example_test_coverage.slg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					    let ok = test_string_to_int_impl();
 | 
				
			||||||
 | 
					    if not ok {
 | 
				
			||||||
 | 
					        println("tests failed!");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn test_string_to_int_impl() -> bool {
 | 
				
			||||||
 | 
					        test("should convert zero",      assert_int_equal(string_to_int_impl("0"), 0))
 | 
				
			||||||
 | 
					    and test("should convert decimal",   assert_int_equal(string_to_int_impl("10"), 10))
 | 
				
			||||||
 | 
					    and test("should convert binary",    assert_int_equal(string_to_int_impl("0b110"), 6))
 | 
				
			||||||
 | 
					    and test("should convert octal",     assert_int_equal(string_to_int_impl("071"), 57))
 | 
				
			||||||
 | 
					    // and test("should convert hex",       assert_int_equal(string_to_int_impl("0xaa"), 170))
 | 
				
			||||||
 | 
					    and test("should fail",              assert_int_equal(string_to_int_impl("john"), -1))
 | 
				
			||||||
 | 
					    // and test("should fail",              assert_int_equal(string_to_int_impl(""), -1))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn assert_int_equal(value: int, target: int) -> bool {
 | 
				
			||||||
 | 
					    if value != target {
 | 
				
			||||||
 | 
					        println("assertion failed: " + itos(value) + " != " + itos(target));
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn test(name: string, assertion: bool) -> bool {
 | 
				
			||||||
 | 
					    println(" * test: " + name + " -> " + if assertion { "ok" } else { "failed" });
 | 
				
			||||||
 | 
					    assertion
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn string_to_int_impl(text: string) -> int {
 | 
				
			||||||
 | 
					    let base_2_digits = "01";
 | 
				
			||||||
 | 
					    let base_8_digits = base_2_digits + "234567";
 | 
				
			||||||
 | 
					    let base_10_digits = base_8_digits + "89";
 | 
				
			||||||
 | 
					    let base_16_digits = base_10_digits + "abcdef";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let len = string_length(text);
 | 
				
			||||||
 | 
					    if len == 0 {
 | 
				
			||||||
 | 
					        return -1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if text[0] == "0"[0] {
 | 
				
			||||||
 | 
					        if len == 1 {
 | 
				
			||||||
 | 
					            0
 | 
				
			||||||
 | 
					        } else if text[1] == "b"[0] {
 | 
				
			||||||
 | 
					            parse_digits(string_slice(text, 2, -1), 2, base_2_digits)
 | 
				
			||||||
 | 
					        } else if text[1] == "x"[0] {
 | 
				
			||||||
 | 
					            parse_digits(string_slice(text, 2, -1), 16, base_16_digits)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            parse_digits(string_slice(text, 1, -1), 8, base_8_digits)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        parse_digits(text, 10, base_10_digits)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_digits(text: string, base: int, digit_set: string) -> int {
 | 
				
			||||||
 | 
					    let val = 0;
 | 
				
			||||||
 | 
					    let len = string_length(text);
 | 
				
			||||||
 | 
					    for (let i = 0; i < len; i += 1) {
 | 
				
			||||||
 | 
					        let ch = text[i];
 | 
				
			||||||
 | 
					        if not string_contains(digit_set, ch) {
 | 
				
			||||||
 | 
					            return -1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        val = val * base;
 | 
				
			||||||
 | 
					        val += char_val(ch);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    val
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn char_val(ch: int) -> int {
 | 
				
			||||||
 | 
					    if ch >= "0"[0] and ch <= "9"[0] {
 | 
				
			||||||
 | 
					        ch - "0"[0]
 | 
				
			||||||
 | 
					    } else if ch >= "a"[0] and ch <= "f"[0] {
 | 
				
			||||||
 | 
					        ch - "a"[0] + 10
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        -1
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn string_slice(str: string, from: int, to: int) -> string {
 | 
				
			||||||
 | 
					    let result = "";
 | 
				
			||||||
 | 
					    let len = string_length(str);
 | 
				
			||||||
 | 
					    let abs_to =
 | 
				
			||||||
 | 
					        if to >= len { len }
 | 
				
			||||||
 | 
					        else if to < 0 { len + to + 1 }
 | 
				
			||||||
 | 
					        else { to };
 | 
				
			||||||
 | 
					    for (let i = from; i < abs_to; i += 1) {
 | 
				
			||||||
 | 
					        result = string_push_char(result, str[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    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 print(msg: string) #[builtin(Print)] {}
 | 
				
			||||||
 | 
					fn println(msg: string) { print(msg + "\n") }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn string_push_char(str: string, value: int) -> string #[builtin(StringPushChar)] {}
 | 
				
			||||||
 | 
					fn string_length(str: string) -> int #[builtin(StringLength)] {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn itos(number: int) -> string #[builtin(IntToString)] {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										21
									
								
								examples/generic_array.slg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								examples/generic_array.slg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					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,12 +56,50 @@ fn input(prompt: string) -> string {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn is_prime(n: int) -> bool {
 | 
					fn min(a: int, b: int) -> int {
 | 
				
			||||||
    if n == 1 or n == 0{
 | 
					    if b < a { b } else { a }
 | 
				
			||||||
        return false;
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (let i = 2; i < n; i += 1) {
 | 
					    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{
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if n == 1 {
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let n_root = sqrt(n);
 | 
				
			||||||
 | 
					    for (let i = 2; i < n_root; i += 1) {
 | 
				
			||||||
        if remainder(n, i) == 0 {
 | 
					        if remainder(n, i) == 0 {
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -70,7 +108,7 @@ fn is_prime(n: int) -> bool {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn main() {
 | 
					fn main() {
 | 
				
			||||||
    for (let i = 1; i < 10000; i += 1) {
 | 
					    for (let i = 1; i <= 10000; i += 1) {
 | 
				
			||||||
        if is_prime(i) {
 | 
					        if is_prime(i) {
 | 
				
			||||||
            print(int_to_string(i) + " ");
 | 
					            print(int_to_string(i) + " ");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										7
									
								
								examples/refs.slg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								examples/refs.slg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					    let a = 5;
 | 
				
			||||||
 | 
					    let b: &int = &a;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1,56 +1,3 @@
 | 
				
			|||||||
 | 
					 | 
				
			||||||
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() {
 | 
					fn main() {
 | 
				
			||||||
    let i = 0;
 | 
					    let i = 0;
 | 
				
			||||||
    while i < 3 {
 | 
					    while i < 3 {
 | 
				
			||||||
@ -63,18 +10,34 @@ fn main() {
 | 
				
			|||||||
    for char in chars {
 | 
					    for char in chars {
 | 
				
			||||||
        println(string_push_char("", char));
 | 
					        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] {
 | 
					fn string_to_array(value: string) -> [int] {
 | 
				
			||||||
    let result = int_array_new();
 | 
					    let result = array_new::<int>();
 | 
				
			||||||
    let length = string_length(value);
 | 
					    let length = string_length(value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (let i = 0; i < length; i += 1) {
 | 
					    for (let i = 0; i < length; i += 1) {
 | 
				
			||||||
        int_array_push(result, value[i]);
 | 
					        array_push(result, value[i]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    result
 | 
					    result
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										22
									
								
								examples/transient_variable.slg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								examples/transient_variable.slg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					// 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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								examples/unused_variable.slg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								examples/unused_variable.slg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					// mod std;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					    let a = 5;
 | 
				
			||||||
 | 
					    let b = a;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
#include "alloc.hpp"
 | 
					#include "alloc.hpp"
 | 
				
			||||||
#include <format>
 | 
					#include <format>
 | 
				
			||||||
#include <iostream>
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace sliger::heap;
 | 
					using namespace sliger::heap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -14,3 +15,18 @@ auto Array::at(int32_t index) & -> Value&
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    return values.at(static_cast<size_t>(index));
 | 
					    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,6 +19,9 @@ struct Array {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
struct Struct {
 | 
					struct Struct {
 | 
				
			||||||
    std::unordered_map<std::string, Value> fields;
 | 
					    std::unordered_map<std::string, Value> fields;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto at(const std::string&) & -> Value&;
 | 
				
			||||||
 | 
					    void assign(const std::string&, Value&& value);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum class AllocType {
 | 
					enum class AllocType {
 | 
				
			||||||
@ -202,7 +205,7 @@ private:
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    size_t max_size = 4;
 | 
					    size_t max_size = 512;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    std::vector<AllocItem> heap_1;
 | 
					    std::vector<AllocItem> heap_1;
 | 
				
			||||||
    std::vector<AllocItem> heap_2;
 | 
					    std::vector<AllocItem> heap_2;
 | 
				
			||||||
 | 
				
			|||||||
@ -41,7 +41,8 @@ enum class Op : uint32_t {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum class Builtin : uint32_t {
 | 
					enum class Builtin : uint32_t {
 | 
				
			||||||
    IntToString = 0x00,
 | 
					    Exit = 0x00,
 | 
				
			||||||
 | 
					    IntToString = 0x01,
 | 
				
			||||||
    StringConcat = 0x10,
 | 
					    StringConcat = 0x10,
 | 
				
			||||||
    StringEqual = 0x11,
 | 
					    StringEqual = 0x11,
 | 
				
			||||||
    StringCharAt = 0x12,
 | 
					    StringCharAt = 0x12,
 | 
				
			||||||
@ -53,7 +54,9 @@ enum class Builtin : uint32_t {
 | 
				
			|||||||
    ArrayPush = 0x22,
 | 
					    ArrayPush = 0x22,
 | 
				
			||||||
    ArrayAt = 0x23,
 | 
					    ArrayAt = 0x23,
 | 
				
			||||||
    ArrayLength = 0x24,
 | 
					    ArrayLength = 0x24,
 | 
				
			||||||
    StructSet = 0x30,
 | 
					    StructNew = 0x30,
 | 
				
			||||||
 | 
					    StructSet = 0x31,
 | 
				
			||||||
 | 
					    StructAt = 0x32,
 | 
				
			||||||
    Print = 0x40,
 | 
					    Print = 0x40,
 | 
				
			||||||
    FileOpen = 0x41,
 | 
					    FileOpen = 0x41,
 | 
				
			||||||
    FileClose = 0x42,
 | 
					    FileClose = 0x42,
 | 
				
			||||||
 | 
				
			|||||||
@ -289,6 +289,11 @@ void VM::run_instruction()
 | 
				
			|||||||
            this->current_pos = { index, line, col };
 | 
					            this->current_pos = { index, line, col };
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            std::cerr << std::format("unrecognized instruction '{}', pc = {}",
 | 
				
			||||||
 | 
					                std::to_underlying(op), this->pc);
 | 
				
			||||||
 | 
					            std::exit(1);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this->instruction_counter += 1;
 | 
					    this->instruction_counter += 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -300,9 +305,15 @@ void VM::run_builtin(Builtin builtin_id)
 | 
				
			|||||||
            maybe_builtin_to_string(static_cast<uint32_t>(builtin_id)));
 | 
					            maybe_builtin_to_string(static_cast<uint32_t>(builtin_id)));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    switch (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: {
 | 
					        case Builtin::IntToString: {
 | 
				
			||||||
            assert_stack_has(1);
 | 
					            assert_stack_has(1);
 | 
				
			||||||
            auto number = static_cast<int32_t>(stack_pop().as_int().value);
 | 
					            auto number = stack_pop().as_int().value;
 | 
				
			||||||
            auto str = std::to_string(number);
 | 
					            auto str = std::to_string(number);
 | 
				
			||||||
            stack_push(String(str));
 | 
					            stack_push(String(str));
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
@ -325,12 +336,11 @@ void VM::run_builtin(Builtin builtin_id)
 | 
				
			|||||||
            run_array_builtin(builtin_id);
 | 
					            run_array_builtin(builtin_id);
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        case Builtin::StructSet: {
 | 
					        case Builtin::StructNew:
 | 
				
			||||||
            assert_stack_has(2);
 | 
					        case Builtin::StructSet:
 | 
				
			||||||
            std::cerr << std::format("not implemented\n");
 | 
					        case Builtin::StructAt:
 | 
				
			||||||
            std::exit(1);
 | 
					            run_struct_builtin(builtin_id);
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        case Builtin::Print:
 | 
					        case Builtin::Print:
 | 
				
			||||||
        case Builtin::FileOpen:
 | 
					        case Builtin::FileOpen:
 | 
				
			||||||
@ -401,6 +411,7 @@ void VM::run_string_builtin(Builtin builtin_id)
 | 
				
			|||||||
            break;
 | 
					            break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void VM::run_array_builtin(Builtin builtin_id)
 | 
					void VM::run_array_builtin(Builtin builtin_id)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    switch (builtin_id) {
 | 
					    switch (builtin_id) {
 | 
				
			||||||
@ -450,6 +461,40 @@ 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)
 | 
					void VM::run_file_builtin(Builtin builtin_id)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    switch (builtin_id) {
 | 
					    switch (builtin_id) {
 | 
				
			||||||
 | 
				
			|||||||
@ -199,6 +199,7 @@ private:
 | 
				
			|||||||
    void run_builtin(Builtin builtin_id);
 | 
					    void run_builtin(Builtin builtin_id);
 | 
				
			||||||
    void run_string_builtin(Builtin builtin_id);
 | 
					    void run_string_builtin(Builtin builtin_id);
 | 
				
			||||||
    void run_array_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);
 | 
					    void run_file_builtin(Builtin builtin_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    inline void step() { this->pc += 1; }
 | 
					    inline void step() { this->pc += 1; }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										10
									
								
								slige-run.sh
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								slige-run.sh
									
									
									
									
									
								
							@ -3,11 +3,17 @@
 | 
				
			|||||||
set -e
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
echo Text:
 | 
					echo Text:
 | 
				
			||||||
cat $1
 | 
					
 | 
				
			||||||
 | 
					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...
 | 
					echo Compiling $1...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
deno run --allow-read --allow-write compiler/main.ts $1
 | 
					deno run --allow-read --allow-write --check compiler/main.ts $1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
echo Running out.slgbc...
 | 
					echo Running out.slgbc...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										184
									
								
								std/lib.slg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								std/lib.slg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,184 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					// 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
									
									
									
									
									
								
							
							
						
						
									
										151
									
								
								stdlib.slg
									
									
									
									
									
								
							@ -1,151 +0,0 @@
 | 
				
			|||||||
 | 
					 | 
				
			||||||
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
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
							
								
								
									
										11
									
								
								tests/array_literal.slg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								tests/array_literal.slg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					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");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										10
									
								
								tests/char_literal.slg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								tests/char_literal.slg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					fn exit(status_code: int) #[builtin(Exit)] {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					    if 'A' != 65 {
 | 
				
			||||||
 | 
					        exit(1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    exit(0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										23
									
								
								tests/generics.slg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								tests/generics.slg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										19
									
								
								tests/import_modules_entry.slg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								tests/import_modules_entry.slg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										5
									
								
								tests/import_modules_inner.slg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/import_modules_inner.slg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					fn inner_fn(a: int) -> int {
 | 
				
			||||||
 | 
					    a + 32
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										25
									
								
								tests/modules.slg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								tests/modules.slg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										20
									
								
								tests/struct_literal.slg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								tests/struct_literal.slg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					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