mirror of
				https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
				synced 2025-11-04 08:57:00 +01:00 
			
		
		
		
	compiler: add backup compiler
This commit is contained in:
		
							parent
							
								
									ece66bd2b8
								
							
						
					
					
						commit
						dcdfc32da6
					
				
							
								
								
									
										51
									
								
								backup-compiler/ast.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								backup-compiler/ast.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					export type Block = {
 | 
				
			||||||
 | 
					    id: number;
 | 
				
			||||||
 | 
					    lineEntry: number;
 | 
				
			||||||
 | 
					    lineExit: number;
 | 
				
			||||||
 | 
					    stmts: Stmt[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Stmt = {
 | 
				
			||||||
 | 
					    id: number;
 | 
				
			||||||
 | 
					    line: number;
 | 
				
			||||||
 | 
					    kind: StmtKind;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type StmtKind =
 | 
				
			||||||
 | 
					    | { tag: "error" }
 | 
				
			||||||
 | 
					    | { tag: "fn" } & FnStmt
 | 
				
			||||||
 | 
					    | { tag: "let"; ident: string; expr?: Expr }
 | 
				
			||||||
 | 
					    | { tag: "loop"; body: Block }
 | 
				
			||||||
 | 
					    | { tag: "if"; expr: Expr; truthy: Block; falsy?: Block }
 | 
				
			||||||
 | 
					    | { tag: "return"; expr?: Expr }
 | 
				
			||||||
 | 
					    | { tag: "break" }
 | 
				
			||||||
 | 
					    | { tag: "assign"; subject: Expr; expr: Expr }
 | 
				
			||||||
 | 
					    | { tag: "expr"; expr: Expr };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type FnStmt = {
 | 
				
			||||||
 | 
					    ident: string;
 | 
				
			||||||
 | 
					    attrs: Attr[];
 | 
				
			||||||
 | 
					    params: string[];
 | 
				
			||||||
 | 
					    body: Block;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Expr = {
 | 
				
			||||||
 | 
					    id: number;
 | 
				
			||||||
 | 
					    line: number;
 | 
				
			||||||
 | 
					    kind: ExprKind;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ExprKind =
 | 
				
			||||||
 | 
					    | { tag: "error" }
 | 
				
			||||||
 | 
					    | { tag: "ident"; ident: string }
 | 
				
			||||||
 | 
					    | { tag: "int"; val: number }
 | 
				
			||||||
 | 
					    | { tag: "string"; val: string }
 | 
				
			||||||
 | 
					    | { tag: "call"; expr: Expr; args: Expr[] }
 | 
				
			||||||
 | 
					    | { tag: "binary"; op: BinaryOp; left: Expr; right: Expr };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type BinaryOp = "<" | "==" | "+" | "*";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Attr = {
 | 
				
			||||||
 | 
					    ident: string;
 | 
				
			||||||
 | 
					    args: Expr[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										5
									
								
								backup-compiler/deno.jsonc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								backup-compiler/deno.jsonc
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "fmt": {
 | 
				
			||||||
 | 
					        "indentWidth": 4
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										11
									
								
								backup-compiler/deno.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								backup-compiler/deno.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "version": "4",
 | 
				
			||||||
 | 
					  "specifiers": {
 | 
				
			||||||
 | 
					    "jsr:@std/yaml@*": "1.0.5"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "jsr": {
 | 
				
			||||||
 | 
					    "@std/yaml@1.0.5": {
 | 
				
			||||||
 | 
					      "integrity": "71ba3d334305ee2149391931508b2c293a8490f94a337eef3a09cade1a2a2742"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										829
									
								
								backup-compiler/front.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										829
									
								
								backup-compiler/front.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,829 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					    Attr,
 | 
				
			||||||
 | 
					    BinaryOp,
 | 
				
			||||||
 | 
					    Block,
 | 
				
			||||||
 | 
					    Expr,
 | 
				
			||||||
 | 
					    ExprKind,
 | 
				
			||||||
 | 
					    Stmt,
 | 
				
			||||||
 | 
					    StmtKind,
 | 
				
			||||||
 | 
					} from "./ast.ts";
 | 
				
			||||||
 | 
					import { Ty, tyToString } from "./ty.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Checker {
 | 
				
			||||||
 | 
					    private stmtTys = new Map<number, Ty>();
 | 
				
			||||||
 | 
					    private exprTys = new Map<number, Ty>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public errorOccured = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public constructor(
 | 
				
			||||||
 | 
					        private re: Resols,
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public fnStmtTy(stmt: Stmt): Ty {
 | 
				
			||||||
 | 
					        const k = stmt.kind;
 | 
				
			||||||
 | 
					        if (k.tag !== "fn") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (this.stmtTys.has(stmt.id)) {
 | 
				
			||||||
 | 
					            return this.stmtTys.get(stmt.id)!;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const params = k.params.map((_): Ty => ({ tag: "int" }));
 | 
				
			||||||
 | 
					        const returnTy: Ty = { tag: "int" };
 | 
				
			||||||
 | 
					        const ty: Ty = { tag: "fn", stmt, params, returnTy };
 | 
				
			||||||
 | 
					        this.stmtTys.set(stmt.id, ty);
 | 
				
			||||||
 | 
					        return ty;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public paramTy(stmt: Stmt, i: number): Ty {
 | 
				
			||||||
 | 
					        const ty = this.fnStmtTy(stmt);
 | 
				
			||||||
 | 
					        if (ty.tag !== "fn") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return ty.params[i];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public letStmtTy(stmt: Stmt): Ty {
 | 
				
			||||||
 | 
					        const k = stmt.kind;
 | 
				
			||||||
 | 
					        if (k.tag !== "let") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (this.stmtTys.has(stmt.id)) {
 | 
				
			||||||
 | 
					            return this.stmtTys.get(stmt.id)!;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const ty: Ty = k.expr ? this.exprTy(k.expr) : { tag: "int" };
 | 
				
			||||||
 | 
					        this.stmtTys.set(stmt.id, ty);
 | 
				
			||||||
 | 
					        return ty;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public exprTy(expr: Expr): Ty {
 | 
				
			||||||
 | 
					        if (this.exprTys.has(expr.id)) {
 | 
				
			||||||
 | 
					            return this.exprTys.get(expr.id)!;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const ty = ((): Ty => {
 | 
				
			||||||
 | 
					            const k = expr.kind;
 | 
				
			||||||
 | 
					            switch (k.tag) {
 | 
				
			||||||
 | 
					                case "error":
 | 
				
			||||||
 | 
					                    return { tag: "error" };
 | 
				
			||||||
 | 
					                case "ident": {
 | 
				
			||||||
 | 
					                    const re = this.re.expr(expr);
 | 
				
			||||||
 | 
					                    if (!re) {
 | 
				
			||||||
 | 
					                        throw new Error();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    switch (re.tag) {
 | 
				
			||||||
 | 
					                        case "fn":
 | 
				
			||||||
 | 
					                            return this.fnStmtTy(re.stmt);
 | 
				
			||||||
 | 
					                        case "param":
 | 
				
			||||||
 | 
					                            return this.paramTy(re.stmt, re.i);
 | 
				
			||||||
 | 
					                        case "let":
 | 
				
			||||||
 | 
					                            return this.letStmtTy(re.stmt);
 | 
				
			||||||
 | 
					                        case "loop":
 | 
				
			||||||
 | 
					                            throw new Error();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    throw new Error();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                case "int":
 | 
				
			||||||
 | 
					                    return { tag: "int" };
 | 
				
			||||||
 | 
					                case "string":
 | 
				
			||||||
 | 
					                    return { tag: "string" };
 | 
				
			||||||
 | 
					                case "call": {
 | 
				
			||||||
 | 
					                    const callee = this.exprTy(k.expr);
 | 
				
			||||||
 | 
					                    if (callee.tag !== "fn") {
 | 
				
			||||||
 | 
					                        this.report("call to non-function", expr.line);
 | 
				
			||||||
 | 
					                        return { tag: "error" };
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    if (callee.params.length !== k.args.length) {
 | 
				
			||||||
 | 
					                        this.report(
 | 
				
			||||||
 | 
					                            `argument mismatch, expected ${callee.params.length}, got ${k.args.length}`,
 | 
				
			||||||
 | 
					                            expr.line,
 | 
				
			||||||
 | 
					                        );
 | 
				
			||||||
 | 
					                        return { tag: "error" };
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    const args = k.args.map((arg) => this.exprTy(arg));
 | 
				
			||||||
 | 
					                    for (const [i, param] of callee.params.entries()) {
 | 
				
			||||||
 | 
					                        if (!this.assignable(args[i], param)) {
 | 
				
			||||||
 | 
					                            this.report(
 | 
				
			||||||
 | 
					                                `argument mismatch, type '${
 | 
				
			||||||
 | 
					                                    tyToString(args[i])
 | 
				
			||||||
 | 
					                                }' not assignable to '${tyToString(param)}'`,
 | 
				
			||||||
 | 
					                                expr.line,
 | 
				
			||||||
 | 
					                            );
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    return callee.returnTy;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                case "binary": {
 | 
				
			||||||
 | 
					                    const left = this.exprTy(k.left);
 | 
				
			||||||
 | 
					                    const right = this.exprTy(k.right);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const cfg = (op: BinaryOp, l: Ty, r: Ty = l) =>
 | 
				
			||||||
 | 
					                        k.op === op && this.assignable(left, l) &&
 | 
				
			||||||
 | 
					                        this.assignable(right, r);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (cfg("<", { tag: "int" })) {
 | 
				
			||||||
 | 
					                        return { tag: "int" };
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    if (cfg("==", { tag: "int" })) {
 | 
				
			||||||
 | 
					                        return { tag: "int" };
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    if (cfg("+", { tag: "int" })) {
 | 
				
			||||||
 | 
					                        return { tag: "int" };
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    if (cfg("*", { tag: "int" })) {
 | 
				
			||||||
 | 
					                        return { tag: "int" };
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    this.report(
 | 
				
			||||||
 | 
					                        `cannot '${k.op}' type '${tyToString(left)}' with '${
 | 
				
			||||||
 | 
					                            tyToString(right)
 | 
				
			||||||
 | 
					                        }'`,
 | 
				
			||||||
 | 
					                        expr.line,
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    return { tag: "error" };
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const _: never = k;
 | 
				
			||||||
 | 
					        })();
 | 
				
			||||||
 | 
					        this.exprTys.set(expr.id, ty);
 | 
				
			||||||
 | 
					        return ty;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private assignable(a: Ty, b: Ty): boolean {
 | 
				
			||||||
 | 
					        if (a.tag !== b.tag) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (a.tag === "fn" && b.tag === "fn" && a.stmt.id !== b.stmt.id) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private report(msg: string, line: number) {
 | 
				
			||||||
 | 
					        this.errorOccured = true;
 | 
				
			||||||
 | 
					        //console.error(`parser: ${msg} on line ${line}`);
 | 
				
			||||||
 | 
					        throw new Error(`parser: ${msg} on line ${line}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Resolve =
 | 
				
			||||||
 | 
					    | { tag: "fn"; stmt: Stmt }
 | 
				
			||||||
 | 
					    | { tag: "param"; stmt: Stmt; i: number }
 | 
				
			||||||
 | 
					    | { tag: "let"; stmt: Stmt }
 | 
				
			||||||
 | 
					    | { tag: "loop"; stmt: Stmt };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function resolveToString(re: Resolve): string {
 | 
				
			||||||
 | 
					    switch (re.tag) {
 | 
				
			||||||
 | 
					        case "fn":
 | 
				
			||||||
 | 
					            return `fn(id: ${re.stmt.id}, line: ${re.stmt.line})`;
 | 
				
			||||||
 | 
					        case "param":
 | 
				
			||||||
 | 
					            return `param(i: ${re.i})`;
 | 
				
			||||||
 | 
					        case "let":
 | 
				
			||||||
 | 
					            return `let(id: ${re.stmt.id}, line: ${re.stmt.line})`;
 | 
				
			||||||
 | 
					        case "loop":
 | 
				
			||||||
 | 
					            return `loop(id: ${re.stmt.id}, line: ${re.stmt.line})`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Resols {
 | 
				
			||||||
 | 
					    public constructor(
 | 
				
			||||||
 | 
					        private stmtResols: Map<number, Resolve>,
 | 
				
			||||||
 | 
					        private exprResols: Map<number, Resolve>,
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public stmt(stmt: Stmt): Resolve | undefined {
 | 
				
			||||||
 | 
					        return this.stmtResols.get(stmt.id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public expr(expr: Expr): Resolve | undefined {
 | 
				
			||||||
 | 
					        return this.exprResols.get(expr.id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Syms {
 | 
				
			||||||
 | 
					    val(ident: string): Resolve | undefined;
 | 
				
			||||||
 | 
					    defineVal(ident: string, res: Resolve): void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class RootSyms implements Syms {
 | 
				
			||||||
 | 
					    private exprResols = new Map<string, Resolve>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val(ident: string): Resolve | undefined {
 | 
				
			||||||
 | 
					        return this.exprResols.get(ident);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    defineVal(ident: string, re: Resolve): void {
 | 
				
			||||||
 | 
					        this.exprResols.set(ident, re);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class FnSyms implements Syms {
 | 
				
			||||||
 | 
					    private exprResols = new Map<string, Resolve>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public constructor(
 | 
				
			||||||
 | 
					        private parent: Syms,
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val(ident: string): Resolve | undefined {
 | 
				
			||||||
 | 
					        const local = this.exprResols.get(ident);
 | 
				
			||||||
 | 
					        if (local) {
 | 
				
			||||||
 | 
					            return local;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const parent = this.parent.val(ident);
 | 
				
			||||||
 | 
					        if (!parent) {
 | 
				
			||||||
 | 
					            return undefined;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (parent.tag === "let") {
 | 
				
			||||||
 | 
					            return undefined;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return parent;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    defineVal(ident: string, re: Resolve): void {
 | 
				
			||||||
 | 
					        this.exprResols.set(ident, re);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class NormalSyms implements Syms {
 | 
				
			||||||
 | 
					    private exprResols = new Map<string, Resolve>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public constructor(
 | 
				
			||||||
 | 
					        private parent: Syms,
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val(ident: string): Resolve | undefined {
 | 
				
			||||||
 | 
					        return this.exprResols.get(ident) ?? this.parent.val(ident);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    defineVal(ident: string, re: Resolve): void {
 | 
				
			||||||
 | 
					        this.exprResols.set(ident, re);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Resolver {
 | 
				
			||||||
 | 
					    private syms: Syms = new RootSyms();
 | 
				
			||||||
 | 
					    private stmtResols = new Map<number, Resolve>();
 | 
				
			||||||
 | 
					    private exprResols = new Map<number, Resolve>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private blockFnsStack: Stmt[][] = [];
 | 
				
			||||||
 | 
					    private loopStack: Stmt[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public errorOccured = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public constructor(
 | 
				
			||||||
 | 
					        private ast: Stmt[],
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public resolve(): Resols {
 | 
				
			||||||
 | 
					        this.resolveStmts(this.ast);
 | 
				
			||||||
 | 
					        return new Resols(
 | 
				
			||||||
 | 
					            this.stmtResols,
 | 
				
			||||||
 | 
					            this.exprResols,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private resolveStmts(stmts: Stmt[]) {
 | 
				
			||||||
 | 
					        this.blockFnsStack.push([]);
 | 
				
			||||||
 | 
					        for (const stmt of stmts) {
 | 
				
			||||||
 | 
					            this.resolveStmt(stmt);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const blockFns = this.blockFnsStack.pop()!;
 | 
				
			||||||
 | 
					        for (const fn of blockFns) {
 | 
				
			||||||
 | 
					            const outerLoops = this.loopStack;
 | 
				
			||||||
 | 
					            this.loopStack = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const outerSyms = this.syms;
 | 
				
			||||||
 | 
					            this.syms = new FnSyms(outerSyms);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const k = fn.kind;
 | 
				
			||||||
 | 
					            if (k.tag !== "fn") {
 | 
				
			||||||
 | 
					                throw new Error();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            for (const [i, param] of k.params.entries()) {
 | 
				
			||||||
 | 
					                this.syms.defineVal(param, { tag: "param", stmt: fn, i });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            this.resolveBlock(k.body);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.syms = outerSyms;
 | 
				
			||||||
 | 
					            this.loopStack = outerLoops;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private resolveBlock(block: Block) {
 | 
				
			||||||
 | 
					        const outerSyms = this.syms;
 | 
				
			||||||
 | 
					        this.syms = new NormalSyms(outerSyms);
 | 
				
			||||||
 | 
					        this.resolveStmts(block.stmts);
 | 
				
			||||||
 | 
					        this.syms = outerSyms;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private resolveStmt(stmt: Stmt) {
 | 
				
			||||||
 | 
					        const k = stmt.kind;
 | 
				
			||||||
 | 
					        switch (k.tag) {
 | 
				
			||||||
 | 
					            case "error":
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            case "fn":
 | 
				
			||||||
 | 
					                this.syms.defineVal(k.ident, { tag: "fn", stmt });
 | 
				
			||||||
 | 
					                this.blockFnsStack.at(-1)!.push(stmt);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            case "let":
 | 
				
			||||||
 | 
					                this.syms.defineVal(k.ident, { tag: "let", stmt });
 | 
				
			||||||
 | 
					                k.expr && this.resolveExpr(k.expr);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            case "loop":
 | 
				
			||||||
 | 
					                this.loopStack.push(stmt);
 | 
				
			||||||
 | 
					                this.resolveBlock(k.body);
 | 
				
			||||||
 | 
					                this.loopStack.pop();
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            case "if":
 | 
				
			||||||
 | 
					                this.resolveExpr(k.expr);
 | 
				
			||||||
 | 
					                this.resolveBlock(k.truthy);
 | 
				
			||||||
 | 
					                k.falsy && this.resolveBlock(k.falsy);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            case "return":
 | 
				
			||||||
 | 
					                k.expr && this.resolveExpr(k.expr);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            case "break": {
 | 
				
			||||||
 | 
					                const loop = this.loopStack.at(-1);
 | 
				
			||||||
 | 
					                if (!loop) {
 | 
				
			||||||
 | 
					                    return this.report("break outside loop", stmt.line);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                this.stmtResols.set(stmt.id, { tag: "loop", stmt: loop });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "assign":
 | 
				
			||||||
 | 
					                this.resolveExpr(k.subject);
 | 
				
			||||||
 | 
					                this.resolveExpr(k.expr);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            case "expr":
 | 
				
			||||||
 | 
					                this.resolveExpr(k.expr);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const _: never = k;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private resolveExpr(expr: Expr) {
 | 
				
			||||||
 | 
					        const k = expr.kind;
 | 
				
			||||||
 | 
					        switch (k.tag) {
 | 
				
			||||||
 | 
					            case "error":
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            case "ident": {
 | 
				
			||||||
 | 
					                const re = this.syms.val(k.ident);
 | 
				
			||||||
 | 
					                if (!re) {
 | 
				
			||||||
 | 
					                    this.report(`ident '${k.ident}' not defined`, expr.line);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                this.exprResols.set(expr.id, re);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "int":
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            case "string":
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            case "call":
 | 
				
			||||||
 | 
					                this.resolveExpr(k.expr);
 | 
				
			||||||
 | 
					                for (const arg of k.args) {
 | 
				
			||||||
 | 
					                    this.resolveExpr(arg);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            case "binary":
 | 
				
			||||||
 | 
					                this.resolveExpr(k.left);
 | 
				
			||||||
 | 
					                this.resolveExpr(k.right);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const _: never = k;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private report(msg: string, line: number) {
 | 
				
			||||||
 | 
					        this.errorOccured = true;
 | 
				
			||||||
 | 
					        //console.error(`parser: ${msg} on line ${line}`);
 | 
				
			||||||
 | 
					        throw new Error(`parser: ${msg} on line ${line}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Parser {
 | 
				
			||||||
 | 
					    private toks: Tok[];
 | 
				
			||||||
 | 
					    private i = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private blockIds = 0;
 | 
				
			||||||
 | 
					    private stmtIds = 0;
 | 
				
			||||||
 | 
					    private exprIds = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private last: Tok;
 | 
				
			||||||
 | 
					    private eaten?: Tok;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public errorOccured = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public constructor(private text: string) {
 | 
				
			||||||
 | 
					        this.toks = lex(this.text);
 | 
				
			||||||
 | 
					        this.last = this.toks[0];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public parse() {
 | 
				
			||||||
 | 
					        return this.parseStmts();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseStmts(): Stmt[] {
 | 
				
			||||||
 | 
					        const stmts: Stmt[] = [];
 | 
				
			||||||
 | 
					        while (!this.done()) {
 | 
				
			||||||
 | 
					            stmts.push(this.parseStmt());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return stmts;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseBlock(): Block {
 | 
				
			||||||
 | 
					        const lineEntry = this.curr().line;
 | 
				
			||||||
 | 
					        this.step();
 | 
				
			||||||
 | 
					        const stmts: Stmt[] = [];
 | 
				
			||||||
 | 
					        if (!this.done() && !this.test("}")) {
 | 
				
			||||||
 | 
					            stmts.push(this.parseStmt());
 | 
				
			||||||
 | 
					            while (!this.done() && !this.test("}")) {
 | 
				
			||||||
 | 
					                stmts.push(this.parseStmt());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const id = this.blockIds++;
 | 
				
			||||||
 | 
					        if (!this.eat("}")) {
 | 
				
			||||||
 | 
					            this.report("expected '}'");
 | 
				
			||||||
 | 
					            return { id, lineEntry, lineExit: 0, stmts: [] };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const lineExit = this.eaten!.line;
 | 
				
			||||||
 | 
					        return { id, lineEntry, lineExit, stmts };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseStmt(): Stmt {
 | 
				
			||||||
 | 
					        const attrs = this.parseAttrs();
 | 
				
			||||||
 | 
					        if (this.test("fn")) {
 | 
				
			||||||
 | 
					            return this.parseFnStmt(attrs);
 | 
				
			||||||
 | 
					        } else if (this.test("let")) {
 | 
				
			||||||
 | 
					            return this.parseLetStmt();
 | 
				
			||||||
 | 
					        } else if (this.test("loop")) {
 | 
				
			||||||
 | 
					            return this.parseLoopStmt();
 | 
				
			||||||
 | 
					        } else if (this.test("if")) {
 | 
				
			||||||
 | 
					            return this.parseIfStmt();
 | 
				
			||||||
 | 
					        } else if (this.test("return")) {
 | 
				
			||||||
 | 
					            return this.parseReturnStmt();
 | 
				
			||||||
 | 
					        } else if (this.test("break")) {
 | 
				
			||||||
 | 
					            return this.parseBreakStmt();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            const subject = this.parseExpr();
 | 
				
			||||||
 | 
					            let stmt: Stmt;
 | 
				
			||||||
 | 
					            if (this.eat("=")) {
 | 
				
			||||||
 | 
					                const expr = this.parseExpr();
 | 
				
			||||||
 | 
					                stmt = this.stmt(
 | 
				
			||||||
 | 
					                    { tag: "assign", subject, expr },
 | 
				
			||||||
 | 
					                    subject.line,
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                stmt = this.stmt({ tag: "expr", expr: subject }, subject.line);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (!this.eat(";")) {
 | 
				
			||||||
 | 
					                this.report("expected ';'");
 | 
				
			||||||
 | 
					                return this.stmt({ tag: "error" }, stmt.line);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return stmt;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseAttrs(): Attr[] {
 | 
				
			||||||
 | 
					        const attrs: Attr[] = [];
 | 
				
			||||||
 | 
					        while (this.eat("#")) {
 | 
				
			||||||
 | 
					            if (!this.eat("[")) {
 | 
				
			||||||
 | 
					                this.report("expected '['");
 | 
				
			||||||
 | 
					                return attrs;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (!this.eat("ident")) {
 | 
				
			||||||
 | 
					                this.report("expected 'ident'");
 | 
				
			||||||
 | 
					                return attrs;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const ident = this.eaten!.identVal!;
 | 
				
			||||||
 | 
					            const args: Expr[] = [];
 | 
				
			||||||
 | 
					            if (this.eat("(")) {
 | 
				
			||||||
 | 
					                if (!this.done() && !this.test(")")) {
 | 
				
			||||||
 | 
					                    args.push(this.parseExpr());
 | 
				
			||||||
 | 
					                    while (!this.done() && !this.test(")")) {
 | 
				
			||||||
 | 
					                        if (!this.eat(",")) {
 | 
				
			||||||
 | 
					                            this.report("expected ','");
 | 
				
			||||||
 | 
					                            return attrs;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        if (this.test(")")) {
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        args.push(this.parseExpr());
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (!this.eat(")")) {
 | 
				
			||||||
 | 
					                    this.report("expected ')'");
 | 
				
			||||||
 | 
					                    return attrs;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (!this.eat("]")) {
 | 
				
			||||||
 | 
					                this.report("expected ']'");
 | 
				
			||||||
 | 
					                return attrs;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            attrs.push({ ident, args });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return attrs;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseFnStmt(attrs: Attr[]): Stmt {
 | 
				
			||||||
 | 
					        const line = this.curr().line;
 | 
				
			||||||
 | 
					        this.step();
 | 
				
			||||||
 | 
					        if (!this.eat("ident")) {
 | 
				
			||||||
 | 
					            this.report("expected 'ident'");
 | 
				
			||||||
 | 
					            return this.stmt({ tag: "error" }, line);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const ident = this.eaten!.identVal!;
 | 
				
			||||||
 | 
					        if (!this.eat("(")) {
 | 
				
			||||||
 | 
					            this.report("expected '('");
 | 
				
			||||||
 | 
					            return this.stmt({ tag: "error" }, line);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const params: string[] = [];
 | 
				
			||||||
 | 
					        if (!this.done() && !this.test(")")) {
 | 
				
			||||||
 | 
					            if (!this.eat("ident")) {
 | 
				
			||||||
 | 
					                this.report("expected 'ident'");
 | 
				
			||||||
 | 
					                return this.stmt({ tag: "error" }, line);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            params.push(this.eaten!.identVal!);
 | 
				
			||||||
 | 
					            while (!this.done() && !this.test(")")) {
 | 
				
			||||||
 | 
					                if (!this.eat(",")) {
 | 
				
			||||||
 | 
					                    this.report("expected ','");
 | 
				
			||||||
 | 
					                    return this.stmt({ tag: "error" }, line);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (this.test(")")) {
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (!this.eat("ident")) {
 | 
				
			||||||
 | 
					                    this.report("expected 'ident'");
 | 
				
			||||||
 | 
					                    return this.stmt({ tag: "error" }, line);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                params.push(this.eaten!.identVal!);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!this.eat(")")) {
 | 
				
			||||||
 | 
					            this.report("expected ')'");
 | 
				
			||||||
 | 
					            return this.stmt({ tag: "error" }, line);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!this.test("{")) {
 | 
				
			||||||
 | 
					            this.report("expected block");
 | 
				
			||||||
 | 
					            return this.stmt({ tag: "error" }, line);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const body = this.parseBlock();
 | 
				
			||||||
 | 
					        return this.stmt({ tag: "fn", ident, attrs, params, body }, line);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseLetStmt(): Stmt {
 | 
				
			||||||
 | 
					        const line = this.curr().line;
 | 
				
			||||||
 | 
					        this.step();
 | 
				
			||||||
 | 
					        if (!this.eat("ident")) {
 | 
				
			||||||
 | 
					            this.report("expected 'ident'");
 | 
				
			||||||
 | 
					            return this.stmt({ tag: "error" }, line);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const ident = this.eaten!.identVal!;
 | 
				
			||||||
 | 
					        if (!this.eat("=")) {
 | 
				
			||||||
 | 
					            if (!this.eat(";")) {
 | 
				
			||||||
 | 
					                this.report("expected ';'");
 | 
				
			||||||
 | 
					                return this.stmt({ tag: "error" }, line);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return this.stmt({ tag: "let", ident }, line);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const expr = this.parseExpr();
 | 
				
			||||||
 | 
					        if (!this.eat(";")) {
 | 
				
			||||||
 | 
					            this.report("expected ';'");
 | 
				
			||||||
 | 
					            return this.stmt({ tag: "error" }, line);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return this.stmt({ tag: "let", ident, expr }, line);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseLoopStmt(): Stmt {
 | 
				
			||||||
 | 
					        const line = this.curr().line;
 | 
				
			||||||
 | 
					        this.step();
 | 
				
			||||||
 | 
					        if (!this.test("{")) {
 | 
				
			||||||
 | 
					            this.report("expected block");
 | 
				
			||||||
 | 
					            return this.stmt({ tag: "error" }, line);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const body = this.parseBlock();
 | 
				
			||||||
 | 
					        return this.stmt({ tag: "loop", body }, line);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseIfStmt(): Stmt {
 | 
				
			||||||
 | 
					        const line = this.curr().line;
 | 
				
			||||||
 | 
					        this.step();
 | 
				
			||||||
 | 
					        const expr = this.parseExpr();
 | 
				
			||||||
 | 
					        if (!this.test("{")) {
 | 
				
			||||||
 | 
					            this.report("expected block");
 | 
				
			||||||
 | 
					            return this.stmt({ tag: "error" }, line);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const truthy = this.parseBlock();
 | 
				
			||||||
 | 
					        if (!this.eat("else")) {
 | 
				
			||||||
 | 
					            return this.stmt({ tag: "if", expr, truthy }, line);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!this.test("{")) {
 | 
				
			||||||
 | 
					            this.report("expected block");
 | 
				
			||||||
 | 
					            return this.stmt({ tag: "error" }, line);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const falsy = this.parseBlock();
 | 
				
			||||||
 | 
					        return this.stmt({ tag: "if", expr, truthy, falsy }, line);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseReturnStmt(): Stmt {
 | 
				
			||||||
 | 
					        const line = this.curr().line;
 | 
				
			||||||
 | 
					        this.step();
 | 
				
			||||||
 | 
					        if (this.eat(";")) {
 | 
				
			||||||
 | 
					            return this.stmt({ tag: "return" }, line);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const expr = this.parseExpr();
 | 
				
			||||||
 | 
					        if (!this.eat(";")) {
 | 
				
			||||||
 | 
					            this.report("expected ';'");
 | 
				
			||||||
 | 
					            return this.stmt({ tag: "error" }, line);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return this.stmt({ tag: "return", expr }, line);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseBreakStmt(): Stmt {
 | 
				
			||||||
 | 
					        const line = this.curr().line;
 | 
				
			||||||
 | 
					        this.step();
 | 
				
			||||||
 | 
					        if (!this.eat(";")) {
 | 
				
			||||||
 | 
					            this.report("expected ';'");
 | 
				
			||||||
 | 
					            return this.stmt({ tag: "error" }, line);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return this.stmt({ tag: "break" }, line);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseExpr(): Expr {
 | 
				
			||||||
 | 
					        return this.parseBinaryExpr();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseBinaryExpr(prec = 4): Expr {
 | 
				
			||||||
 | 
					        if (prec == 0) {
 | 
				
			||||||
 | 
					            return this.parsePostfixExpr();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const ops: [BinaryOp, number][] = [
 | 
				
			||||||
 | 
					            ["<", 4],
 | 
				
			||||||
 | 
					            ["==", 3],
 | 
				
			||||||
 | 
					            ["+", 2],
 | 
				
			||||||
 | 
					            ["*", 1],
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let left = this.parseBinaryExpr(prec - 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let should_continue = true;
 | 
				
			||||||
 | 
					        while (should_continue) {
 | 
				
			||||||
 | 
					            should_continue = false;
 | 
				
			||||||
 | 
					            for (const [op, p] of ops) {
 | 
				
			||||||
 | 
					                if (prec >= p && this.eat(op)) {
 | 
				
			||||||
 | 
					                    const right = this.parseBinaryExpr(prec - 1);
 | 
				
			||||||
 | 
					                    left = this.expr(
 | 
				
			||||||
 | 
					                        { tag: "binary", op, left, right },
 | 
				
			||||||
 | 
					                        left.line,
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    should_continue = true;
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return left;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parsePostfixExpr(): Expr {
 | 
				
			||||||
 | 
					        let expr = this.parseOperandExpr();
 | 
				
			||||||
 | 
					        while (true) {
 | 
				
			||||||
 | 
					            if (this.eat("(")) {
 | 
				
			||||||
 | 
					                const args: Expr[] = [];
 | 
				
			||||||
 | 
					                if (!this.done() && !this.test(")")) {
 | 
				
			||||||
 | 
					                    args.push(this.parseExpr());
 | 
				
			||||||
 | 
					                    while (!this.done() && !this.test(")")) {
 | 
				
			||||||
 | 
					                        if (!this.eat(",")) {
 | 
				
			||||||
 | 
					                            this.report("expected ','");
 | 
				
			||||||
 | 
					                            return this.expr({ tag: "error" }, this.last.line);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        if (this.test(")")) {
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        args.push(this.parseExpr());
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (!this.eat(")")) {
 | 
				
			||||||
 | 
					                    this.report("expected ')'");
 | 
				
			||||||
 | 
					                    return this.expr({ tag: "error" }, this.last.line);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                expr = this.expr({ tag: "call", expr, args }, expr.line);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return expr;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private parseOperandExpr(): Expr {
 | 
				
			||||||
 | 
					        if (this.eat("ident")) {
 | 
				
			||||||
 | 
					            return this.expr(
 | 
				
			||||||
 | 
					                { tag: "ident", ident: this.eaten!.identVal! },
 | 
				
			||||||
 | 
					                this.eaten!.line,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        } else if (this.eat("int")) {
 | 
				
			||||||
 | 
					            return this.expr(
 | 
				
			||||||
 | 
					                { tag: "int", val: this.eaten!.intVal! },
 | 
				
			||||||
 | 
					                this.eaten!.line,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        } else if (this.eat("string")) {
 | 
				
			||||||
 | 
					            return this.expr(
 | 
				
			||||||
 | 
					                { tag: "string", val: this.eaten?.stringVal! },
 | 
				
			||||||
 | 
					                this.eaten!.line,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            this.report("expected expr");
 | 
				
			||||||
 | 
					            return this.expr({ tag: "error" }, this.last!.line);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private stmt(kind: StmtKind, line: number): Stmt {
 | 
				
			||||||
 | 
					        const id = this.stmtIds++;
 | 
				
			||||||
 | 
					        return { id, line, kind };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private expr(kind: ExprKind, line: number): Expr {
 | 
				
			||||||
 | 
					        const id = this.exprIds++;
 | 
				
			||||||
 | 
					        return { id, line, kind };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private eat(type: string): boolean {
 | 
				
			||||||
 | 
					        if (this.test(type)) {
 | 
				
			||||||
 | 
					            this.eaten = this.curr();
 | 
				
			||||||
 | 
					            this.step();
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private step() {
 | 
				
			||||||
 | 
					        this.i += 1;
 | 
				
			||||||
 | 
					        if (!this.done()) {
 | 
				
			||||||
 | 
					            this.last = this.curr();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private test(type: string) {
 | 
				
			||||||
 | 
					        return !this.done() && this.curr().type === type;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private curr(): Tok {
 | 
				
			||||||
 | 
					        return this.toks[this.i];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private done(): boolean {
 | 
				
			||||||
 | 
					        return this.i >= this.toks.length;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private report(msg: string, line = this.last.line) {
 | 
				
			||||||
 | 
					        this.errorOccured = true;
 | 
				
			||||||
 | 
					        //console.error(`parser: ${msg} on line ${line}`);
 | 
				
			||||||
 | 
					        throw new Error(`parser: ${msg} on line ${line}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Tok = {
 | 
				
			||||||
 | 
					    type: string;
 | 
				
			||||||
 | 
					    line: number;
 | 
				
			||||||
 | 
					    intVal?: number;
 | 
				
			||||||
 | 
					    stringVal?: string;
 | 
				
			||||||
 | 
					    identVal?: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function lex(text: string): Tok[] {
 | 
				
			||||||
 | 
					    const ops = "(){}[]<>+*=,;#\n";
 | 
				
			||||||
 | 
					    const kws = ["let", "fn", "return", "if", "else", "loop", "break"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return ops
 | 
				
			||||||
 | 
					        .split("")
 | 
				
			||||||
 | 
					        .reduce((text, op) =>
 | 
				
			||||||
 | 
					            text
 | 
				
			||||||
 | 
					                .replaceAll(/\/\/.*?$/mg, "")
 | 
				
			||||||
 | 
					                .replaceAll(op, ` ${op} `)
 | 
				
			||||||
 | 
					                .replaceAll(" =  = ", " == ")
 | 
				
			||||||
 | 
					                .replaceAll(/\\ /g, "\\SPACE"), text)
 | 
				
			||||||
 | 
					        .split(/[ \t\r]/)
 | 
				
			||||||
 | 
					        .filter((val) => val !== "")
 | 
				
			||||||
 | 
					        .reduce<[[string, number][], number]>(
 | 
				
			||||||
 | 
					            ([toks, line], tok) =>
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    [...toks, [tok, line]],
 | 
				
			||||||
 | 
					                    tok === "\n" ? line + 1 : line,
 | 
				
			||||||
 | 
					                ] as const,
 | 
				
			||||||
 | 
					            [[], 1],
 | 
				
			||||||
 | 
					        )[0]
 | 
				
			||||||
 | 
					        .filter(([val, _line]) => val !== "\n")
 | 
				
			||||||
 | 
					        .map(([val, line]): Tok => {
 | 
				
			||||||
 | 
					            if (/^[0-9]+$/.test(val)) {
 | 
				
			||||||
 | 
					                return { type: "int", line, intVal: parseInt(val) };
 | 
				
			||||||
 | 
					            } else if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(val)) {
 | 
				
			||||||
 | 
					                return kws.includes(val)
 | 
				
			||||||
 | 
					                    ? { type: val, line }
 | 
				
			||||||
 | 
					                    : { type: "ident", line, identVal: val };
 | 
				
			||||||
 | 
					            } else if (/^".*?"$/.test(val)) {
 | 
				
			||||||
 | 
					                return {
 | 
				
			||||||
 | 
					                    type: "string",
 | 
				
			||||||
 | 
					                    line,
 | 
				
			||||||
 | 
					                    stringVal: val
 | 
				
			||||||
 | 
					                        .slice(1, val.length - 1)
 | 
				
			||||||
 | 
					                        .replace(/\\SPACE/g, " ")
 | 
				
			||||||
 | 
					                        .replace(/\\n/g, "\n")
 | 
				
			||||||
 | 
					                        .replace(/\\/g, ""),
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                return { type: val, line };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										101
									
								
								backup-compiler/lir.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								backup-compiler/lir.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,101 @@
 | 
				
			|||||||
 | 
					import * as mir from "./mir.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Program = {
 | 
				
			||||||
 | 
					    strings: Map<number, string>;
 | 
				
			||||||
 | 
					    fns: Fn[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Fn = {
 | 
				
			||||||
 | 
					    id: number;
 | 
				
			||||||
 | 
					    label: string;
 | 
				
			||||||
 | 
					    mir: mir.Fn;
 | 
				
			||||||
 | 
					    lines: Line[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Line = {
 | 
				
			||||||
 | 
					    labels: Label[];
 | 
				
			||||||
 | 
					    ins: Ins;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Ins =
 | 
				
			||||||
 | 
					    | { tag: "error" }
 | 
				
			||||||
 | 
					    | { tag: "nop" }
 | 
				
			||||||
 | 
					    | { tag: "mov_int"; reg: Reg; val: number }
 | 
				
			||||||
 | 
					    | { tag: "mov_string"; reg: Reg; stringId: number }
 | 
				
			||||||
 | 
					    | { tag: "mov_fn"; reg: Reg; fn: Fn }
 | 
				
			||||||
 | 
					    | { tag: "push"; reg: Reg }
 | 
				
			||||||
 | 
					    | { tag: "pop"; reg: Reg }
 | 
				
			||||||
 | 
					    | { tag: "load"; reg: Reg; offset: number }
 | 
				
			||||||
 | 
					    | { tag: "store"; offset: number; reg: Reg }
 | 
				
			||||||
 | 
					    | { tag: "call_reg"; reg: Reg }
 | 
				
			||||||
 | 
					    | { tag: "call_fn"; fn: Fn }
 | 
				
			||||||
 | 
					    | { tag: "jmp"; target: Label }
 | 
				
			||||||
 | 
					    | { tag: "jnz_reg"; reg: Reg; target: Label }
 | 
				
			||||||
 | 
					    | { tag: "ret" }
 | 
				
			||||||
 | 
					    | { tag: "lt" | "eq" | "add" | "mul"; dst: Reg; src: Reg };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Reg = number;
 | 
				
			||||||
 | 
					export type Label = number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ProgramStringifyer {
 | 
				
			||||||
 | 
					    public constructor(
 | 
				
			||||||
 | 
					        private program: Program,
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public stringify(): string {
 | 
				
			||||||
 | 
					        return this.program.fns
 | 
				
			||||||
 | 
					            .map((fn) =>
 | 
				
			||||||
 | 
					                `${fn.label}:\n${
 | 
				
			||||||
 | 
					                    fn.lines
 | 
				
			||||||
 | 
					                        .map((label) =>
 | 
				
			||||||
 | 
					                            `${
 | 
				
			||||||
 | 
					                                label.labels
 | 
				
			||||||
 | 
					                                    .map((label) => `.${label}:\n`)
 | 
				
			||||||
 | 
					                                    .join()
 | 
				
			||||||
 | 
					                            }    ${this.ins(label.ins)}\n`
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        .join("")
 | 
				
			||||||
 | 
					                }`
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .join("");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ins(ins: Ins): string {
 | 
				
			||||||
 | 
					        switch (ins.tag) {
 | 
				
			||||||
 | 
					            case "error":
 | 
				
			||||||
 | 
					                return "<error>";
 | 
				
			||||||
 | 
					            case "nop":
 | 
				
			||||||
 | 
					                return "nop";
 | 
				
			||||||
 | 
					            case "mov_int":
 | 
				
			||||||
 | 
					                return `mov_int %${ins.reg}, ${ins.val}`;
 | 
				
			||||||
 | 
					            case "mov_string":
 | 
				
			||||||
 | 
					                return `mov_string %${ins.reg}, string+${ins.stringId}`;
 | 
				
			||||||
 | 
					            case "mov_fn":
 | 
				
			||||||
 | 
					                return `mov_fn %${ins.reg}, ${ins.fn.label}`;
 | 
				
			||||||
 | 
					            case "push":
 | 
				
			||||||
 | 
					                return `push %${ins.reg}`;
 | 
				
			||||||
 | 
					            case "pop":
 | 
				
			||||||
 | 
					                return `pop %${ins.reg}`;
 | 
				
			||||||
 | 
					            case "load":
 | 
				
			||||||
 | 
					                return `load %${ins.reg}, ${ins.offset}`;
 | 
				
			||||||
 | 
					            case "store":
 | 
				
			||||||
 | 
					                return `store ${ins.offset}, %${ins.reg}`;
 | 
				
			||||||
 | 
					            case "call_reg":
 | 
				
			||||||
 | 
					                return `call_reg %${ins.reg}`;
 | 
				
			||||||
 | 
					            case "call_fn":
 | 
				
			||||||
 | 
					                return `call_fn ${ins.fn.label}`;
 | 
				
			||||||
 | 
					            case "jmp":
 | 
				
			||||||
 | 
					                return `jmp .b${ins.target}`;
 | 
				
			||||||
 | 
					            case "jnz_reg":
 | 
				
			||||||
 | 
					                return `jmp %${ins.reg}, .b${ins.target}`;
 | 
				
			||||||
 | 
					            case "ret":
 | 
				
			||||||
 | 
					                return "ret";
 | 
				
			||||||
 | 
					            case "lt":
 | 
				
			||||||
 | 
					            case "eq":
 | 
				
			||||||
 | 
					            case "add":
 | 
				
			||||||
 | 
					            case "mul":
 | 
				
			||||||
 | 
					                return `${ins.tag} %${ins.dst}, %${ins.src}`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const _: never = ins;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										259
									
								
								backup-compiler/lir_gen.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										259
									
								
								backup-compiler/lir_gen.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,259 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					    Fn,
 | 
				
			||||||
 | 
					    Ins,
 | 
				
			||||||
 | 
					    Label,
 | 
				
			||||||
 | 
					    Line,
 | 
				
			||||||
 | 
					    Program,
 | 
				
			||||||
 | 
					    ProgramStringifyer,
 | 
				
			||||||
 | 
					    Reg,
 | 
				
			||||||
 | 
					} from "./lir.ts";
 | 
				
			||||||
 | 
					import { MirGen } from "./mir_gen.ts";
 | 
				
			||||||
 | 
					import * as ast from "./ast.ts";
 | 
				
			||||||
 | 
					import * as mir from "./mir.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class LirGen {
 | 
				
			||||||
 | 
					    private strings = new StringIntern();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fnIds = 0;
 | 
				
			||||||
 | 
					    private fns = new Map<number, Fn>();
 | 
				
			||||||
 | 
					    private stmtFns = new Map<number, Fn>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public constructor(
 | 
				
			||||||
 | 
					        private ast: ast.Stmt[],
 | 
				
			||||||
 | 
					        private mirGen: MirGen,
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public generate(): Program {
 | 
				
			||||||
 | 
					        for (const stmt of this.ast) {
 | 
				
			||||||
 | 
					            if (stmt.kind.tag !== "fn") {
 | 
				
			||||||
 | 
					                throw new Error("only functions can compile top level");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const mir = this.mirGen.fnMir(stmt, stmt.kind);
 | 
				
			||||||
 | 
					            const id = this.fnIds++;
 | 
				
			||||||
 | 
					            const label = `sbc__${stmt.kind.ident}`;
 | 
				
			||||||
 | 
					            const fn: Fn = { id, label, mir, lines: [] };
 | 
				
			||||||
 | 
					            this.fns.set(id, fn);
 | 
				
			||||||
 | 
					            this.stmtFns.set(stmt.id, fn);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const id of this.fns.keys()) {
 | 
				
			||||||
 | 
					            const fn = this.fns.get(id)!;
 | 
				
			||||||
 | 
					            const stmtKind = fn.mir.stmt.kind;
 | 
				
			||||||
 | 
					            if (stmtKind.tag !== "fn") {
 | 
				
			||||||
 | 
					                throw new Error();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // if (stmtKind.attrs.at(0)?.ident === "c_function") {
 | 
				
			||||||
 | 
					            //     const arg = stmtKind.attrs.at(0)!.args.at(0);
 | 
				
			||||||
 | 
					            //     if (!arg || arg.kind.tag !== "string") {
 | 
				
			||||||
 | 
					            //         throw new Error("incorrect args for attribute");
 | 
				
			||||||
 | 
					            //     }
 | 
				
			||||||
 | 
					            //     const label = arg.kind.val;
 | 
				
			||||||
 | 
					            //     new CFunctionGen(fn, label).generate();
 | 
				
			||||||
 | 
					            //     continue;
 | 
				
			||||||
 | 
					            // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            new FnGen(
 | 
				
			||||||
 | 
					                fn,
 | 
				
			||||||
 | 
					                this.strings,
 | 
				
			||||||
 | 
					                this.stmtFns,
 | 
				
			||||||
 | 
					            ).generate();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            fns: this.fns.values().toArray(),
 | 
				
			||||||
 | 
					            strings: this.strings.done(),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FnGen {
 | 
				
			||||||
 | 
					    private regIds = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private labelIds = 0;
 | 
				
			||||||
 | 
					    private blockLabels = new Map<number, Label>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private currentLabels: Label[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private nextOffset = -8;
 | 
				
			||||||
 | 
					    private localOffsets = new Map<number, number>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public constructor(
 | 
				
			||||||
 | 
					        private fn: Fn,
 | 
				
			||||||
 | 
					        private strings: StringIntern,
 | 
				
			||||||
 | 
					        private stmtFns: Map<number, Fn>,
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public generate() {
 | 
				
			||||||
 | 
					        for (const block of this.fn.mir.blocks) {
 | 
				
			||||||
 | 
					            const label = this.labelIds++;
 | 
				
			||||||
 | 
					            this.blockLabels.set(block.id, label);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (const local of this.fn.mir.locals) {
 | 
				
			||||||
 | 
					            this.localOffsets.set(local.id, this.nextOffset);
 | 
				
			||||||
 | 
					            this.nextOffset -= 8;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (const block of this.fn.mir.blocks) {
 | 
				
			||||||
 | 
					            this.currentLabels.push(this.blockLabels.get(block.id)!);
 | 
				
			||||||
 | 
					            for (const stmt of block.stmts) {
 | 
				
			||||||
 | 
					                this.lowerStmt(stmt);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            this.lowerTer(block.ter);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (this.currentLabels.length > 0) {
 | 
				
			||||||
 | 
					            this.pushIns({ tag: "nop" });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerStmt(stmt: mir.Stmt) {
 | 
				
			||||||
 | 
					        const k = stmt.kind;
 | 
				
			||||||
 | 
					        switch (k.tag) {
 | 
				
			||||||
 | 
					            case "error":
 | 
				
			||||||
 | 
					                this.pushIns({ tag: "error" });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            case "push": {
 | 
				
			||||||
 | 
					                switch (k.val.tag) {
 | 
				
			||||||
 | 
					                    case "string": {
 | 
				
			||||||
 | 
					                        const reg = this.reg();
 | 
				
			||||||
 | 
					                        const stringId = this.strings.intern(k.val.val);
 | 
				
			||||||
 | 
					                        this.pushIns({ tag: "mov_string", reg, stringId });
 | 
				
			||||||
 | 
					                        this.pushIns({ tag: "push", reg });
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    case "int": {
 | 
				
			||||||
 | 
					                        const reg = this.reg();
 | 
				
			||||||
 | 
					                        this.pushIns({ tag: "mov_int", reg, val: k.val.val });
 | 
				
			||||||
 | 
					                        this.pushIns({ tag: "push", reg });
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    case "fn": {
 | 
				
			||||||
 | 
					                        const reg = this.reg();
 | 
				
			||||||
 | 
					                        this.pushIns({
 | 
				
			||||||
 | 
					                            tag: "mov_fn",
 | 
				
			||||||
 | 
					                            reg,
 | 
				
			||||||
 | 
					                            fn: this.stmtFns.get(k.val.stmt.id)!,
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                        this.pushIns({ tag: "push", reg });
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                const __: never = k.val;
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "pop": {
 | 
				
			||||||
 | 
					                const reg = this.reg();
 | 
				
			||||||
 | 
					                this.pushIns({ tag: "pop", reg });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "load": {
 | 
				
			||||||
 | 
					                const reg = this.reg();
 | 
				
			||||||
 | 
					                const offset = this.localOffsets.get(k.local.id)!;
 | 
				
			||||||
 | 
					                this.pushIns({ tag: "load", reg, offset });
 | 
				
			||||||
 | 
					                this.pushIns({ tag: "push", reg });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "store": {
 | 
				
			||||||
 | 
					                const reg = this.reg();
 | 
				
			||||||
 | 
					                const offset = this.localOffsets.get(k.local.id)!;
 | 
				
			||||||
 | 
					                this.pushIns({ tag: "pop", reg });
 | 
				
			||||||
 | 
					                this.pushIns({ tag: "store", offset, reg });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "call": {
 | 
				
			||||||
 | 
					                const reg = this.reg();
 | 
				
			||||||
 | 
					                this.pushIns({ tag: "pop", reg });
 | 
				
			||||||
 | 
					                this.pushIns({ tag: "call_reg", reg });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "lt":
 | 
				
			||||||
 | 
					            case "eq":
 | 
				
			||||||
 | 
					            case "add":
 | 
				
			||||||
 | 
					            case "mul": {
 | 
				
			||||||
 | 
					                const dst = this.reg();
 | 
				
			||||||
 | 
					                const src = this.reg();
 | 
				
			||||||
 | 
					                this.pushIns({ tag: "pop", reg: src });
 | 
				
			||||||
 | 
					                this.pushIns({ tag: "pop", reg: dst });
 | 
				
			||||||
 | 
					                this.pushIns({ tag: k.tag, dst, src });
 | 
				
			||||||
 | 
					                this.pushIns({ tag: "push", reg: dst });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const _: never = k;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerTer(ter: mir.Ter) {
 | 
				
			||||||
 | 
					        const k = ter.kind;
 | 
				
			||||||
 | 
					        switch (k.tag) {
 | 
				
			||||||
 | 
					            case "error":
 | 
				
			||||||
 | 
					                this.pushIns({ tag: "error" });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            case "unset":
 | 
				
			||||||
 | 
					                this.pushIns({ tag: "error" });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            case "return":
 | 
				
			||||||
 | 
					                this.pushIns({ tag: "ret" });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            case "goto":
 | 
				
			||||||
 | 
					                this.pushIns({
 | 
				
			||||||
 | 
					                    tag: "jmp",
 | 
				
			||||||
 | 
					                    target: this.blockLabels.get(k.target.id)!,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            case "if": {
 | 
				
			||||||
 | 
					                const reg = this.reg();
 | 
				
			||||||
 | 
					                this.pushIns({ tag: "pop", reg });
 | 
				
			||||||
 | 
					                this.pushIns({
 | 
				
			||||||
 | 
					                    tag: "jnz_reg",
 | 
				
			||||||
 | 
					                    reg,
 | 
				
			||||||
 | 
					                    target: this.blockLabels.get(k.falsy.id)!,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                this.pushIns({
 | 
				
			||||||
 | 
					                    tag: "jmp",
 | 
				
			||||||
 | 
					                    target: this.blockLabels.get(k.falsy.id)!,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const _: never = k;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private pushIns(ins: Ins) {
 | 
				
			||||||
 | 
					        this.fn.lines.push({ labels: this.currentLabels, ins });
 | 
				
			||||||
 | 
					        this.currentLabels = [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private reg(): Reg {
 | 
				
			||||||
 | 
					        const reg = this.regIds++;
 | 
				
			||||||
 | 
					        return reg;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CFunctionGen {
 | 
				
			||||||
 | 
					    public constructor(
 | 
				
			||||||
 | 
					        private fn: Fn,
 | 
				
			||||||
 | 
					        private label: string,
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public generate() {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StringIntern {
 | 
				
			||||||
 | 
					    private ids = 0;
 | 
				
			||||||
 | 
					    private strings = new Map<number, string>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public intern(value: string): number {
 | 
				
			||||||
 | 
					        const entry = this.strings
 | 
				
			||||||
 | 
					            .entries()
 | 
				
			||||||
 | 
					            .find(([_id, v]) => v === value);
 | 
				
			||||||
 | 
					        if (entry) {
 | 
				
			||||||
 | 
					            return entry[0];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const id = this.ids++;
 | 
				
			||||||
 | 
					        this.strings.set(id, value);
 | 
				
			||||||
 | 
					        return id;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public done(): Map<number, string> {
 | 
				
			||||||
 | 
					        return this.strings;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										34
									
								
								backup-compiler/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								backup-compiler/main.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					import * as yaml from "jsr:@std/yaml";
 | 
				
			||||||
 | 
					import { Checker, Parser, Resolver } from "./front.ts";
 | 
				
			||||||
 | 
					import { MirGen } from "./mir_gen.ts";
 | 
				
			||||||
 | 
					import { FnStringifyer } from "./mir.ts";
 | 
				
			||||||
 | 
					import { LirGen } from "./lir_gen.ts";
 | 
				
			||||||
 | 
					import { ProgramStringifyer } from "./lir.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function main() {
 | 
				
			||||||
 | 
					    const text = await Deno.readTextFile(Deno.args[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const ast = new Parser(text).parse();
 | 
				
			||||||
 | 
					    console.log("=== AST ===");
 | 
				
			||||||
 | 
					    console.log(yaml.stringify(ast));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const re = new Resolver(ast).resolve();
 | 
				
			||||||
 | 
					    const ch = new Checker(re);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const mirGen = new MirGen(re, ch);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log("=== MIR ===");
 | 
				
			||||||
 | 
					    for (const stmt of ast) {
 | 
				
			||||||
 | 
					        if (stmt.kind.tag !== "fn") {
 | 
				
			||||||
 | 
					            throw new Error("only functions can compile top level");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const fnMir = mirGen.fnMir(stmt, stmt.kind);
 | 
				
			||||||
 | 
					        console.log(new FnStringifyer(fnMir).stringify());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const lir = new LirGen(ast, mirGen).generate();
 | 
				
			||||||
 | 
					    console.log("=== LIR ===");
 | 
				
			||||||
 | 
					    console.log(new ProgramStringifyer(lir).stringify());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					main();
 | 
				
			||||||
							
								
								
									
										143
									
								
								backup-compiler/mir.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								backup-compiler/mir.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,143 @@
 | 
				
			|||||||
 | 
					import { Ty, tyToString } from "./ty.ts";
 | 
				
			||||||
 | 
					import * as ast from "./ast.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Fn = {
 | 
				
			||||||
 | 
					    stmt: ast.Stmt;
 | 
				
			||||||
 | 
					    locals: Local[];
 | 
				
			||||||
 | 
					    paramLocals: Map<number, Local>;
 | 
				
			||||||
 | 
					    returnLocal: Local;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    blocks: Block[];
 | 
				
			||||||
 | 
					    entry: Block;
 | 
				
			||||||
 | 
					    exit: Block;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Block = {
 | 
				
			||||||
 | 
					    id: number;
 | 
				
			||||||
 | 
					    stmts: Stmt[];
 | 
				
			||||||
 | 
					    ter: Ter;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Local = {
 | 
				
			||||||
 | 
					    id: number;
 | 
				
			||||||
 | 
					    ty: Ty;
 | 
				
			||||||
 | 
					    ident?: string;
 | 
				
			||||||
 | 
					    stmt?: ast.Stmt;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Stmt = {
 | 
				
			||||||
 | 
					    kind: StmtKind;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Stmt = (kind: StmtKind): Stmt => ({ kind });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type StmtKind =
 | 
				
			||||||
 | 
					    | { tag: "error" }
 | 
				
			||||||
 | 
					    | { tag: "push"; val: Val; ty: Ty }
 | 
				
			||||||
 | 
					    | { tag: "pop" }
 | 
				
			||||||
 | 
					    | { tag: "load"; local: Local }
 | 
				
			||||||
 | 
					    | { tag: "store"; local: Local }
 | 
				
			||||||
 | 
					    | { tag: "call"; args: number }
 | 
				
			||||||
 | 
					    | { tag: "lt" | "eq" | "add" | "mul" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Ter = {
 | 
				
			||||||
 | 
					    kind: TerKind;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export const Ter = (kind: TerKind): Ter => ({ kind });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type TerKind =
 | 
				
			||||||
 | 
					    | { tag: "error" }
 | 
				
			||||||
 | 
					    | { tag: "unset" }
 | 
				
			||||||
 | 
					    | { tag: "return" }
 | 
				
			||||||
 | 
					    | { tag: "goto"; target: Block }
 | 
				
			||||||
 | 
					    | {
 | 
				
			||||||
 | 
					        tag: "if";
 | 
				
			||||||
 | 
					        truthy: Block;
 | 
				
			||||||
 | 
					        falsy: Block;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Val =
 | 
				
			||||||
 | 
					    | { tag: "int"; val: number }
 | 
				
			||||||
 | 
					    | { tag: "string"; val: string }
 | 
				
			||||||
 | 
					    | { tag: "fn"; stmt: ast.Stmt };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class FnStringifyer {
 | 
				
			||||||
 | 
					    public constructor(
 | 
				
			||||||
 | 
					        private fn: Fn,
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public stringify(): string {
 | 
				
			||||||
 | 
					        const kind = this.fn.stmt.kind;
 | 
				
			||||||
 | 
					        if (kind.tag !== "fn") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return `${kind.ident}:\n${
 | 
				
			||||||
 | 
					            this.fn.locals
 | 
				
			||||||
 | 
					                .map((local) => `    %${local.id}: ${tyToString(local.ty)}\n`)
 | 
				
			||||||
 | 
					                .join("")
 | 
				
			||||||
 | 
					        }${
 | 
				
			||||||
 | 
					            this.fn.blocks
 | 
				
			||||||
 | 
					                .map((block) =>
 | 
				
			||||||
 | 
					                    `    .b${block.id}:\n${
 | 
				
			||||||
 | 
					                        block.stmts
 | 
				
			||||||
 | 
					                            .map((stmt) => `        ${this.stmt(stmt)}\n`)
 | 
				
			||||||
 | 
					                            .join("")
 | 
				
			||||||
 | 
					                    }        ${this.ter(block.ter)}\n`
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .join("")
 | 
				
			||||||
 | 
					        }`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private stmt(stmt: Stmt): string {
 | 
				
			||||||
 | 
					        const k = stmt.kind;
 | 
				
			||||||
 | 
					        switch (k.tag) {
 | 
				
			||||||
 | 
					            case "error":
 | 
				
			||||||
 | 
					                return "<error>";
 | 
				
			||||||
 | 
					            case "push":
 | 
				
			||||||
 | 
					                return `push (${tyToString(k.ty)}) ${this.val(k.val)}`;
 | 
				
			||||||
 | 
					            case "pop":
 | 
				
			||||||
 | 
					                return "pop";
 | 
				
			||||||
 | 
					            case "load":
 | 
				
			||||||
 | 
					                return `load %${k.local.id}`;
 | 
				
			||||||
 | 
					            case "store":
 | 
				
			||||||
 | 
					                return `store %${k.local.id}`;
 | 
				
			||||||
 | 
					            case "call":
 | 
				
			||||||
 | 
					                return `call ${k.args}`;
 | 
				
			||||||
 | 
					            case "lt":
 | 
				
			||||||
 | 
					            case "eq":
 | 
				
			||||||
 | 
					            case "add":
 | 
				
			||||||
 | 
					            case "mul":
 | 
				
			||||||
 | 
					                return k.tag;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ter(ter: Ter): string {
 | 
				
			||||||
 | 
					        const k = ter.kind;
 | 
				
			||||||
 | 
					        switch (k.tag) {
 | 
				
			||||||
 | 
					            case "error":
 | 
				
			||||||
 | 
					                return "<error>";
 | 
				
			||||||
 | 
					            case "unset":
 | 
				
			||||||
 | 
					                return "<unset>";
 | 
				
			||||||
 | 
					            case "return":
 | 
				
			||||||
 | 
					                return "return";
 | 
				
			||||||
 | 
					            case "goto":
 | 
				
			||||||
 | 
					                return `goto .b${k.target.id}`;
 | 
				
			||||||
 | 
					            case "if":
 | 
				
			||||||
 | 
					                return `goto .b${k.truthy.id}, .b${k.falsy.id}`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val(val: Val): string {
 | 
				
			||||||
 | 
					        switch (val.tag) {
 | 
				
			||||||
 | 
					            case "string":
 | 
				
			||||||
 | 
					                return JSON.stringify(val.val);
 | 
				
			||||||
 | 
					            case "int":
 | 
				
			||||||
 | 
					                return `${val.val}`;
 | 
				
			||||||
 | 
					            case "fn":
 | 
				
			||||||
 | 
					                if (val.stmt.kind.tag !== "fn") {
 | 
				
			||||||
 | 
					                    throw new Error();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return val.stmt.kind.ident;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										293
									
								
								backup-compiler/mir_gen.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										293
									
								
								backup-compiler/mir_gen.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,293 @@
 | 
				
			|||||||
 | 
					import { Checker, Resols } from "./front.ts";
 | 
				
			||||||
 | 
					import * as ast from "./ast.ts";
 | 
				
			||||||
 | 
					import { Block, Fn, Local, Stmt, StmtKind, Ter, TerKind, Val } from "./mir.ts";
 | 
				
			||||||
 | 
					import { Ty } from "./ty.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class MirGen {
 | 
				
			||||||
 | 
					    public constructor(
 | 
				
			||||||
 | 
					        private re: Resols,
 | 
				
			||||||
 | 
					        private ch: Checker,
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public fnMir(stmt: ast.Stmt, stmtKind: ast.FnStmt): Fn {
 | 
				
			||||||
 | 
					        return new FnMirGen(this.re, this.ch, stmt, stmtKind).generate();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class FnMirGen {
 | 
				
			||||||
 | 
					    private localIds = 0;
 | 
				
			||||||
 | 
					    private locals: Local[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private paramLocals = new Map<number, Local>();
 | 
				
			||||||
 | 
					    private returnLocal!: Local;
 | 
				
			||||||
 | 
					    private letLocals = new Map<number, Local>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private blockIds = 0;
 | 
				
			||||||
 | 
					    private blocks: Block[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private returnBlock!: Block;
 | 
				
			||||||
 | 
					    private currentBlock!: Block;
 | 
				
			||||||
 | 
					    private loopExitBlocks = new Map<number, Block>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public constructor(
 | 
				
			||||||
 | 
					        private re: Resols,
 | 
				
			||||||
 | 
					        private ch: Checker,
 | 
				
			||||||
 | 
					        private stmt: ast.Stmt,
 | 
				
			||||||
 | 
					        private stmtKind: ast.FnStmt,
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public generate(): Fn {
 | 
				
			||||||
 | 
					        const fnTy = this.ch.fnStmtTy(this.stmt);
 | 
				
			||||||
 | 
					        if (fnTy.tag !== "fn") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.returnLocal = this.local(fnTy.returnTy);
 | 
				
			||||||
 | 
					        for (const [i, param] of this.stmtKind.params.entries()) {
 | 
				
			||||||
 | 
					            const ty = this.ch.paramTy(this.stmt, i);
 | 
				
			||||||
 | 
					            const local = this.local(ty, param);
 | 
				
			||||||
 | 
					            this.paramLocals.set(i, local);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const entry = this.block();
 | 
				
			||||||
 | 
					        const exit = this.block();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.currentBlock = entry;
 | 
				
			||||||
 | 
					        this.lowerBlock(this.stmtKind.body);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        entry.ter = Ter({ tag: "goto", target: exit });
 | 
				
			||||||
 | 
					        exit.ter = Ter({ tag: "return" });
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            stmt: this.stmt,
 | 
				
			||||||
 | 
					            locals: this.locals,
 | 
				
			||||||
 | 
					            paramLocals: this.paramLocals,
 | 
				
			||||||
 | 
					            returnLocal: this.returnLocal,
 | 
				
			||||||
 | 
					            blocks: this.blocks,
 | 
				
			||||||
 | 
					            entry: entry,
 | 
				
			||||||
 | 
					            exit: this.returnBlock,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerBlock(block: ast.Block) {
 | 
				
			||||||
 | 
					        for (const stmt of block.stmts) {
 | 
				
			||||||
 | 
					            this.lowerStmt(stmt);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerStmt(stmt: ast.Stmt) {
 | 
				
			||||||
 | 
					        const k = stmt.kind;
 | 
				
			||||||
 | 
					        switch (k.tag) {
 | 
				
			||||||
 | 
					            case "error":
 | 
				
			||||||
 | 
					                throw new Error();
 | 
				
			||||||
 | 
					            case "fn":
 | 
				
			||||||
 | 
					                throw new Error("cannot lower");
 | 
				
			||||||
 | 
					            case "let": {
 | 
				
			||||||
 | 
					                const ty = this.ch.letStmtTy(stmt);
 | 
				
			||||||
 | 
					                const local = this.local(ty);
 | 
				
			||||||
 | 
					                this.letLocals.set(stmt.id, local);
 | 
				
			||||||
 | 
					                if (k.expr) {
 | 
				
			||||||
 | 
					                    this.lowerExpr(k.expr);
 | 
				
			||||||
 | 
					                    this.pushStmt({ tag: "store", local });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "loop": {
 | 
				
			||||||
 | 
					                const entry = this.currentBlock;
 | 
				
			||||||
 | 
					                const exit = this.block();
 | 
				
			||||||
 | 
					                const loop = this.block();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                this.loopExitBlocks.set(stmt.id, exit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                this.currentBlock = loop;
 | 
				
			||||||
 | 
					                this.lowerBlock(k.body);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                entry.ter = Ter({ tag: "goto", target: loop });
 | 
				
			||||||
 | 
					                loop.ter = Ter({ tag: "goto", target: exit });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                this.currentBlock = exit;
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "if": {
 | 
				
			||||||
 | 
					                this.lowerExpr(k.expr);
 | 
				
			||||||
 | 
					                const entry = this.currentBlock;
 | 
				
			||||||
 | 
					                const exit = this.block();
 | 
				
			||||||
 | 
					                const truthy = this.block();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                this.currentBlock = truthy;
 | 
				
			||||||
 | 
					                this.lowerBlock(k.truthy);
 | 
				
			||||||
 | 
					                truthy.ter = Ter({ tag: "goto", target: exit });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let falsy = exit;
 | 
				
			||||||
 | 
					                if (k.falsy) {
 | 
				
			||||||
 | 
					                    falsy = this.block();
 | 
				
			||||||
 | 
					                    this.currentBlock = falsy;
 | 
				
			||||||
 | 
					                    this.lowerBlock(k.falsy);
 | 
				
			||||||
 | 
					                    falsy.ter = Ter({ tag: "goto", target: exit });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                entry.ter = Ter({ tag: "if", truthy, falsy });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "return": {
 | 
				
			||||||
 | 
					                if (k.expr) {
 | 
				
			||||||
 | 
					                    this.lowerExpr(k.expr);
 | 
				
			||||||
 | 
					                    this.pushStmt({
 | 
				
			||||||
 | 
					                        tag: "store",
 | 
				
			||||||
 | 
					                        local: this.returnLocal,
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                this.currentBlock.ter = Ter({
 | 
				
			||||||
 | 
					                    tag: "goto",
 | 
				
			||||||
 | 
					                    target: this.returnBlock,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                this.currentBlock = this.block();
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "break": {
 | 
				
			||||||
 | 
					                const re = this.re.stmt(stmt)!;
 | 
				
			||||||
 | 
					                const target = this.loopExitBlocks.get(re!.stmt.id)!;
 | 
				
			||||||
 | 
					                this.currentBlock.ter = Ter({ tag: "goto", target });
 | 
				
			||||||
 | 
					                this.currentBlock = this.block();
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "assign": {
 | 
				
			||||||
 | 
					                const re = this.re.expr(k.subject)!;
 | 
				
			||||||
 | 
					                let local: Local;
 | 
				
			||||||
 | 
					                switch (re.tag) {
 | 
				
			||||||
 | 
					                    case "fn":
 | 
				
			||||||
 | 
					                        throw new Error("cannot assign to expression");
 | 
				
			||||||
 | 
					                    case "let":
 | 
				
			||||||
 | 
					                        local = this.letLocals.get(re.stmt.id)!;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    case "loop":
 | 
				
			||||||
 | 
					                        throw new Error("cannot assign to expression");
 | 
				
			||||||
 | 
					                    case "param":
 | 
				
			||||||
 | 
					                        local = this.paramLocals.get(re.i)!;
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                this.lowerExpr(k.expr);
 | 
				
			||||||
 | 
					                this.pushStmt({ tag: "store", local });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "expr": {
 | 
				
			||||||
 | 
					                const expr = this.lowerExpr(k.expr);
 | 
				
			||||||
 | 
					                void expr;
 | 
				
			||||||
 | 
					                this.pushStmt({ tag: "pop" });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const _: never = k;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerExpr(expr: ast.Expr) {
 | 
				
			||||||
 | 
					        const k = expr.kind;
 | 
				
			||||||
 | 
					        switch (k.tag) {
 | 
				
			||||||
 | 
					            case "error":
 | 
				
			||||||
 | 
					                throw new Error();
 | 
				
			||||||
 | 
					            case "ident": {
 | 
				
			||||||
 | 
					                const re = this.re.expr(expr);
 | 
				
			||||||
 | 
					                if (!re) {
 | 
				
			||||||
 | 
					                    throw new Error();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                const ty = this.ch.exprTy(expr);
 | 
				
			||||||
 | 
					                switch (re.tag) {
 | 
				
			||||||
 | 
					                    case "fn": {
 | 
				
			||||||
 | 
					                        this.pushStmt({
 | 
				
			||||||
 | 
					                            tag: "push",
 | 
				
			||||||
 | 
					                            val: { tag: "fn", stmt: re.stmt },
 | 
				
			||||||
 | 
					                            ty,
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    case "param": {
 | 
				
			||||||
 | 
					                        const local = this.paramLocals.get(re.i);
 | 
				
			||||||
 | 
					                        if (!local) {
 | 
				
			||||||
 | 
					                            throw new Error();
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        this.pushStmt({ tag: "load", local });
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    case "let": {
 | 
				
			||||||
 | 
					                        const local = this.letLocals.get(re.stmt.id);
 | 
				
			||||||
 | 
					                        if (!local) {
 | 
				
			||||||
 | 
					                            throw new Error();
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        this.pushStmt({ tag: "load", local });
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    case "loop":
 | 
				
			||||||
 | 
					                        throw new Error();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                const __: never = re;
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "int": {
 | 
				
			||||||
 | 
					                const ty = this.ch.exprTy(expr);
 | 
				
			||||||
 | 
					                this.pushStmt({
 | 
				
			||||||
 | 
					                    tag: "push",
 | 
				
			||||||
 | 
					                    val: { tag: "int", val: k.val },
 | 
				
			||||||
 | 
					                    ty,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "string": {
 | 
				
			||||||
 | 
					                const ty = this.ch.exprTy(expr);
 | 
				
			||||||
 | 
					                this.pushStmt({
 | 
				
			||||||
 | 
					                    tag: "push",
 | 
				
			||||||
 | 
					                    val: { tag: "string", val: k.val },
 | 
				
			||||||
 | 
					                    ty,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "call": {
 | 
				
			||||||
 | 
					                for (const arg of k.args) {
 | 
				
			||||||
 | 
					                    this.lowerExpr(arg);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                this.lowerExpr(k.expr);
 | 
				
			||||||
 | 
					                this.pushStmt({
 | 
				
			||||||
 | 
					                    tag: "call",
 | 
				
			||||||
 | 
					                    args: k.args.length,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "binary": {
 | 
				
			||||||
 | 
					                switch (k.op) {
 | 
				
			||||||
 | 
					                    case "<":
 | 
				
			||||||
 | 
					                        this.pushStmt({ tag: "lt" });
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    case "==":
 | 
				
			||||||
 | 
					                        this.pushStmt({ tag: "eq" });
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    case "+":
 | 
				
			||||||
 | 
					                        this.pushStmt({ tag: "add" });
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                    case "*":
 | 
				
			||||||
 | 
					                        this.pushStmt({ tag: "mul" });
 | 
				
			||||||
 | 
					                        return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                const __: never = k.op;
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const _: never = k;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private local(ty: Ty, ident?: string, stmt?: ast.Stmt): Local {
 | 
				
			||||||
 | 
					        const id = this.localIds++;
 | 
				
			||||||
 | 
					        const local: Local = { id, ty, ident, stmt };
 | 
				
			||||||
 | 
					        this.locals.push(local);
 | 
				
			||||||
 | 
					        return local;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private block(): Block {
 | 
				
			||||||
 | 
					        const id = this.blockIds++;
 | 
				
			||||||
 | 
					        const block: Block = { id, stmts: [], ter: Ter({ tag: "unset" }) };
 | 
				
			||||||
 | 
					        this.blocks.push(block);
 | 
				
			||||||
 | 
					        return block;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private pushStmt(kind: StmtKind) {
 | 
				
			||||||
 | 
					        this.currentBlock.stmts.push(Stmt(kind));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										15
									
								
								backup-compiler/program.sbl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								backup-compiler/program.sbl
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					#[c_function("print_int")]
 | 
				
			||||||
 | 
					fn print_int(value) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// #[c_function("println")]
 | 
				
			||||||
 | 
					// fn println(value) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
					    // println("hello\ world");
 | 
				
			||||||
 | 
					    let a = 4;
 | 
				
			||||||
 | 
					    print_int(a + 2);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// vim: syntax=rust commentstring=//\ %s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										26
									
								
								backup-compiler/ty.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								backup-compiler/ty.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					import * as ast from "./ast.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Ty =
 | 
				
			||||||
 | 
					    | { tag: "error" }
 | 
				
			||||||
 | 
					    | { tag: "int" }
 | 
				
			||||||
 | 
					    | { tag: "string" }
 | 
				
			||||||
 | 
					    | { tag: "fn"; stmt: ast.Stmt; params: Ty[]; returnTy: Ty };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function tyToString(ty: Ty): string {
 | 
				
			||||||
 | 
					    switch (ty.tag) {
 | 
				
			||||||
 | 
					        case "error":
 | 
				
			||||||
 | 
					            return `<error>`;
 | 
				
			||||||
 | 
					        case "int":
 | 
				
			||||||
 | 
					            return `int`;
 | 
				
			||||||
 | 
					        case "string":
 | 
				
			||||||
 | 
					            return `string`;
 | 
				
			||||||
 | 
					        case "fn": {
 | 
				
			||||||
 | 
					            const k = ty.stmt.kind as ast.StmtKind & { tag: "fn" };
 | 
				
			||||||
 | 
					            const params = ty.params
 | 
				
			||||||
 | 
					                .map((param, i) => `${k.params[i]}: ${tyToString(param)}`)
 | 
				
			||||||
 | 
					                .join(", ");
 | 
				
			||||||
 | 
					            const returnTy = tyToString(ty.returnTy);
 | 
				
			||||||
 | 
					            return `fn ${k.ident}(${params}) -> ${returnTy}`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user