mirror of
				https://git.sfja.dk/Mikkel/slige.git
				synced 2025-11-04 04:58:15 +00:00 
			
		
		
		
	generics work
This commit is contained in:
		
							parent
							
								
									34529d9cbb
								
							
						
					
					
						commit
						87d46b6647
					
				@ -125,6 +125,7 @@ export type ETypeKind =
 | 
				
			|||||||
    | { type: "struct"; fields: Param[] };
 | 
					    | { type: "struct"; fields: Param[] };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type GenericParam = {
 | 
					export type GenericParam = {
 | 
				
			||||||
 | 
					    id: number;
 | 
				
			||||||
    ident: string;
 | 
					    ident: string;
 | 
				
			||||||
    pos: Pos;
 | 
					    pos: Pos;
 | 
				
			||||||
    vtype?: VType;
 | 
					    vtype?: VType;
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,8 @@ import { EType, Expr, Stmt } from "./ast.ts";
 | 
				
			|||||||
import { printStackTrace, Reporter } from "./info.ts";
 | 
					import { printStackTrace, Reporter } from "./info.ts";
 | 
				
			||||||
import { Pos } from "./token.ts";
 | 
					import { Pos } from "./token.ts";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					    extractGenericType,
 | 
				
			||||||
 | 
					    GenericArgsMap,
 | 
				
			||||||
    VType,
 | 
					    VType,
 | 
				
			||||||
    VTypeGenericParam,
 | 
					    VTypeGenericParam,
 | 
				
			||||||
    VTypeParam,
 | 
					    VTypeParam,
 | 
				
			||||||
@ -34,7 +36,8 @@ export class Checker {
 | 
				
			|||||||
            if (stmt.kind.genericParams !== undefined) {
 | 
					            if (stmt.kind.genericParams !== undefined) {
 | 
				
			||||||
                genericParams = [];
 | 
					                genericParams = [];
 | 
				
			||||||
                for (const etypeParam of stmt.kind.genericParams) {
 | 
					                for (const etypeParam of stmt.kind.genericParams) {
 | 
				
			||||||
                    genericParams.push({ ident: etypeParam.ident });
 | 
					                    const id = genericParams.length;
 | 
				
			||||||
 | 
					                    genericParams.push({ id, ident: etypeParam.ident });
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            const params: VTypeParam[] = [];
 | 
					            const params: VTypeParam[] = [];
 | 
				
			||||||
@ -47,7 +50,13 @@ export class Checker {
 | 
				
			|||||||
                param.vtype = vtype;
 | 
					                param.vtype = vtype;
 | 
				
			||||||
                params.push({ ident: param.ident, vtype });
 | 
					                params.push({ ident: param.ident, vtype });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            stmt.kind.vtype = { type: "fn", genericParams, params, returnType };
 | 
					            stmt.kind.vtype = {
 | 
				
			||||||
 | 
					                type: "fn",
 | 
				
			||||||
 | 
					                genericParams,
 | 
				
			||||||
 | 
					                params,
 | 
				
			||||||
 | 
					                returnType,
 | 
				
			||||||
 | 
					                stmtId: stmt.id,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -336,8 +345,7 @@ export class Checker {
 | 
				
			|||||||
                if (vtype.type !== "fn") {
 | 
					                if (vtype.type !== "fn") {
 | 
				
			||||||
                    throw new Error();
 | 
					                    throw new Error();
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                const { params, returnType } = vtype;
 | 
					                return vtype;
 | 
				
			||||||
                return { type: "fn", params, returnType };
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            case "fn_param":
 | 
					            case "fn_param":
 | 
				
			||||||
                return expr.kind.sym.param.vtype!;
 | 
					                return expr.kind.sym.param.vtype!;
 | 
				
			||||||
@ -399,30 +407,75 @@ export class Checker {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        const pos = expr.pos;
 | 
					        const pos = expr.pos;
 | 
				
			||||||
        const subject = this.checkExpr(expr.kind.subject);
 | 
					        const subject = this.checkExpr(expr.kind.subject);
 | 
				
			||||||
        if (subject.type !== "fn") {
 | 
					        if (subject.type === "error") return subject;
 | 
				
			||||||
            this.report("cannot call non-fn", pos);
 | 
					        if (subject.type === "fn") {
 | 
				
			||||||
            return { type: "error" };
 | 
					            if (subject.genericParams !== undefined) {
 | 
				
			||||||
        }
 | 
					                throw new Error("😭😭😭");
 | 
				
			||||||
        const args = expr.kind.args.map((arg) => this.checkExpr(arg));
 | 
					            }
 | 
				
			||||||
        if (args.length !== subject.params.length) {
 | 
					            const args = expr.kind.args.map((arg) => this.checkExpr(arg));
 | 
				
			||||||
            this.report(
 | 
					            if (args.length !== subject.params.length) {
 | 
				
			||||||
                `incorrect number of arguments` +
 | 
					 | 
				
			||||||
                    `, expected ${subject.params.length}`,
 | 
					 | 
				
			||||||
                pos,
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        for (let i = 0; i < args.length; ++i) {
 | 
					 | 
				
			||||||
            if (!vtypesEqual(args[i], subject.params[i].vtype)) {
 | 
					 | 
				
			||||||
                this.report(
 | 
					                this.report(
 | 
				
			||||||
                    `incorrect argument ${i} '${subject.params[i].ident}'` +
 | 
					                    `incorrect number of arguments` +
 | 
				
			||||||
                        `, expected ${vtypeToString(subject.params[i].vtype)}` +
 | 
					                        `, expected ${subject.params.length}`,
 | 
				
			||||||
                        `, got ${vtypeToString(args[i])}`,
 | 
					 | 
				
			||||||
                    pos,
 | 
					                    pos,
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            for (let i = 0; i < args.length; ++i) {
 | 
				
			||||||
 | 
					                if (!vtypesEqual(args[i], subject.params[i].vtype)) {
 | 
				
			||||||
 | 
					                    this.report(
 | 
				
			||||||
 | 
					                        `incorrect argument ${i} '${subject.params[i].ident}'` +
 | 
				
			||||||
 | 
					                            `, expected ${
 | 
				
			||||||
 | 
					                                vtypeToString(subject.params[i].vtype)
 | 
				
			||||||
 | 
					                            }` +
 | 
				
			||||||
 | 
					                            `, got ${vtypeToString(args[i])}`,
 | 
				
			||||||
 | 
					                        pos,
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return subject.returnType;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return subject.returnType;
 | 
					        if (subject.type === "generic_spec" && subject.subject.type === "fn") {
 | 
				
			||||||
 | 
					            const inner = subject.subject;
 | 
				
			||||||
 | 
					            const params = inner.params;
 | 
				
			||||||
 | 
					            const args = expr.kind.args.map((arg) => this.checkExpr(arg));
 | 
				
			||||||
 | 
					            if (args.length !== params.length) {
 | 
				
			||||||
 | 
					                this.report(
 | 
				
			||||||
 | 
					                    `incorrect number of arguments` +
 | 
				
			||||||
 | 
					                        `, expected ${params.length}`,
 | 
				
			||||||
 | 
					                    pos,
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            for (let i = 0; i < args.length; ++i) {
 | 
				
			||||||
 | 
					                const vtypeCompatible = vtypesEqual(
 | 
				
			||||||
 | 
					                    args[i],
 | 
				
			||||||
 | 
					                    params[i].vtype,
 | 
				
			||||||
 | 
					                    subject.genericArgs,
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                if (!vtypeCompatible) {
 | 
				
			||||||
 | 
					                    this.report(
 | 
				
			||||||
 | 
					                        `incorrect argument ${i} '${inner.params[i].ident}'` +
 | 
				
			||||||
 | 
					                            `, expected ${
 | 
				
			||||||
 | 
					                                vtypeToString(
 | 
				
			||||||
 | 
					                                    extractGenericType(
 | 
				
			||||||
 | 
					                                        params[i].vtype,
 | 
				
			||||||
 | 
					                                        subject.genericArgs,
 | 
				
			||||||
 | 
					                                    ),
 | 
				
			||||||
 | 
					                                )
 | 
				
			||||||
 | 
					                            }` +
 | 
				
			||||||
 | 
					                            `, got ${vtypeToString(args[i])}`,
 | 
				
			||||||
 | 
					                        pos,
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return extractGenericType(
 | 
				
			||||||
 | 
					                subject.subject.returnType,
 | 
				
			||||||
 | 
					                subject.genericArgs,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.report("cannot call non-fn", pos);
 | 
				
			||||||
 | 
					        return { type: "error" };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public checkPathExpr(expr: Expr): VType {
 | 
					    public checkPathExpr(expr: Expr): VType {
 | 
				
			||||||
@ -445,20 +498,23 @@ export class Checker {
 | 
				
			|||||||
            );
 | 
					            );
 | 
				
			||||||
            return { type: "error" };
 | 
					            return { type: "error" };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const genericParams = expr.kind.etypeArgs.map((arg) =>
 | 
					        const args = expr.kind.etypeArgs;
 | 
				
			||||||
            this.checkEType(arg)
 | 
					        if (args.length !== subject.params.length) {
 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        if (genericParams.length !== subject.params.length) {
 | 
					 | 
				
			||||||
            this.report(
 | 
					            this.report(
 | 
				
			||||||
                `incorrect number of arguments` +
 | 
					                `incorrect number of arguments` +
 | 
				
			||||||
                    `, expected ${subject.params.length}`,
 | 
					                    `, expected ${subject.params.length}`,
 | 
				
			||||||
                pos,
 | 
					                pos,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        const genericArgs: GenericArgsMap = {};
 | 
				
			||||||
 | 
					        for (let i = 0; i < args.length; ++i) {
 | 
				
			||||||
 | 
					            const etype = this.checkEType(args[i]);
 | 
				
			||||||
 | 
					            genericArgs[subject.genericParams[i].id] = etype;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            type: "generic_spec",
 | 
					            type: "generic_spec",
 | 
				
			||||||
            subject,
 | 
					            subject,
 | 
				
			||||||
            genericParams,
 | 
					            genericArgs,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -491,7 +547,9 @@ export class Checker {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        const pos = expr.pos;
 | 
					        const pos = expr.pos;
 | 
				
			||||||
        const left = this.checkExpr(expr.kind.left);
 | 
					        const left = this.checkExpr(expr.kind.left);
 | 
				
			||||||
 | 
					        if (left.type === "error") return left;
 | 
				
			||||||
        const right = this.checkExpr(expr.kind.right);
 | 
					        const right = this.checkExpr(expr.kind.right);
 | 
				
			||||||
 | 
					        if (right.type === "error") return right;
 | 
				
			||||||
        for (const operation of simpleBinaryOperations) {
 | 
					        for (const operation of simpleBinaryOperations) {
 | 
				
			||||||
            if (operation.binaryType !== expr.kind.binaryType) {
 | 
					            if (operation.binaryType !== expr.kind.binaryType) {
 | 
				
			||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
@ -520,10 +578,13 @@ export class Checker {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        const pos = expr.pos;
 | 
					        const pos = expr.pos;
 | 
				
			||||||
        const cond = this.checkExpr(expr.kind.cond);
 | 
					        const cond = this.checkExpr(expr.kind.cond);
 | 
				
			||||||
 | 
					        if (cond.type === "error") return cond;
 | 
				
			||||||
        const truthy = this.checkExpr(expr.kind.truthy);
 | 
					        const truthy = this.checkExpr(expr.kind.truthy);
 | 
				
			||||||
 | 
					        if (truthy.type === "error") return truthy;
 | 
				
			||||||
        const falsy = expr.kind.falsy
 | 
					        const falsy = expr.kind.falsy
 | 
				
			||||||
            ? this.checkExpr(expr.kind.falsy)
 | 
					            ? this.checkExpr(expr.kind.falsy)
 | 
				
			||||||
            : undefined;
 | 
					            : undefined;
 | 
				
			||||||
 | 
					        if (falsy?.type === "error") return falsy;
 | 
				
			||||||
        if (cond.type !== "bool") {
 | 
					        if (cond.type !== "bool") {
 | 
				
			||||||
            this.report(
 | 
					            this.report(
 | 
				
			||||||
                `if condition should be 'bool', got '${vtypeToString(cond)}'`,
 | 
					                `if condition should be 'bool', got '${vtypeToString(cond)}'`,
 | 
				
			||||||
@ -626,7 +687,8 @@ export class Checker {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        if (etype.kind.type === "sym") {
 | 
					        if (etype.kind.type === "sym") {
 | 
				
			||||||
            if (etype.kind.sym.type === "generic") {
 | 
					            if (etype.kind.sym.type === "generic") {
 | 
				
			||||||
                return { type: "generic" };
 | 
					                const { id, ident } = etype.kind.sym.genericParam;
 | 
				
			||||||
 | 
					                return { type: "generic", param: { id, ident } };
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            this.report(`sym type '${etype.kind.sym.type}' used as type`, pos);
 | 
					            this.report(`sym type '${etype.kind.sym.type}' used as type`, pos);
 | 
				
			||||||
            return { type: "error" };
 | 
					            return { type: "error" };
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,8 @@ import { SpecialLoopDesugarer } from "./desugar/special_loop.ts";
 | 
				
			|||||||
import { Reporter } from "./info.ts";
 | 
					import { Reporter } from "./info.ts";
 | 
				
			||||||
import { Lexer } from "./lexer.ts";
 | 
					import { Lexer } from "./lexer.ts";
 | 
				
			||||||
import { FnNamesMap, Lowerer } from "./lowerer.ts";
 | 
					import { FnNamesMap, Lowerer } from "./lowerer.ts";
 | 
				
			||||||
 | 
					import { Monomorphizer } from "./mono.ts";
 | 
				
			||||||
 | 
					import { MonoLowerer } from "./mono_lower.ts";
 | 
				
			||||||
import { Parser } from "./parser.ts";
 | 
					import { Parser } from "./parser.ts";
 | 
				
			||||||
import { Resolver } from "./resolver.ts";
 | 
					import { Resolver } from "./resolver.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -45,10 +47,16 @@ export class Compiler {
 | 
				
			|||||||
            Deno.exit(1);
 | 
					            Deno.exit(1);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const lowerer = new Lowerer(lexer.currentPos());
 | 
					        const { monoFns, callMap } = new Monomorphizer(ast).monomorphize();
 | 
				
			||||||
        lowerer.lower(ast);
 | 
					
 | 
				
			||||||
        // lowerer.printProgram();
 | 
					        //const lowerer = new Lowerer(lexer.currentPos());
 | 
				
			||||||
        const { program, fnNames } = lowerer.finish();
 | 
					        //lowerer.lower(ast);
 | 
				
			||||||
 | 
					        //// lowerer.printProgram();
 | 
				
			||||||
 | 
					        //const { program, fnNames } = lowerer.finish();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const lowerer = new MonoLowerer(monoFns, callMap, lexer.currentPos());
 | 
				
			||||||
 | 
					        const { program, fnNames } = lowerer.lower();
 | 
				
			||||||
 | 
					        lowerer.printProgram();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return { program, fnNames };
 | 
					        return { program, fnNames };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -42,7 +42,7 @@ export function printStackTrace() {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        //throw new ReportNotAnError();
 | 
					        throw new ReportNotAnError();
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
        if (!(error instanceof ReportNotAnError)) {
 | 
					        if (!(error instanceof ReportNotAnError)) {
 | 
				
			||||||
            throw error;
 | 
					            throw error;
 | 
				
			||||||
 | 
				
			|||||||
@ -257,6 +257,8 @@ export class Lowerer {
 | 
				
			|||||||
                return this.lowerIndexExpr(expr);
 | 
					                return this.lowerIndexExpr(expr);
 | 
				
			||||||
            case "call":
 | 
					            case "call":
 | 
				
			||||||
                return this.lowerCallExpr(expr);
 | 
					                return this.lowerCallExpr(expr);
 | 
				
			||||||
 | 
					            case "etype_args":
 | 
				
			||||||
 | 
					                return this.lowerETypeArgsExpr(expr);
 | 
				
			||||||
            case "unary":
 | 
					            case "unary":
 | 
				
			||||||
                return this.lowerUnaryExpr(expr);
 | 
					                return this.lowerUnaryExpr(expr);
 | 
				
			||||||
            case "binary":
 | 
					            case "binary":
 | 
				
			||||||
@ -465,6 +467,13 @@ export class Lowerer {
 | 
				
			|||||||
        this.program.add(Ops.Call, expr.kind.args.length);
 | 
					        this.program.add(Ops.Call, expr.kind.args.length);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerETypeArgsExpr(expr: Expr) {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "etype_args") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw new Error("not implemented");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private lowerIfExpr(expr: Expr) {
 | 
					    private lowerIfExpr(expr: Expr) {
 | 
				
			||||||
        if (expr.kind.type !== "if") {
 | 
					        if (expr.kind.type !== "if") {
 | 
				
			||||||
            throw new Error();
 | 
					            throw new Error();
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										262
									
								
								compiler/mono.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								compiler/mono.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,262 @@
 | 
				
			|||||||
 | 
					import { Expr, Stmt } from "./ast.ts";
 | 
				
			||||||
 | 
					import { AstVisitor, visitExpr, VisitRes, visitStmts } from "./ast_visitor.ts";
 | 
				
			||||||
 | 
					import { GenericArgsMap, VType } from "./vtype.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Monomorphizer {
 | 
				
			||||||
 | 
					    private fns: MonoFnsMap = {};
 | 
				
			||||||
 | 
					    private callMap: MonoCallNameGenMap = {};
 | 
				
			||||||
 | 
					    private allFns: Map<number, Stmt>;
 | 
				
			||||||
 | 
					    private entryFn: Stmt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(private ast: Stmt[]) {
 | 
				
			||||||
 | 
					        this.allFns = new AllFnsCollector().collect(this.ast);
 | 
				
			||||||
 | 
					        this.entryFn = findMain(this.allFns);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public monomorphize(): MonoResult {
 | 
				
			||||||
 | 
					        this.monomorphizeFn(this.entryFn);
 | 
				
			||||||
 | 
					        return { monoFns: this.fns, callMap: this.callMap };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private monomorphizeFn(
 | 
				
			||||||
 | 
					        stmt: Stmt,
 | 
				
			||||||
 | 
					        genericArgs?: GenericArgsMap,
 | 
				
			||||||
 | 
					    ): MonoFn {
 | 
				
			||||||
 | 
					        const nameGen = monoFnNameGen(stmt, genericArgs);
 | 
				
			||||||
 | 
					        if (nameGen in this.fns) {
 | 
				
			||||||
 | 
					            return this.fns[nameGen];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const monoFn = { nameGen, stmt, genericArgs };
 | 
				
			||||||
 | 
					        this.fns[nameGen] = monoFn;
 | 
				
			||||||
 | 
					        const calls = new CallCollector().collect(stmt);
 | 
				
			||||||
 | 
					        for (const call of calls) {
 | 
				
			||||||
 | 
					            this.callMap[call.id] = nameGen;
 | 
				
			||||||
 | 
					            if (call.kind.type !== "call") {
 | 
				
			||||||
 | 
					                throw new Error();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (call.kind.subject.vtype?.type === "fn") {
 | 
				
			||||||
 | 
					                const fn = this.allFns.get(call.kind.subject.vtype.stmtId);
 | 
				
			||||||
 | 
					                if (fn === undefined) {
 | 
				
			||||||
 | 
					                    throw new Error();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                const monoFn = this.monomorphizeFn(fn);
 | 
				
			||||||
 | 
					                this.callMap[call.id] = monoFn.nameGen;
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (call.kind.subject.vtype?.type === "generic_spec") {
 | 
				
			||||||
 | 
					                const genericSpecType = call.kind.subject.vtype!;
 | 
				
			||||||
 | 
					                if (genericSpecType.subject.type !== "fn") {
 | 
				
			||||||
 | 
					                    throw new Error();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                const fnType = genericSpecType.subject;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const monoArgs: GenericArgsMap = {};
 | 
				
			||||||
 | 
					                for (const key in genericSpecType.genericArgs) {
 | 
				
			||||||
 | 
					                    const vtype = genericSpecType.genericArgs[key];
 | 
				
			||||||
 | 
					                    if (vtype.type === "generic") {
 | 
				
			||||||
 | 
					                        if (genericArgs === undefined) {
 | 
				
			||||||
 | 
					                            throw new Error();
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        monoArgs[key] = genericArgs[vtype.param.id];
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        monoArgs[key] = vtype;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const fn = this.allFns.get(fnType.stmtId);
 | 
				
			||||||
 | 
					                if (fn === undefined) {
 | 
				
			||||||
 | 
					                    throw new Error();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                const monoFn = this.monomorphizeFn(fn, monoArgs);
 | 
				
			||||||
 | 
					                this.callMap[call.id] = monoFn.nameGen;
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return monoFn;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type MonoResult = {
 | 
				
			||||||
 | 
					    monoFns: MonoFnsMap;
 | 
				
			||||||
 | 
					    callMap: MonoCallNameGenMap;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type MonoFnsMap = { [nameGen: string]: MonoFn };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type MonoFn = {
 | 
				
			||||||
 | 
					    nameGen: string;
 | 
				
			||||||
 | 
					    stmt: Stmt;
 | 
				
			||||||
 | 
					    genericArgs?: GenericArgsMap;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type MonoCallNameGenMap = { [exprId: number]: string };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function monoFnNameGen(stmt: Stmt, genericArgs?: GenericArgsMap): string {
 | 
				
			||||||
 | 
					    if (stmt.kind.type !== "fn") {
 | 
				
			||||||
 | 
					        throw new Error();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (stmt.kind.ident === "main") {
 | 
				
			||||||
 | 
					        return "main";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (genericArgs === undefined) {
 | 
				
			||||||
 | 
					        return `${stmt.kind.ident}_${stmt.id}`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const args = Object.values(genericArgs)
 | 
				
			||||||
 | 
					        .map((arg) => vtypeNameGenPart(arg))
 | 
				
			||||||
 | 
					        .join("_");
 | 
				
			||||||
 | 
					    return `${stmt.kind.ident}_${stmt.id}_${args}`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function vtypeNameGenPart(vtype: VType): string {
 | 
				
			||||||
 | 
					    switch (vtype.type) {
 | 
				
			||||||
 | 
					        case "error":
 | 
				
			||||||
 | 
					            throw new Error("error in type");
 | 
				
			||||||
 | 
					        case "string":
 | 
				
			||||||
 | 
					        case "int":
 | 
				
			||||||
 | 
					        case "bool":
 | 
				
			||||||
 | 
					        case "null":
 | 
				
			||||||
 | 
					        case "unknown":
 | 
				
			||||||
 | 
					            return vtype.type;
 | 
				
			||||||
 | 
					        case "array":
 | 
				
			||||||
 | 
					            return `[${vtypeNameGenPart(vtype.inner)}]`;
 | 
				
			||||||
 | 
					        case "struct": {
 | 
				
			||||||
 | 
					            const fields = vtype.fields
 | 
				
			||||||
 | 
					                .map((field) =>
 | 
				
			||||||
 | 
					                    `${field.ident}, ${vtypeNameGenPart(field.vtype)}`
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .join(", ");
 | 
				
			||||||
 | 
					            return `struct { ${fields} }`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        case "fn":
 | 
				
			||||||
 | 
					            return `fn(${vtype.stmtId})`;
 | 
				
			||||||
 | 
					        case "generic":
 | 
				
			||||||
 | 
					        case "generic_spec":
 | 
				
			||||||
 | 
					            throw new Error("cannot be monomorphized");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AllFnsCollector implements AstVisitor {
 | 
				
			||||||
 | 
					    private allFns = new Map<number, Stmt>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public collect(ast: Stmt[]): Map<number, Stmt> {
 | 
				
			||||||
 | 
					        visitStmts(ast, this);
 | 
				
			||||||
 | 
					        return this.allFns;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    visitFnStmt(stmt: Stmt): VisitRes {
 | 
				
			||||||
 | 
					        if (stmt.kind.type !== "fn") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.allFns.set(stmt.id, stmt);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function findMain(fns: Map<number, Stmt>): Stmt {
 | 
				
			||||||
 | 
					    const mainId = fns.values().find((stmt) =>
 | 
				
			||||||
 | 
					        stmt.kind.type === "fn" && stmt.kind.ident === "main"
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (mainId === undefined) {
 | 
				
			||||||
 | 
					        console.error("error: cannot find function 'main'");
 | 
				
			||||||
 | 
					        console.error(apology);
 | 
				
			||||||
 | 
					        throw new Error("cannot find function 'main'");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return mainId;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CallCollector implements AstVisitor {
 | 
				
			||||||
 | 
					    private calls: Expr[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public collect(fn: Stmt): Expr[] {
 | 
				
			||||||
 | 
					        if (fn.kind.type !== "fn") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        visitExpr(fn.kind.body, this);
 | 
				
			||||||
 | 
					        return this.calls;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    visitFnStmt(_stmt: Stmt): VisitRes {
 | 
				
			||||||
 | 
					        return "stop";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    visitCallExpr(expr: Expr): VisitRes {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "call") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.calls.push(expr);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const apology = `
 | 
				
			||||||
 | 
					    Hear me out. Monomorphization, meaning the process
 | 
				
			||||||
 | 
					    inwich generic functions are stamped out into seperate
 | 
				
			||||||
 | 
					    specialized functions is actually really hard, and I
 | 
				
			||||||
 | 
					    have a really hard time right now, figuring out, how
 | 
				
			||||||
 | 
					    to do it in a smart way. To really explain it, let's
 | 
				
			||||||
 | 
					    imagine you have a function, you defined as a<T>().
 | 
				
			||||||
 | 
					    For each call with seperate generics arguments given,
 | 
				
			||||||
 | 
					    such as a::<int>() and a::<string>(), a specialized
 | 
				
			||||||
 | 
					    function has to be 'stamped out', ie. created and put
 | 
				
			||||||
 | 
					    into the compilation with the rest of the program. Now
 | 
				
			||||||
 | 
					    to the reason as to why 'main' is needed. To do the
 | 
				
			||||||
 | 
					    monomorphization, we have to do it recursively. To
 | 
				
			||||||
 | 
					    explain this, imagine you have a generic function a<T>
 | 
				
			||||||
 | 
					    and inside the body of a<T>, you call another generic
 | 
				
			||||||
 | 
					    function such as b<T> with the same generic type. This
 | 
				
			||||||
 | 
					    means that the monomorphization process of b<T> depends
 | 
				
			||||||
 | 
					    on the monomorphization of a<T>. What this essentially
 | 
				
			||||||
 | 
					    means, is that the monomorphization process works on
 | 
				
			||||||
 | 
					    the program as a call graph, meaning a graph or tree
 | 
				
			||||||
 | 
					    structure where each represents a function call to
 | 
				
			||||||
 | 
					    either another function or a recursive call to the
 | 
				
			||||||
 | 
					    function itself. But a problem arises from doing it
 | 
				
			||||||
 | 
					    this way, which is that a call graph will need an
 | 
				
			||||||
 | 
					    entrypoint. The language, as it is currently, does
 | 
				
			||||||
 | 
					    not really require a 'main'-function. Or maybe it
 | 
				
			||||||
 | 
					    does, but that's beside the point. The point is that
 | 
				
			||||||
 | 
					    we need a main function, to be the entry point for
 | 
				
			||||||
 | 
					    the call graph. The monomorphization process then
 | 
				
			||||||
 | 
					    runs through the program from that entry point. This
 | 
				
			||||||
 | 
					    means that each function we call, will itself be
 | 
				
			||||||
 | 
					    monomorphized and added to the compilation. It also
 | 
				
			||||||
 | 
					    means that functions that are not called, will also
 | 
				
			||||||
 | 
					    not be added to the compilation. This essentially
 | 
				
			||||||
 | 
					    eliminates uncalled/dead functions. Is this
 | 
				
			||||||
 | 
					    particularly smart to do in such a high level part
 | 
				
			||||||
 | 
					    of the compilation process? I don't know. It's
 | 
				
			||||||
 | 
					    obvious that we can't just use every function as
 | 
				
			||||||
 | 
					    an entry point in the call graph, because we're
 | 
				
			||||||
 | 
					    actively added new functions. Additionally, with
 | 
				
			||||||
 | 
					    generic functions, we don't know, if they're the
 | 
				
			||||||
 | 
					    entry point, what generic arguments, they should
 | 
				
			||||||
 | 
					    be monomorphized with. We could do monomorphization
 | 
				
			||||||
 | 
					    the same way C++ does it, where all non-generic
 | 
				
			||||||
 | 
					    functions before monomorphization are treated as
 | 
				
			||||||
 | 
					    entry points in the call graph. But this has the
 | 
				
			||||||
 | 
					    drawback that generic and non-generic functions
 | 
				
			||||||
 | 
					    are treated differently, which has many underlying
 | 
				
			||||||
 | 
					    drawbacks, especially pertaining to the amount of
 | 
				
			||||||
 | 
					    work needed to handle both in all proceeding steps
 | 
				
			||||||
 | 
					    of the compiler. Anyways, I just wanted to yap and
 | 
				
			||||||
 | 
					    complain about the way generics and monomorphization
 | 
				
			||||||
 | 
					    has made the compiler 100x more complicated, and
 | 
				
			||||||
 | 
					    that I find it really hard to implement in a way,
 | 
				
			||||||
 | 
					    that is not either too simplistic or so complicated
 | 
				
			||||||
 | 
					    and advanced I'm too dumb to implement it. So if
 | 
				
			||||||
 | 
					    you would be so kind as to make it clear to the
 | 
				
			||||||
 | 
					    compiler, what function it should designate as
 | 
				
			||||||
 | 
					    the entry point to the call graph, it will use
 | 
				
			||||||
 | 
					    for monomorphization, that would be very kind of
 | 
				
			||||||
 | 
					    you. The way you do this, is by added or selecting
 | 
				
			||||||
 | 
					    one of your current functions and giving it the
 | 
				
			||||||
 | 
					    name of 'main'. This is spelled m-a-i-n. The word
 | 
				
			||||||
 | 
					    is synonemous with the words primary and principle.
 | 
				
			||||||
 | 
					    The name is meant to designate the entry point into
 | 
				
			||||||
 | 
					    the program, which is why the monomorphization
 | 
				
			||||||
 | 
					    process uses this specific function as the entry
 | 
				
			||||||
 | 
					    point into the call graph, it generates. So if you
 | 
				
			||||||
 | 
					    would be so kind as to do that, that would really
 | 
				
			||||||
 | 
					    make my day. In any case, keep hacking ferociously
 | 
				
			||||||
 | 
					    on whatever you're working on. I have monomorphizer
 | 
				
			||||||
 | 
					    to implement. See ya. -Your favorite compiler girl <3
 | 
				
			||||||
 | 
					`.replaceAll("    ", "").trim();
 | 
				
			||||||
							
								
								
									
										618
									
								
								compiler/mono_lower.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										618
									
								
								compiler/mono_lower.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,618 @@
 | 
				
			|||||||
 | 
					import { Builtins, Ops } from "./arch.ts";
 | 
				
			||||||
 | 
					import { Assembler, Label } from "./assembler.ts";
 | 
				
			||||||
 | 
					import { Expr, Stmt } from "./ast.ts";
 | 
				
			||||||
 | 
					import { FnNamesMap } from "./lowerer.ts";
 | 
				
			||||||
 | 
					import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts";
 | 
				
			||||||
 | 
					import { MonoCallNameGenMap, MonoFn, MonoFnsMap } from "./mono.ts";
 | 
				
			||||||
 | 
					import { Pos } from "./token.ts";
 | 
				
			||||||
 | 
					import { vtypeToString } from "./vtype.ts";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class MonoLowerer {
 | 
				
			||||||
 | 
					    private program = Assembler.newRoot();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public constructor(
 | 
				
			||||||
 | 
					        private monoFns: MonoFnsMap,
 | 
				
			||||||
 | 
					        private callMap: MonoCallNameGenMap,
 | 
				
			||||||
 | 
					        private lastPos: Pos,
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public lower(): { program: number[]; fnNames: FnNamesMap } {
 | 
				
			||||||
 | 
					        const fnLabelNameMap: FnLabelMap = {};
 | 
				
			||||||
 | 
					        for (const nameGen in this.monoFns) {
 | 
				
			||||||
 | 
					            fnLabelNameMap[nameGen] = nameGen;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.addPrelimiary();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const fn of Object.values(this.monoFns)) {
 | 
				
			||||||
 | 
					            const fnProgram = new MonoFnLowerer(
 | 
				
			||||||
 | 
					                fn,
 | 
				
			||||||
 | 
					                this.program.fork(),
 | 
				
			||||||
 | 
					                this.callMap,
 | 
				
			||||||
 | 
					            ).lower();
 | 
				
			||||||
 | 
					            this.program.join(fnProgram);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.addConcluding();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const { program, locs } = this.program.assemble();
 | 
				
			||||||
 | 
					        const fnNames: FnNamesMap = {};
 | 
				
			||||||
 | 
					        for (const label in locs) {
 | 
				
			||||||
 | 
					            if (label in fnLabelNameMap) {
 | 
				
			||||||
 | 
					                fnNames[locs[label]] = fnLabelNameMap[label];
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return { program, fnNames };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private addPrelimiary() {
 | 
				
			||||||
 | 
					        this.addClearingSourceMap();
 | 
				
			||||||
 | 
					        this.program.add(Ops.PushPtr, { label: "main" });
 | 
				
			||||||
 | 
					        this.program.add(Ops.Call, 0);
 | 
				
			||||||
 | 
					        this.program.add(Ops.PushPtr, { label: "_exit" });
 | 
				
			||||||
 | 
					        this.program.add(Ops.Jump);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private addConcluding() {
 | 
				
			||||||
 | 
					        this.program.setLabel({ label: "_exit" });
 | 
				
			||||||
 | 
					        this.addSourceMap(this.lastPos);
 | 
				
			||||||
 | 
					        this.program.add(Ops.Pop);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private addSourceMap({ index, line, col }: Pos) {
 | 
				
			||||||
 | 
					        this.program.add(Ops.SourceMap, index, line, col);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private addClearingSourceMap() {
 | 
				
			||||||
 | 
					        this.program.add(Ops.SourceMap, 0, 1, 1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public printProgram() {
 | 
				
			||||||
 | 
					        this.program.printProgram();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FnLabelMap = { [nameGen: string]: string };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MonoFnLowerer {
 | 
				
			||||||
 | 
					    private locals: Locals = new LocalsFnRoot();
 | 
				
			||||||
 | 
					    private returnStack: Label[] = [];
 | 
				
			||||||
 | 
					    private breakStack: Label[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public constructor(
 | 
				
			||||||
 | 
					        private fn: MonoFn,
 | 
				
			||||||
 | 
					        private program: Assembler,
 | 
				
			||||||
 | 
					        private callMap: MonoCallNameGenMap,
 | 
				
			||||||
 | 
					    ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public lower(): Assembler {
 | 
				
			||||||
 | 
					        this.lowerFnStmt(this.fn.stmt);
 | 
				
			||||||
 | 
					        return this.program;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerFnStmt(stmt: Stmt) {
 | 
				
			||||||
 | 
					        if (stmt.kind.type !== "fn") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const label = this.fn.nameGen;
 | 
				
			||||||
 | 
					        this.program.setLabel({ label });
 | 
				
			||||||
 | 
					        this.addSourceMap(stmt.pos);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const outerLocals = this.locals;
 | 
				
			||||||
 | 
					        const fnRoot = new LocalsFnRoot(outerLocals);
 | 
				
			||||||
 | 
					        const outerProgram = this.program;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const returnLabel = this.program.makeLabel();
 | 
				
			||||||
 | 
					        this.returnStack.push(returnLabel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.program = outerProgram.fork();
 | 
				
			||||||
 | 
					        this.locals = fnRoot;
 | 
				
			||||||
 | 
					        for (const { ident } of stmt.kind.params) {
 | 
				
			||||||
 | 
					            this.locals.allocSym(ident);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (stmt.kind.anno?.ident === "builtin") {
 | 
				
			||||||
 | 
					            this.lowerFnBuiltinBody(stmt.kind.anno.values);
 | 
				
			||||||
 | 
					        } else if (stmt.kind.anno?.ident === "remainder") {
 | 
				
			||||||
 | 
					            this.program.add(Ops.Remainder);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            this.lowerExpr(stmt.kind.body);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.locals = outerLocals;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const localAmount = fnRoot.stackReserved() -
 | 
				
			||||||
 | 
					            stmt.kind.params.length;
 | 
				
			||||||
 | 
					        for (let i = 0; i < localAmount; ++i) {
 | 
				
			||||||
 | 
					            outerProgram.add(Ops.PushNull);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.returnStack.pop();
 | 
				
			||||||
 | 
					        this.program.setLabel(returnLabel);
 | 
				
			||||||
 | 
					        this.program.add(Ops.Return);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        outerProgram.join(this.program);
 | 
				
			||||||
 | 
					        this.program = outerProgram;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private addSourceMap({ index, line, col }: Pos) {
 | 
				
			||||||
 | 
					        this.program.add(Ops.SourceMap, index, line, col);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private addClearingSourceMap() {
 | 
				
			||||||
 | 
					        this.program.add(Ops.SourceMap, 0, 1, 1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerStmt(stmt: Stmt) {
 | 
				
			||||||
 | 
					        switch (stmt.kind.type) {
 | 
				
			||||||
 | 
					            case "error":
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case "break":
 | 
				
			||||||
 | 
					                return this.lowerBreakStmt(stmt);
 | 
				
			||||||
 | 
					            case "return":
 | 
				
			||||||
 | 
					                return this.lowerReturnStmt(stmt);
 | 
				
			||||||
 | 
					            case "fn":
 | 
				
			||||||
 | 
					                return this.lowerFnStmt(stmt);
 | 
				
			||||||
 | 
					            case "let":
 | 
				
			||||||
 | 
					                return this.lowerLetStmt(stmt);
 | 
				
			||||||
 | 
					            case "assign":
 | 
				
			||||||
 | 
					                return this.lowerAssignStmt(stmt);
 | 
				
			||||||
 | 
					            case "expr":
 | 
				
			||||||
 | 
					                this.lowerExpr(stmt.kind.expr);
 | 
				
			||||||
 | 
					                this.program.add(Ops.Pop);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw new Error(`unhandled stmt '${stmt.kind.type}'`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerAssignStmt(stmt: Stmt) {
 | 
				
			||||||
 | 
					        if (stmt.kind.type !== "assign") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.lowerExpr(stmt.kind.value);
 | 
				
			||||||
 | 
					        switch (stmt.kind.subject.kind.type) {
 | 
				
			||||||
 | 
					            case "field": {
 | 
				
			||||||
 | 
					                this.lowerExpr(stmt.kind.subject.kind.subject);
 | 
				
			||||||
 | 
					                this.program.add(Ops.PushString, stmt.kind.subject.kind.ident);
 | 
				
			||||||
 | 
					                this.program.add(Ops.Builtin, Builtins.StructSet);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "index": {
 | 
				
			||||||
 | 
					                this.lowerExpr(stmt.kind.subject.kind.subject);
 | 
				
			||||||
 | 
					                this.lowerExpr(stmt.kind.subject.kind.value);
 | 
				
			||||||
 | 
					                this.program.add(Ops.Builtin, Builtins.ArraySet);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            case "sym": {
 | 
				
			||||||
 | 
					                this.program.add(
 | 
				
			||||||
 | 
					                    Ops.StoreLocal,
 | 
				
			||||||
 | 
					                    this.locals.symId(stmt.kind.subject.kind.sym.ident),
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerReturnStmt(stmt: Stmt) {
 | 
				
			||||||
 | 
					        if (stmt.kind.type !== "return") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (stmt.kind.expr) {
 | 
				
			||||||
 | 
					            this.lowerExpr(stmt.kind.expr);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.addClearingSourceMap();
 | 
				
			||||||
 | 
					        this.program.add(Ops.PushPtr, this.returnStack.at(-1)!);
 | 
				
			||||||
 | 
					        this.program.add(Ops.Jump);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerBreakStmt(stmt: Stmt) {
 | 
				
			||||||
 | 
					        if (stmt.kind.type !== "break") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (stmt.kind.expr) {
 | 
				
			||||||
 | 
					            this.lowerExpr(stmt.kind.expr);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.addClearingSourceMap();
 | 
				
			||||||
 | 
					        this.program.add(Ops.PushPtr, this.breakStack.at(-1)!);
 | 
				
			||||||
 | 
					        this.program.add(Ops.Jump);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerFnBuiltinBody(annoArgs: Expr[]) {
 | 
				
			||||||
 | 
					        if (annoArgs.length !== 1) {
 | 
				
			||||||
 | 
					            throw new Error("invalid # of arguments to builtin annotation");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const anno = annoArgs[0];
 | 
				
			||||||
 | 
					        if (anno.kind.type !== "ident") {
 | 
				
			||||||
 | 
					            throw new Error(
 | 
				
			||||||
 | 
					                `unexpected argument type '${anno.kind.type}' expected 'ident'`,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const value = anno.kind.ident;
 | 
				
			||||||
 | 
					        const builtin = Object.entries(Builtins).find((entry) =>
 | 
				
			||||||
 | 
					            entry[0] === value
 | 
				
			||||||
 | 
					        )?.[1];
 | 
				
			||||||
 | 
					        if (builtin === undefined) {
 | 
				
			||||||
 | 
					            throw new Error(
 | 
				
			||||||
 | 
					                `unrecognized builtin '${value}'`,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.program.add(Ops.Builtin, builtin);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerLetStmt(stmt: Stmt) {
 | 
				
			||||||
 | 
					        if (stmt.kind.type !== "let") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.lowerExpr(stmt.kind.value);
 | 
				
			||||||
 | 
					        this.locals.allocSym(stmt.kind.param.ident);
 | 
				
			||||||
 | 
					        this.program.add(
 | 
				
			||||||
 | 
					            Ops.StoreLocal,
 | 
				
			||||||
 | 
					            this.locals.symId(stmt.kind.param.ident),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerExpr(expr: Expr) {
 | 
				
			||||||
 | 
					        switch (expr.kind.type) {
 | 
				
			||||||
 | 
					            case "error":
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case "sym":
 | 
				
			||||||
 | 
					                return this.lowerSymExpr(expr);
 | 
				
			||||||
 | 
					            case "null":
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case "int":
 | 
				
			||||||
 | 
					                return this.lowerIntExpr(expr);
 | 
				
			||||||
 | 
					            case "bool":
 | 
				
			||||||
 | 
					                return this.lowerBoolExpr(expr);
 | 
				
			||||||
 | 
					            case "string":
 | 
				
			||||||
 | 
					                return this.lowerStringExpr(expr);
 | 
				
			||||||
 | 
					            case "ident":
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case "group":
 | 
				
			||||||
 | 
					                return void this.lowerExpr(expr.kind.expr);
 | 
				
			||||||
 | 
					            case "field":
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case "index":
 | 
				
			||||||
 | 
					                return this.lowerIndexExpr(expr);
 | 
				
			||||||
 | 
					            case "call":
 | 
				
			||||||
 | 
					                return this.lowerCallExpr(expr);
 | 
				
			||||||
 | 
					            case "etype_args":
 | 
				
			||||||
 | 
					                return this.lowerETypeArgsExpr(expr);
 | 
				
			||||||
 | 
					            case "unary":
 | 
				
			||||||
 | 
					                return this.lowerUnaryExpr(expr);
 | 
				
			||||||
 | 
					            case "binary":
 | 
				
			||||||
 | 
					                return this.lowerBinaryExpr(expr);
 | 
				
			||||||
 | 
					            case "if":
 | 
				
			||||||
 | 
					                return this.lowerIfExpr(expr);
 | 
				
			||||||
 | 
					            case "loop":
 | 
				
			||||||
 | 
					                return this.lowerLoopExpr(expr);
 | 
				
			||||||
 | 
					            case "block":
 | 
				
			||||||
 | 
					                return this.lowerBlockExpr(expr);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw new Error(`unhandled expr '${expr.kind.type}'`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerIndexExpr(expr: Expr) {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "index") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.lowerExpr(expr.kind.subject);
 | 
				
			||||||
 | 
					        this.lowerExpr(expr.kind.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (expr.kind.subject.vtype?.type == "array") {
 | 
				
			||||||
 | 
					            this.program.add(Ops.Builtin, Builtins.ArrayAt);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (expr.kind.subject.vtype?.type == "string") {
 | 
				
			||||||
 | 
					            this.program.add(Ops.Builtin, Builtins.StringCharAt);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw new Error(`unhandled index subject type '${expr.kind.subject}'`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerSymExpr(expr: Expr) {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "sym") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (expr.kind.sym.type === "let") {
 | 
				
			||||||
 | 
					            const symId = this.locals.symId(expr.kind.ident);
 | 
				
			||||||
 | 
					            this.program.add(Ops.LoadLocal, symId);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (expr.kind.sym.type === "fn_param") {
 | 
				
			||||||
 | 
					            this.program.add(
 | 
				
			||||||
 | 
					                Ops.LoadLocal,
 | 
				
			||||||
 | 
					                this.locals.symId(expr.kind.ident),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (expr.kind.sym.type === "fn") {
 | 
				
			||||||
 | 
					            // Is this smart? Well, my presumption is
 | 
				
			||||||
 | 
					            // that it isn't. The underlying problem, which
 | 
				
			||||||
 | 
					            // this solutions raison d'être is to solve, is
 | 
				
			||||||
 | 
					            // that the compiler, as it d'être's currently
 | 
				
			||||||
 | 
					            // doesn't support checking and infering generic
 | 
				
			||||||
 | 
					            // fn args all the way down to the sym. Therefore,
 | 
				
			||||||
 | 
					            // when a sym is checked in a call expr, we can't
 | 
				
			||||||
 | 
					            // really do anything useful. Instead the actual
 | 
				
			||||||
 | 
					            // function pointer pointing to the actual
 | 
				
			||||||
 | 
					            // monomorphized function is emplaced when
 | 
				
			||||||
 | 
					            // lowering the call expression itself. But what
 | 
				
			||||||
 | 
					            // should we do then, if the user decides to
 | 
				
			||||||
 | 
					            // assign a function to a local? You might ask.
 | 
				
			||||||
 | 
					            // You see, that's where the problem lies.
 | 
				
			||||||
 | 
					            // My current, very thought out solution, as
 | 
				
			||||||
 | 
					            // you can read below, is to push a null pointer,
 | 
				
			||||||
 | 
					            // for it to then be replaced later. This will
 | 
				
			||||||
 | 
					            // probably cause many hastles in the future
 | 
				
			||||||
 | 
					            // for myself in particular, when trying to
 | 
				
			||||||
 | 
					            // decipher the lowerer's output. So if you're
 | 
				
			||||||
 | 
					            // the unlucky girl, who has tried for ages to
 | 
				
			||||||
 | 
					            // decipher why a zero value is pushed and then
 | 
				
			||||||
 | 
					            // later replaced, and then you finally
 | 
				
			||||||
 | 
					            // stumbled upon this here implementation,
 | 
				
			||||||
 | 
					            // let me first say, I'm so sorry. At the time
 | 
				
			||||||
 | 
					            // of writing, I really haven't thought out
 | 
				
			||||||
 | 
					            // very well, how the generic call system should
 | 
				
			||||||
 | 
					            // work, and it's therefore a bit flaky, and the
 | 
				
			||||||
 | 
					            // implementation kinda looks like it was
 | 
				
			||||||
 | 
					            // implementated by a girl who didn't really
 | 
				
			||||||
 | 
					            // understand very well what they were
 | 
				
			||||||
 | 
					            // implementing at the time that they were
 | 
				
			||||||
 | 
					            // implementing it. Anyway, I just wanted to
 | 
				
			||||||
 | 
					            // apologize. Happy coding.
 | 
				
			||||||
 | 
					            // -Your favorite compiler girl.
 | 
				
			||||||
 | 
					            this.program.add(Ops.PushPtr, 0);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw new Error(`unhandled sym type '${expr.kind.sym.type}'`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerIntExpr(expr: Expr) {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "int") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.program.add(Ops.PushInt, expr.kind.value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerBoolExpr(expr: Expr) {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "bool") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.program.add(Ops.PushBool, expr.kind.value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerStringExpr(expr: Expr) {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "string") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.program.add(Ops.PushString, expr.kind.value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerUnaryExpr(expr: Expr) {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "unary") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.lowerExpr(expr.kind.subject);
 | 
				
			||||||
 | 
					        const vtype = expr.kind.subject.vtype!;
 | 
				
			||||||
 | 
					        if (vtype.type === "bool") {
 | 
				
			||||||
 | 
					            switch (expr.kind.unaryType) {
 | 
				
			||||||
 | 
					                case "not":
 | 
				
			||||||
 | 
					                    this.program.add(Ops.Not);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                default:
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (vtype.type === "int") {
 | 
				
			||||||
 | 
					            switch (expr.kind.unaryType) {
 | 
				
			||||||
 | 
					                case "-": {
 | 
				
			||||||
 | 
					                    this.program.add(Ops.PushInt, 0);
 | 
				
			||||||
 | 
					                    this.program.add(Ops.Swap);
 | 
				
			||||||
 | 
					                    this.program.add(Ops.Subtract);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                default:
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw new Error(
 | 
				
			||||||
 | 
					            `unhandled unary` +
 | 
				
			||||||
 | 
					                ` '${vtypeToString(expr.vtype!)}' aka. ` +
 | 
				
			||||||
 | 
					                ` ${expr.kind.unaryType}` +
 | 
				
			||||||
 | 
					                ` '${vtypeToString(expr.kind.subject.vtype!)}'`,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerBinaryExpr(expr: Expr) {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "binary") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const vtype = expr.kind.left.vtype!;
 | 
				
			||||||
 | 
					        if (vtype.type === "bool") {
 | 
				
			||||||
 | 
					            if (["or", "and"].includes(expr.kind.binaryType)) {
 | 
				
			||||||
 | 
					                const shortCircuitLabel = this.program.makeLabel();
 | 
				
			||||||
 | 
					                this.lowerExpr(expr.kind.left);
 | 
				
			||||||
 | 
					                this.program.add(Ops.Duplicate);
 | 
				
			||||||
 | 
					                if (expr.kind.binaryType === "and") {
 | 
				
			||||||
 | 
					                    this.program.add(Ops.Not);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                this.program.add(Ops.PushPtr, shortCircuitLabel);
 | 
				
			||||||
 | 
					                this.program.add(Ops.JumpIfTrue);
 | 
				
			||||||
 | 
					                this.program.add(Ops.Pop);
 | 
				
			||||||
 | 
					                this.lowerExpr(expr.kind.right);
 | 
				
			||||||
 | 
					                this.program.setLabel(shortCircuitLabel);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.lowerExpr(expr.kind.left);
 | 
				
			||||||
 | 
					        this.lowerExpr(expr.kind.right);
 | 
				
			||||||
 | 
					        if (vtype.type === "int") {
 | 
				
			||||||
 | 
					            switch (expr.kind.binaryType) {
 | 
				
			||||||
 | 
					                case "+":
 | 
				
			||||||
 | 
					                    this.program.add(Ops.Add);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                case "-":
 | 
				
			||||||
 | 
					                    this.program.add(Ops.Subtract);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                case "*":
 | 
				
			||||||
 | 
					                    this.program.add(Ops.Multiply);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                case "/":
 | 
				
			||||||
 | 
					                    this.program.add(Ops.Multiply);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                case "==":
 | 
				
			||||||
 | 
					                    this.program.add(Ops.Equal);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                case "!=":
 | 
				
			||||||
 | 
					                    this.program.add(Ops.Equal);
 | 
				
			||||||
 | 
					                    this.program.add(Ops.Not);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                case "<":
 | 
				
			||||||
 | 
					                    this.program.add(Ops.LessThan);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                case ">":
 | 
				
			||||||
 | 
					                    this.program.add(Ops.Swap);
 | 
				
			||||||
 | 
					                    this.program.add(Ops.LessThan);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                case "<=":
 | 
				
			||||||
 | 
					                    this.program.add(Ops.Swap);
 | 
				
			||||||
 | 
					                    this.program.add(Ops.LessThan);
 | 
				
			||||||
 | 
					                    this.program.add(Ops.Not);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                case ">=":
 | 
				
			||||||
 | 
					                    this.program.add(Ops.LessThan);
 | 
				
			||||||
 | 
					                    this.program.add(Ops.Not);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                default:
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (vtype.type === "bool") {
 | 
				
			||||||
 | 
					            switch (expr.kind.binaryType) {
 | 
				
			||||||
 | 
					                case "==":
 | 
				
			||||||
 | 
					                    this.program.add(Ops.And);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                case "!=":
 | 
				
			||||||
 | 
					                    this.program.add(Ops.And);
 | 
				
			||||||
 | 
					                    this.program.add(Ops.Not);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                default:
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (vtype.type === "string") {
 | 
				
			||||||
 | 
					            if (expr.kind.binaryType === "+") {
 | 
				
			||||||
 | 
					                this.program.add(Ops.Builtin, Builtins.StringConcat);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (expr.kind.binaryType === "==") {
 | 
				
			||||||
 | 
					                this.program.add(Ops.Builtin, Builtins.StringEqual);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (expr.kind.binaryType === "!=") {
 | 
				
			||||||
 | 
					                this.program.add(Ops.Builtin, Builtins.StringEqual);
 | 
				
			||||||
 | 
					                this.program.add(Ops.Not);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw new Error(
 | 
				
			||||||
 | 
					            `unhandled binaryType` +
 | 
				
			||||||
 | 
					                ` '${vtypeToString(expr.vtype!)}' aka. ` +
 | 
				
			||||||
 | 
					                ` '${vtypeToString(expr.kind.left.vtype!)}'` +
 | 
				
			||||||
 | 
					                ` ${expr.kind.binaryType}` +
 | 
				
			||||||
 | 
					                ` '${vtypeToString(expr.kind.left.vtype!)}'`,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerCallExpr(expr: Expr) {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "call") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for (const arg of expr.kind.args) {
 | 
				
			||||||
 | 
					            this.lowerExpr(arg);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.lowerExpr(expr.kind.subject);
 | 
				
			||||||
 | 
					        this.program.add(Ops.Pop);
 | 
				
			||||||
 | 
					        this.program.add(Ops.PushPtr, { label: this.callMap[expr.id] });
 | 
				
			||||||
 | 
					        this.program.add(Ops.Call, expr.kind.args.length);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerETypeArgsExpr(expr: Expr) {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "etype_args") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.lowerExpr(expr.kind.subject);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerIfExpr(expr: Expr) {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "if") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const falseLabel = this.program.makeLabel();
 | 
				
			||||||
 | 
					        const doneLabel = this.program.makeLabel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.lowerExpr(expr.kind.cond);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.program.add(Ops.Not);
 | 
				
			||||||
 | 
					        this.addClearingSourceMap();
 | 
				
			||||||
 | 
					        this.program.add(Ops.PushPtr, falseLabel);
 | 
				
			||||||
 | 
					        this.program.add(Ops.JumpIfTrue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.addSourceMap(expr.kind.truthy.pos);
 | 
				
			||||||
 | 
					        this.lowerExpr(expr.kind.truthy);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.addClearingSourceMap();
 | 
				
			||||||
 | 
					        this.program.add(Ops.PushPtr, doneLabel);
 | 
				
			||||||
 | 
					        this.program.add(Ops.Jump);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.program.setLabel(falseLabel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (expr.kind.falsy) {
 | 
				
			||||||
 | 
					            this.addSourceMap(expr.kind.elsePos!);
 | 
				
			||||||
 | 
					            this.lowerExpr(expr.kind.falsy);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            this.program.add(Ops.PushNull);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.program.setLabel(doneLabel);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerLoopExpr(expr: Expr) {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "loop") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const continueLabel = this.program.makeLabel();
 | 
				
			||||||
 | 
					        const breakLabel = this.program.makeLabel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.breakStack.push(breakLabel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.program.setLabel(continueLabel);
 | 
				
			||||||
 | 
					        this.addSourceMap(expr.kind.body.pos);
 | 
				
			||||||
 | 
					        this.lowerExpr(expr.kind.body);
 | 
				
			||||||
 | 
					        this.program.add(Ops.Pop);
 | 
				
			||||||
 | 
					        this.addClearingSourceMap();
 | 
				
			||||||
 | 
					        this.program.add(Ops.PushPtr, continueLabel);
 | 
				
			||||||
 | 
					        this.program.add(Ops.Jump);
 | 
				
			||||||
 | 
					        this.program.setLabel(breakLabel);
 | 
				
			||||||
 | 
					        if (expr.vtype!.type === "null") {
 | 
				
			||||||
 | 
					            this.program.add(Ops.PushNull);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.breakStack.pop();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private lowerBlockExpr(expr: Expr) {
 | 
				
			||||||
 | 
					        if (expr.kind.type !== "block") {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const outerLocals = this.locals;
 | 
				
			||||||
 | 
					        this.locals = new LocalLeaf(this.locals);
 | 
				
			||||||
 | 
					        for (const stmt of expr.kind.stmts) {
 | 
				
			||||||
 | 
					            this.addSourceMap(stmt.pos);
 | 
				
			||||||
 | 
					            this.lowerStmt(stmt);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (expr.kind.expr) {
 | 
				
			||||||
 | 
					            this.addSourceMap(expr.kind.expr.pos);
 | 
				
			||||||
 | 
					            this.lowerExpr(expr.kind.expr);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            this.program.add(Ops.PushNull);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.locals = outerLocals;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -269,12 +269,16 @@ export class Parser {
 | 
				
			|||||||
        return this.parseDelimitedList(this.parseETypeParam, ">", ",");
 | 
					        return this.parseDelimitedList(this.parseETypeParam, ">", ",");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private veryTemporaryETypeParamIdCounter = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private parseETypeParam(): Res<GenericParam> {
 | 
					    private parseETypeParam(): Res<GenericParam> {
 | 
				
			||||||
        const pos = this.pos();
 | 
					        const pos = this.pos();
 | 
				
			||||||
        if (this.test("ident")) {
 | 
					        if (this.test("ident")) {
 | 
				
			||||||
            const ident = this.current().identValue!;
 | 
					            const ident = this.current().identValue!;
 | 
				
			||||||
            this.step();
 | 
					            this.step();
 | 
				
			||||||
            return { ok: true, value: { ident, pos } };
 | 
					            const id = this.veryTemporaryETypeParamIdCounter;
 | 
				
			||||||
 | 
					            this.veryTemporaryETypeParamIdCounter += 1;
 | 
				
			||||||
 | 
					            return { ok: true, value: { id, ident, pos } };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.report("expected generic parameter");
 | 
					        this.report("expected generic parameter");
 | 
				
			||||||
        return { ok: false };
 | 
					        return { ok: false };
 | 
				
			||||||
 | 
				
			|||||||
@ -12,12 +12,13 @@ export type VType =
 | 
				
			|||||||
        genericParams?: VTypeGenericParam[];
 | 
					        genericParams?: VTypeGenericParam[];
 | 
				
			||||||
        params: VTypeParam[];
 | 
					        params: VTypeParam[];
 | 
				
			||||||
        returnType: VType;
 | 
					        returnType: VType;
 | 
				
			||||||
 | 
					        stmtId: number;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    | { type: "generic" }
 | 
					    | { type: "generic"; param: VTypeGenericParam }
 | 
				
			||||||
    | {
 | 
					    | {
 | 
				
			||||||
        type: "generic_spec";
 | 
					        type: "generic_spec";
 | 
				
			||||||
        subject: VType;
 | 
					        subject: VType;
 | 
				
			||||||
        genericParams: VType[];
 | 
					        genericArgs: GenericArgsMap;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type VTypeParam = {
 | 
					export type VTypeParam = {
 | 
				
			||||||
@ -26,21 +27,25 @@ export type VTypeParam = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type VTypeGenericParam = {
 | 
					export type VTypeGenericParam = {
 | 
				
			||||||
 | 
					    id: number;
 | 
				
			||||||
    ident: string;
 | 
					    ident: string;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function vtypesEqual(a: VType, b: VType): boolean {
 | 
					export type GenericArgsMap = { [id: number]: VType };
 | 
				
			||||||
    if (a.type !== b.type) {
 | 
					
 | 
				
			||||||
        return false;
 | 
					export function vtypesEqual(
 | 
				
			||||||
    }
 | 
					    a: VType,
 | 
				
			||||||
 | 
					    b: VType,
 | 
				
			||||||
 | 
					    generics?: GenericArgsMap,
 | 
				
			||||||
 | 
					): boolean {
 | 
				
			||||||
    if (
 | 
					    if (
 | 
				
			||||||
        ["error", "unknown", "null", "int", "string", "bool"]
 | 
					        ["error", "unknown", "null", "int", "string", "bool"]
 | 
				
			||||||
            .includes(a.type)
 | 
					            .includes(a.type) && a.type === b.type
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (a.type === "array" && b.type === "array") {
 | 
					    if (a.type === "array" && b.type === "array") {
 | 
				
			||||||
        return vtypesEqual(a.inner, b.inner);
 | 
					        return vtypesEqual(a.inner, b.inner, generics);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (a.type === "fn" && b.type === "fn") {
 | 
					    if (a.type === "fn" && b.type === "fn") {
 | 
				
			||||||
        if (a.params.length !== b.params.length) {
 | 
					        if (a.params.length !== b.params.length) {
 | 
				
			||||||
@ -51,11 +56,41 @@ export function vtypesEqual(a: VType, b: VType): boolean {
 | 
				
			|||||||
                return false;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return vtypesEqual(a.returnType, b.returnType);
 | 
					        return vtypesEqual(a.returnType, b.returnType, generics);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (a.type === "generic" && b.type === "generic") {
 | 
				
			||||||
 | 
					        return a.param.id === b.param.id;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					        (a.type === "generic" || b.type === "generic") &&
 | 
				
			||||||
 | 
					        generics !== undefined
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        if (generics === undefined) {
 | 
				
			||||||
 | 
					            throw new Error();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const generic = a.type === "generic" ? a : b;
 | 
				
			||||||
 | 
					        const concrete = a.type === "generic" ? b : a;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const genericType = extractGenericType(generic, generics);
 | 
				
			||||||
 | 
					        return vtypesEqual(genericType, concrete, generics);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function extractGenericType(
 | 
				
			||||||
 | 
					    generic: VType,
 | 
				
			||||||
 | 
					    generics: GenericArgsMap,
 | 
				
			||||||
 | 
					): VType {
 | 
				
			||||||
 | 
					    if (generic.type !== "generic") {
 | 
				
			||||||
 | 
					        return generic;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!(generic.param.id in generics)) {
 | 
				
			||||||
 | 
					        throw new Error("generic not found (not supposed to happen)");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return generics[generic.param.id];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function vtypeToString(vtype: VType): string {
 | 
					export function vtypeToString(vtype: VType): string {
 | 
				
			||||||
    if (
 | 
					    if (
 | 
				
			||||||
        ["error", "unknown", "null", "int", "string", "bool"]
 | 
					        ["error", "unknown", "null", "int", "string", "bool"]
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					fn exit(status_code: int) #[builtin(Exit)] {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn print(msg: string) #[builtin(Print)] {}
 | 
					fn print(msg: string) #[builtin(Print)] {}
 | 
				
			||||||
fn println(msg: string) { print(msg + "\n") }
 | 
					fn println(msg: string) { print(msg + "\n") }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,17 +1,23 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
fn exit(status_code: int) #[builtin(Exit)] {}
 | 
					fn exit(status_code: int) #[builtin(Exit)] {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn print(msg: string) #[builtin(Print)] {}
 | 
				
			||||||
 | 
					fn println(msg: string) { print(msg + "\n") }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn id<T>(v: T) -> T {
 | 
					fn id<T>(v: T) -> T {
 | 
				
			||||||
    v
 | 
					    v
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn main() {
 | 
					fn main() {
 | 
				
			||||||
 | 
					    println("calling with int");
 | 
				
			||||||
    if id::<int>(123) != 123 {
 | 
					    if id::<int>(123) != 123 {
 | 
				
			||||||
        exit(1);
 | 
					        exit(1);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    println("calling with bool");
 | 
				
			||||||
    if id::<bool>(true) != true {
 | 
					    if id::<bool>(true) != true {
 | 
				
			||||||
        exit(1);
 | 
					        exit(1);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    println("all tests ran successfully");
 | 
				
			||||||
    exit(0);
 | 
					    exit(0);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user