Compare commits
	
		
			No commits in common. "76fff577b1e72481cdccbe659e120357afda9379" and "1bbf6121b0dab84a1140d14028b7cf9772c5304d" have entirely different histories.
		
	
	
		
			76fff577b1
			...
			1bbf6121b0
		
	
		
@ -111,8 +111,6 @@ export type Field = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Param = {
 | 
			
		||||
    id: number;
 | 
			
		||||
    index?: number;
 | 
			
		||||
    ident: string;
 | 
			
		||||
    etype?: EType;
 | 
			
		||||
    pos: Pos;
 | 
			
		||||
@ -158,7 +156,6 @@ export type ETypeKind =
 | 
			
		||||
 | 
			
		||||
export type GenericParam = {
 | 
			
		||||
    id: number;
 | 
			
		||||
    index: number;
 | 
			
		||||
    ident: string;
 | 
			
		||||
    pos: Pos;
 | 
			
		||||
    vtype?: VType;
 | 
			
		||||
@ -174,34 +171,21 @@ export class AstCreator {
 | 
			
		||||
    private nextNodeId = 0;
 | 
			
		||||
 | 
			
		||||
    public stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt {
 | 
			
		||||
        const id = this.genId();
 | 
			
		||||
        const id = this.nextNodeId;
 | 
			
		||||
        this.nextNodeId += 1;
 | 
			
		||||
        return { kind, pos, details, id };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public expr(kind: ExprKind, pos: Pos): Expr {
 | 
			
		||||
        const id = this.genId();
 | 
			
		||||
        const id = this.nextNodeId;
 | 
			
		||||
        this.nextNodeId += 1;
 | 
			
		||||
        return { kind, pos, id };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public etype(kind: ETypeKind, pos: Pos): EType {
 | 
			
		||||
        const id = this.genId();
 | 
			
		||||
        return { kind, pos, id };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public param(val: Omit<Param, "id">): Param {
 | 
			
		||||
        const id = this.genId();
 | 
			
		||||
        return { ...val, id };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public genericParam(val: Omit<GenericParam, "id">): GenericParam {
 | 
			
		||||
        const id = this.genId();
 | 
			
		||||
        return { ...val, id };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private genId(): number {
 | 
			
		||||
        const id = this.nextNodeId;
 | 
			
		||||
        this.nextNodeId += 1;
 | 
			
		||||
        return id;
 | 
			
		||||
        return { kind, pos, id };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -10,12 +10,10 @@ import { FnNamesMap, Lowerer } from "./lowerer.ts";
 | 
			
		||||
import { Parser } from "./parser.ts";
 | 
			
		||||
import { Resolver } from "./resolver.ts";
 | 
			
		||||
import { AstVisitor, VisitRes, visitStmts } from "./ast_visitor.ts";
 | 
			
		||||
import { Pos } from "./token.ts";
 | 
			
		||||
import { ArrayLiteralDesugarer } from "./desugar/array_literal.ts";
 | 
			
		||||
 | 
			
		||||
import * as path from "jsr:@std/path";
 | 
			
		||||
import { AstLowerer } from "./middle/lower_ast.ts";
 | 
			
		||||
import { printMir } from "./middle/mir.ts";
 | 
			
		||||
import { Pos } from "./token.ts";
 | 
			
		||||
import { ArrayLiteralDesugarer } from "./desugar/array_literal.ts";
 | 
			
		||||
 | 
			
		||||
export type CompileResult = {
 | 
			
		||||
    program: number[];
 | 
			
		||||
@ -31,32 +29,28 @@ export class Compiler {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async compile(): Promise<CompileResult> {
 | 
			
		||||
        const { ast } = new ModTree(
 | 
			
		||||
        const mod = new ModTree(
 | 
			
		||||
            this.startFilePath,
 | 
			
		||||
            this.astCreator,
 | 
			
		||||
            this.reporter,
 | 
			
		||||
        ).resolve();
 | 
			
		||||
 | 
			
		||||
        new SpecialLoopDesugarer(this.astCreator).desugar(ast);
 | 
			
		||||
        new ArrayLiteralDesugarer(this.astCreator).desugar(ast);
 | 
			
		||||
        new StructLiteralDesugarer(this.astCreator).desugar(ast);
 | 
			
		||||
        new SpecialLoopDesugarer(this.astCreator).desugar(mod.ast);
 | 
			
		||||
        new ArrayLiteralDesugarer(this.astCreator).desugar(mod.ast);
 | 
			
		||||
        new StructLiteralDesugarer(this.astCreator).desugar(mod.ast);
 | 
			
		||||
 | 
			
		||||
        new Resolver(this.reporter).resolve(ast);
 | 
			
		||||
        new Resolver(this.reporter).resolve(mod.ast);
 | 
			
		||||
 | 
			
		||||
        new CompoundAssignDesugarer(this.astCreator).desugar(ast);
 | 
			
		||||
        new CompoundAssignDesugarer(this.astCreator).desugar(mod.ast);
 | 
			
		||||
 | 
			
		||||
        new Checker(this.reporter).check(ast);
 | 
			
		||||
 | 
			
		||||
        const mir = new AstLowerer(ast).lower();
 | 
			
		||||
 | 
			
		||||
        printMir(mir);
 | 
			
		||||
        new Checker(this.reporter).check(mod.ast);
 | 
			
		||||
 | 
			
		||||
        if (this.reporter.errorOccured()) {
 | 
			
		||||
            console.error("Errors occurred, stopping compilation.");
 | 
			
		||||
            Deno.exit(1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const { monoFns, callMap } = new Monomorphizer(ast).monomorphize();
 | 
			
		||||
        const { monoFns, callMap } = new Monomorphizer(mod.ast).monomorphize();
 | 
			
		||||
 | 
			
		||||
        const lastPos = await lastPosInTextFile(this.startFilePath);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -49,10 +49,10 @@ export class ArrayLiteralDesugarer implements AstVisitor {
 | 
			
		||||
            stmts: [
 | 
			
		||||
                Stmt({
 | 
			
		||||
                    type: "let",
 | 
			
		||||
                    param: this.astCreator.param({
 | 
			
		||||
                    param: {
 | 
			
		||||
                        ident: "::value",
 | 
			
		||||
                        pos: npos,
 | 
			
		||||
                    }),
 | 
			
		||||
                    },
 | 
			
		||||
                    value: Expr({
 | 
			
		||||
                        type: "call",
 | 
			
		||||
                        subject: Expr({
 | 
			
		||||
 | 
			
		||||
@ -70,18 +70,12 @@ export class SpecialLoopDesugarer implements AstVisitor {
 | 
			
		||||
            stmts: [
 | 
			
		||||
                Stmt({
 | 
			
		||||
                    type: "let",
 | 
			
		||||
                    param: this.astCreator.param({
 | 
			
		||||
                        ident: "::values",
 | 
			
		||||
                        pos: npos,
 | 
			
		||||
                    }),
 | 
			
		||||
                    param: { ident: "::values", pos: npos },
 | 
			
		||||
                    value: expr.kind.value,
 | 
			
		||||
                }),
 | 
			
		||||
                Stmt({
 | 
			
		||||
                    type: "let",
 | 
			
		||||
                    param: this.astCreator.param({
 | 
			
		||||
                        ident: "::length",
 | 
			
		||||
                        pos: npos,
 | 
			
		||||
                    }),
 | 
			
		||||
                    param: { ident: "::length", pos: npos },
 | 
			
		||||
                    value: Expr({
 | 
			
		||||
                        type: "call",
 | 
			
		||||
                        subject: Expr({
 | 
			
		||||
@ -102,10 +96,7 @@ export class SpecialLoopDesugarer implements AstVisitor {
 | 
			
		||||
                }),
 | 
			
		||||
                Stmt({
 | 
			
		||||
                    type: "let",
 | 
			
		||||
                    param: this.astCreator.param({
 | 
			
		||||
                        ident: "::index",
 | 
			
		||||
                        pos: npos,
 | 
			
		||||
                    }),
 | 
			
		||||
                    param: { ident: "::index", pos: npos },
 | 
			
		||||
                    value: Expr({ type: "int", value: 0 }),
 | 
			
		||||
                }, expr.pos),
 | 
			
		||||
                Stmt({
 | 
			
		||||
 | 
			
		||||
@ -52,10 +52,10 @@ export class StructLiteralDesugarer implements AstVisitor {
 | 
			
		||||
            stmts: [
 | 
			
		||||
                Stmt({
 | 
			
		||||
                    type: "let",
 | 
			
		||||
                    param: this.astCreator.param({
 | 
			
		||||
                    param: {
 | 
			
		||||
                        ident: "::value",
 | 
			
		||||
                        pos: npos,
 | 
			
		||||
                    }),
 | 
			
		||||
                    },
 | 
			
		||||
                    value: Expr({
 | 
			
		||||
                        type: "call",
 | 
			
		||||
                        subject: Expr({
 | 
			
		||||
 | 
			
		||||
@ -1,469 +0,0 @@
 | 
			
		||||
import * as Ast from "../ast.ts";
 | 
			
		||||
import { AllFnsCollector } from "../mono.ts";
 | 
			
		||||
import { VType, vtypesEqual } from "../vtype.ts";
 | 
			
		||||
import {
 | 
			
		||||
    Block,
 | 
			
		||||
    BlockId,
 | 
			
		||||
    Fn,
 | 
			
		||||
    Local,
 | 
			
		||||
    LocalId,
 | 
			
		||||
    Mir,
 | 
			
		||||
    Op,
 | 
			
		||||
    OpKind,
 | 
			
		||||
    Ter,
 | 
			
		||||
    TerKind,
 | 
			
		||||
} from "./mir.ts";
 | 
			
		||||
 | 
			
		||||
export class AstLowerer {
 | 
			
		||||
    public constructor(private ast: Ast.Stmt[]) {}
 | 
			
		||||
 | 
			
		||||
    public lower(): Mir {
 | 
			
		||||
        const fnAsts = new AllFnsCollector().collect(this.ast).values();
 | 
			
		||||
        const fns = fnAsts
 | 
			
		||||
            .map((fnAst) => new FnAstLowerer(fnAst).lower())
 | 
			
		||||
            .toArray();
 | 
			
		||||
        return { fns };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class LocalAllocator {
 | 
			
		||||
    private locals: Local[] = [];
 | 
			
		||||
 | 
			
		||||
    public alloc(vtype: VType): LocalId {
 | 
			
		||||
        const id = this.locals.length;
 | 
			
		||||
        this.locals.push({ id, vtype });
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public finish(): Local[] {
 | 
			
		||||
        return this.locals;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class FnAstLowerer {
 | 
			
		||||
    private locals = new LocalAllocator();
 | 
			
		||||
    private blockIdCounter = 0;
 | 
			
		||||
    private currentBlockId = 0;
 | 
			
		||||
    private blocks = new Map<BlockId, Block>();
 | 
			
		||||
 | 
			
		||||
    private fnParamIndexLocals = new Map<number, LocalId>();
 | 
			
		||||
    private letStmtIdLocals = new Map<number, LocalId>();
 | 
			
		||||
 | 
			
		||||
    private breakStack: { local: LocalId; block: BlockId }[] = [];
 | 
			
		||||
 | 
			
		||||
    public constructor(private ast: Ast.Stmt) {}
 | 
			
		||||
 | 
			
		||||
    public lower(): Fn {
 | 
			
		||||
        const stmt = this.ast;
 | 
			
		||||
        if (stmt.kind.type !== "fn") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const vtype = stmt.kind.vtype;
 | 
			
		||||
        if (vtype?.type !== "fn") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.locals.alloc(stmt.kind.vtype!);
 | 
			
		||||
        for (const param of stmt.kind.params) {
 | 
			
		||||
            const id = this.locals.alloc(param.vtype!);
 | 
			
		||||
            this.fnParamIndexLocals.set(param.index!, id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.pushBlock("entry");
 | 
			
		||||
        this.lowerBlockExpr(stmt.kind.body);
 | 
			
		||||
        this.pushBlock("exit");
 | 
			
		||||
 | 
			
		||||
        const locals = this.locals.finish();
 | 
			
		||||
        const blocks = this.blocks.values().toArray();
 | 
			
		||||
        return { stmt, locals, blocks };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerStmt(stmt: Ast.Stmt) {
 | 
			
		||||
        switch (stmt.kind.type) {
 | 
			
		||||
            case "error":
 | 
			
		||||
            case "mod_block":
 | 
			
		||||
            case "mod_file":
 | 
			
		||||
            case "mod":
 | 
			
		||||
                break;
 | 
			
		||||
            case "break": {
 | 
			
		||||
                const { local, block } = this.breakStack.at(-1)!;
 | 
			
		||||
                if (stmt.kind.expr) {
 | 
			
		||||
                    const val = this.lowerExpr(stmt.kind.expr);
 | 
			
		||||
                    this.addOp({ type: "assign", dst: local, src: val });
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.addOp({ type: "assign_null", dst: local });
 | 
			
		||||
                }
 | 
			
		||||
                this.setTer({ type: "jump", target: block });
 | 
			
		||||
                this.pushBlock();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            case "return":
 | 
			
		||||
                break;
 | 
			
		||||
            case "fn":
 | 
			
		||||
                // nothing
 | 
			
		||||
                return;
 | 
			
		||||
            case "let":
 | 
			
		||||
                this.lowerLetStmt(stmt);
 | 
			
		||||
                return;
 | 
			
		||||
            case "type_alias":
 | 
			
		||||
                break;
 | 
			
		||||
            case "assign":
 | 
			
		||||
                return this.lowerAssign(stmt);
 | 
			
		||||
            case "expr": {
 | 
			
		||||
                const val = this.lowerExpr(stmt.kind.expr);
 | 
			
		||||
                this.addOp({ type: "drop", val });
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        throw new Error(`statement type '${stmt.kind.type}' not covered`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerAssign(stmt: Ast.Stmt) {
 | 
			
		||||
        if (stmt.kind.type !== "assign") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        if (stmt.kind.assignType !== "=") {
 | 
			
		||||
            throw new Error("incomplete desugar");
 | 
			
		||||
        }
 | 
			
		||||
        const src = this.lowerExpr(stmt.kind.value);
 | 
			
		||||
        const s = stmt.kind.subject;
 | 
			
		||||
        switch (s.kind.type) {
 | 
			
		||||
            case "field": {
 | 
			
		||||
                const subject = this.lowerExpr(s.kind.subject);
 | 
			
		||||
                const ident = s.kind.ident;
 | 
			
		||||
                this.addOp({ type: "assign_field", subject, ident, src });
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            case "index": {
 | 
			
		||||
                const subject = this.lowerExpr(s.kind.subject);
 | 
			
		||||
                const index = this.lowerExpr(s.kind.value);
 | 
			
		||||
                this.addOp({ type: "assign_field", subject, index, src });
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            case "sym": {
 | 
			
		||||
                const sym = s.kind.sym;
 | 
			
		||||
                switch (sym.type) {
 | 
			
		||||
                    case "let": {
 | 
			
		||||
                        const dst = this.letStmtIdLocals.get(sym.stmt.id)!;
 | 
			
		||||
                        this.addOp({ type: "assign", dst, src });
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    case "fn_param": {
 | 
			
		||||
                        const dst = this.fnParamIndexLocals.get(
 | 
			
		||||
                            sym.param.index!,
 | 
			
		||||
                        )!;
 | 
			
		||||
                        this.addOp({ type: "assign", dst, src });
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                throw new Error(`symbol type '${sym.type}' not covered`);
 | 
			
		||||
            }
 | 
			
		||||
            default:
 | 
			
		||||
                throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerLetStmt(stmt: Ast.Stmt) {
 | 
			
		||||
        if (stmt.kind.type !== "let") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const src = this.lowerExpr(stmt.kind.value);
 | 
			
		||||
        const dst = this.locals.alloc(stmt.kind.param.vtype!);
 | 
			
		||||
        this.addOp({ type: "assign", dst, src });
 | 
			
		||||
        this.letStmtIdLocals.set(stmt.id, dst);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        switch (expr.kind.type) {
 | 
			
		||||
            case "error": {
 | 
			
		||||
                const dst = this.locals.alloc({ type: "error" });
 | 
			
		||||
                this.addOp({ type: "assign_error", dst });
 | 
			
		||||
                return dst;
 | 
			
		||||
            }
 | 
			
		||||
            case "null": {
 | 
			
		||||
                const dst = this.locals.alloc({ type: "null" });
 | 
			
		||||
                this.addOp({ type: "assign_null", dst });
 | 
			
		||||
                return dst;
 | 
			
		||||
            }
 | 
			
		||||
            case "bool": {
 | 
			
		||||
                const val = expr.kind.value;
 | 
			
		||||
                const dst = this.locals.alloc({ type: "bool" });
 | 
			
		||||
                this.addOp({ type: "assign_bool", dst, val });
 | 
			
		||||
                return dst;
 | 
			
		||||
            }
 | 
			
		||||
            case "int": {
 | 
			
		||||
                const val = expr.kind.value;
 | 
			
		||||
                const dst = this.locals.alloc({ type: "int" });
 | 
			
		||||
                this.addOp({ type: "assign_int", dst, val });
 | 
			
		||||
                return dst;
 | 
			
		||||
            }
 | 
			
		||||
            case "string": {
 | 
			
		||||
                const val = expr.kind.value;
 | 
			
		||||
                const dst = this.locals.alloc({ type: "string" });
 | 
			
		||||
                this.addOp({ type: "assign_string", dst, val });
 | 
			
		||||
                return dst;
 | 
			
		||||
            }
 | 
			
		||||
            case "ident":
 | 
			
		||||
                throw new Error("should've been resolved");
 | 
			
		||||
            case "sym":
 | 
			
		||||
                return this.lowerSymExpr(expr);
 | 
			
		||||
            case "group":
 | 
			
		||||
                return this.lowerExpr(expr.kind.expr);
 | 
			
		||||
            case "array":
 | 
			
		||||
                throw new Error("incomplete desugar");
 | 
			
		||||
            case "struct":
 | 
			
		||||
                throw new Error("incomplete desugar");
 | 
			
		||||
            case "field":
 | 
			
		||||
                return this.lowerFieldExpr(expr);
 | 
			
		||||
            case "index":
 | 
			
		||||
                return this.lowerIndexExpr(expr);
 | 
			
		||||
            case "call":
 | 
			
		||||
                return this.lowerCallExpr(expr);
 | 
			
		||||
            case "path":
 | 
			
		||||
            case "etype_args":
 | 
			
		||||
            case "unary":
 | 
			
		||||
                break;
 | 
			
		||||
            case "binary":
 | 
			
		||||
                return this.lowerBinaryExpr(expr);
 | 
			
		||||
            case "if":
 | 
			
		||||
                return this.lowerIfExpr(expr);
 | 
			
		||||
            case "loop":
 | 
			
		||||
                return this.lowerLoopExpr(expr);
 | 
			
		||||
            case "block":
 | 
			
		||||
                return this.lowerBlockExpr(expr);
 | 
			
		||||
            case "while":
 | 
			
		||||
            case "for_in":
 | 
			
		||||
            case "for":
 | 
			
		||||
                throw new Error("incomplete desugar");
 | 
			
		||||
        }
 | 
			
		||||
        throw new Error(`expression type '${expr.kind.type}' not covered`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerSymExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        if (expr.kind.type !== "sym") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const sym = expr.kind.sym;
 | 
			
		||||
        switch (sym.type) {
 | 
			
		||||
            case "let":
 | 
			
		||||
                return this.letStmtIdLocals.get(sym.stmt.id)!;
 | 
			
		||||
            case "let_static":
 | 
			
		||||
            case "type_alias":
 | 
			
		||||
                break;
 | 
			
		||||
            case "fn": {
 | 
			
		||||
                const stmt = sym.stmt;
 | 
			
		||||
                if (sym.stmt.kind.type !== "fn") {
 | 
			
		||||
                    throw new Error();
 | 
			
		||||
                }
 | 
			
		||||
                const dst = this.locals.alloc(sym.stmt.kind.vtype!);
 | 
			
		||||
                this.addOp({ type: "assign_fn", dst, stmt });
 | 
			
		||||
                return dst;
 | 
			
		||||
            }
 | 
			
		||||
            case "fn_param": {
 | 
			
		||||
                return this.fnParamIndexLocals.get(sym.param.index!)!;
 | 
			
		||||
            }
 | 
			
		||||
            case "closure":
 | 
			
		||||
            case "generic":
 | 
			
		||||
            case "mod":
 | 
			
		||||
        }
 | 
			
		||||
        throw new Error(`symbol type '${sym.type}' not covered`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerFieldExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        if (expr.kind.type !== "field") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const ident = expr.kind.ident;
 | 
			
		||||
        const subject = this.lowerExpr(expr.kind.subject);
 | 
			
		||||
 | 
			
		||||
        const subjectVType = expr.kind.subject.vtype!;
 | 
			
		||||
        if (subjectVType.type !== "struct") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const fieldVType = subjectVType.fields.find((field) =>
 | 
			
		||||
            field.ident === ident
 | 
			
		||||
        );
 | 
			
		||||
        if (fieldVType === undefined) {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const dst = this.locals.alloc(fieldVType.vtype);
 | 
			
		||||
        this.addOp({ type: "field", dst, subject, ident });
 | 
			
		||||
        return dst;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerIndexExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        if (expr.kind.type !== "index") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const subject = this.lowerExpr(expr.kind.subject);
 | 
			
		||||
        const index = this.lowerExpr(expr.kind.value);
 | 
			
		||||
 | 
			
		||||
        const dstVType = ((): VType => {
 | 
			
		||||
            const outer = expr.kind.subject.vtype!;
 | 
			
		||||
            if (outer.type === "array") {
 | 
			
		||||
                return outer.inner;
 | 
			
		||||
            }
 | 
			
		||||
            if (outer.type === "string") {
 | 
			
		||||
                return { type: "int" };
 | 
			
		||||
            }
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        })();
 | 
			
		||||
 | 
			
		||||
        const dst = this.locals.alloc(dstVType);
 | 
			
		||||
        this.addOp({ type: "index", dst, subject, index });
 | 
			
		||||
        return dst;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerCallExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        if (expr.kind.type !== "call") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const args = expr.kind.args.map((arg) => this.lowerExpr(arg));
 | 
			
		||||
 | 
			
		||||
        const subject = this.lowerExpr(expr.kind.subject);
 | 
			
		||||
 | 
			
		||||
        const subjectVType = expr.kind.subject.vtype!;
 | 
			
		||||
        if (subjectVType.type !== "fn") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const dst = this.locals.alloc(subjectVType.returnType);
 | 
			
		||||
        this.addOp({ type: "call_val", dst, subject, args });
 | 
			
		||||
        return dst;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerBinaryExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        if (expr.kind.type !== "binary") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const leftVType = expr.kind.left.vtype!;
 | 
			
		||||
        const rightVType = expr.kind.right.vtype!;
 | 
			
		||||
        if (!vtypesEqual(leftVType, rightVType)) {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        //const vtype = leftVType.type === "error" && rightVType || leftVType;
 | 
			
		||||
 | 
			
		||||
        const binaryType = expr.kind.binaryType;
 | 
			
		||||
        const left = this.lowerExpr(expr.kind.left);
 | 
			
		||||
        const right = this.lowerExpr(expr.kind.right);
 | 
			
		||||
 | 
			
		||||
        const dst = this.locals.alloc(expr.vtype!);
 | 
			
		||||
 | 
			
		||||
        this.addOp({ type: "binary", binaryType, dst, left, right });
 | 
			
		||||
        return dst;
 | 
			
		||||
 | 
			
		||||
        //throw new Error(
 | 
			
		||||
        //    `binary vtype '${vtypeToString(leftVType)}' not covered`,
 | 
			
		||||
        //);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerIfExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        if (expr.kind.type !== "if") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const condBlock = this.currentBlock();
 | 
			
		||||
        const cond = this.lowerExpr(expr.kind.cond);
 | 
			
		||||
        const end = this.reserveBlock();
 | 
			
		||||
 | 
			
		||||
        const val = this.locals.alloc(expr.vtype!);
 | 
			
		||||
 | 
			
		||||
        const truthy = this.pushBlock();
 | 
			
		||||
        const truthyVal = this.lowerExpr(expr.kind.truthy);
 | 
			
		||||
        this.addOp({ type: "assign", dst: val, src: truthyVal });
 | 
			
		||||
        this.setTer({ type: "jump", target: end });
 | 
			
		||||
 | 
			
		||||
        if (expr.kind.falsy) {
 | 
			
		||||
            const falsy = this.pushBlock();
 | 
			
		||||
            const falsyVal = this.lowerExpr(expr.kind.falsy);
 | 
			
		||||
            this.addOp({ type: "assign", dst: val, src: falsyVal });
 | 
			
		||||
            this.setTer({ type: "jump", target: end });
 | 
			
		||||
 | 
			
		||||
            this.setTerOn(condBlock, { type: "if", cond, truthy, falsy });
 | 
			
		||||
        } else {
 | 
			
		||||
            this.setTerOn(condBlock, { type: "if", cond, truthy, falsy: end });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return val;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerLoopExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        if (expr.kind.type !== "loop") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const val = this.locals.alloc(expr.vtype!);
 | 
			
		||||
        const breakBlock = this.reserveBlock();
 | 
			
		||||
        this.breakStack.push({ local: val, block: breakBlock });
 | 
			
		||||
 | 
			
		||||
        const body = this.pushBlock();
 | 
			
		||||
        this.setTer({ type: "jump", target: body });
 | 
			
		||||
 | 
			
		||||
        this.lowerExpr(expr.kind.body);
 | 
			
		||||
        this.setTer({ type: "jump", target: body });
 | 
			
		||||
 | 
			
		||||
        this.breakStack.pop();
 | 
			
		||||
 | 
			
		||||
        this.pushBlockWithId(breakBlock);
 | 
			
		||||
        return val;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private lowerBlockExpr(expr: Ast.Expr): LocalId {
 | 
			
		||||
        if (expr.kind.type !== "block") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (const stmt of expr.kind.stmts) {
 | 
			
		||||
            this.lowerStmt(stmt);
 | 
			
		||||
        }
 | 
			
		||||
        if (expr.kind.expr) {
 | 
			
		||||
            return this.lowerExpr(expr.kind.expr);
 | 
			
		||||
        } else {
 | 
			
		||||
            const local = this.locals.alloc({ type: "null" });
 | 
			
		||||
            this.addOp({ type: "assign_null", dst: local });
 | 
			
		||||
            return local;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private addOp(kind: OpKind) {
 | 
			
		||||
        this.blocks.get(this.currentBlockId)!.ops.push({ kind });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private addOpOn(blockId: BlockId, kind: OpKind) {
 | 
			
		||||
        this.blocks.get(blockId)!.ops.push({ kind });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private setTer(kind: TerKind) {
 | 
			
		||||
        this.blocks.get(this.currentBlockId)!.ter = { kind };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private setTerOn(blockId: BlockId, kind: TerKind) {
 | 
			
		||||
        this.blocks.get(blockId)!.ter = { kind };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private currentBlock(): BlockId {
 | 
			
		||||
        return this.currentBlockId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private reserveBlock(): BlockId {
 | 
			
		||||
        const id = this.blockIdCounter;
 | 
			
		||||
        this.blockIdCounter += 1;
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private pushBlock(label?: string): BlockId {
 | 
			
		||||
        const id = this.blockIdCounter;
 | 
			
		||||
        this.blockIdCounter += 1;
 | 
			
		||||
        const ter: Ter = { kind: { type: "error" } };
 | 
			
		||||
        this.blocks.set(id, { id, ops: [], ter, label });
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private pushBlockWithId(id: BlockId): BlockId {
 | 
			
		||||
        this.blockIdCounter += 1;
 | 
			
		||||
        const ter: Ter = { kind: { type: "error" } };
 | 
			
		||||
        this.blocks.set(id, { id, ops: [], ter });
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,152 +0,0 @@
 | 
			
		||||
import { BinaryType, Stmt } from "../ast.ts";
 | 
			
		||||
import { VType, vtypeToString } from "../vtype.ts";
 | 
			
		||||
 | 
			
		||||
export type Mir = {
 | 
			
		||||
    fns: Fn[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Fn = {
 | 
			
		||||
    stmt: Stmt;
 | 
			
		||||
    locals: Local[];
 | 
			
		||||
    blocks: Block[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type LocalId = number;
 | 
			
		||||
 | 
			
		||||
export type Local = {
 | 
			
		||||
    id: LocalId;
 | 
			
		||||
    vtype: VType;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type BlockId = number;
 | 
			
		||||
 | 
			
		||||
export type Block = {
 | 
			
		||||
    id: BlockId;
 | 
			
		||||
    ops: Op[];
 | 
			
		||||
    ter: Ter;
 | 
			
		||||
    label?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type Op = {
 | 
			
		||||
    kind: OpKind;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type L = LocalId;
 | 
			
		||||
 | 
			
		||||
export type OpKind =
 | 
			
		||||
    | { type: "error" }
 | 
			
		||||
    | { type: "return" }
 | 
			
		||||
    | { type: "drop"; val: L }
 | 
			
		||||
    | { type: "assign"; dst: L; src: L }
 | 
			
		||||
    | { type: "assign_error"; dst: L }
 | 
			
		||||
    | { type: "assign_null"; dst: L }
 | 
			
		||||
    | { type: "assign_bool"; dst: L; val: boolean }
 | 
			
		||||
    | { type: "assign_int"; dst: L; val: number }
 | 
			
		||||
    | { type: "assign_string"; dst: L; val: string }
 | 
			
		||||
    | { type: "assign_fn"; dst: L; stmt: Stmt }
 | 
			
		||||
    | { type: "field"; dst: L; subject: L; ident: string }
 | 
			
		||||
    | { type: "assign_field"; subject: L; ident: string; src: L }
 | 
			
		||||
    | { type: "index"; dst: L; subject: L; index: number }
 | 
			
		||||
    | { type: "assign_field"; subject: L; index: L; src: L }
 | 
			
		||||
    | { type: "call_val"; dst: L; subject: L; args: L[] }
 | 
			
		||||
    | { type: "binary"; binaryType: BinaryType; dst: L; left: L; right: L };
 | 
			
		||||
 | 
			
		||||
export type Ter = {
 | 
			
		||||
    kind: TerKind;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type TerKind =
 | 
			
		||||
    | { type: "error" }
 | 
			
		||||
    | { type: "jump"; target: BlockId }
 | 
			
		||||
    | { type: "if"; cond: L; truthy: BlockId; falsy: BlockId };
 | 
			
		||||
 | 
			
		||||
export function printMir(mir: Mir) {
 | 
			
		||||
    for (const fn of mir.fns) {
 | 
			
		||||
        const stmt = fn.stmt;
 | 
			
		||||
        if (stmt.kind.type !== "fn") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const name = stmt.kind.ident;
 | 
			
		||||
 | 
			
		||||
        const vtype = stmt.kind.vtype;
 | 
			
		||||
        if (vtype?.type !== "fn") {
 | 
			
		||||
            throw new Error();
 | 
			
		||||
        }
 | 
			
		||||
        const generics = vtype.genericParams
 | 
			
		||||
            ?.map(({ ident }) => `${ident}`).join(", ") ?? "";
 | 
			
		||||
        const params = vtype.params
 | 
			
		||||
            .map(({ vtype }, i) =>
 | 
			
		||||
                `_${fn.locals[i + 1].id}: ${vtypeToString(vtype)}`
 | 
			
		||||
            )
 | 
			
		||||
            .join(", ");
 | 
			
		||||
        const returnType = vtypeToString(vtype.returnType);
 | 
			
		||||
        console.log(`${name}${generics}(${params}) -> ${returnType}:`);
 | 
			
		||||
        for (const { id, vtype } of fn.locals) {
 | 
			
		||||
            console.log(`    let _${id}: ${vtypeToString(vtype)};`);
 | 
			
		||||
        }
 | 
			
		||||
        for (const block of fn.blocks) {
 | 
			
		||||
            console.log(`.${block.label ?? block.id}:`);
 | 
			
		||||
            for (const op of block.ops) {
 | 
			
		||||
                const k = op.kind;
 | 
			
		||||
                switch (k.type) {
 | 
			
		||||
                    case "error":
 | 
			
		||||
                        console.log(`    <error>;`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "return":
 | 
			
		||||
                        console.log(`    return;`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "drop":
 | 
			
		||||
                        console.log(`    drop _${k.val};`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "assign":
 | 
			
		||||
                        console.log(`    _${k.dst} = _${k.src};`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "assign_error":
 | 
			
		||||
                        console.log(`    _${k.dst} = <error>;`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "assign_null":
 | 
			
		||||
                        console.log(`    _${k.dst} = null;`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "assign_bool":
 | 
			
		||||
                        console.log(`    _${k.dst} = ${k.val};`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "assign_int":
 | 
			
		||||
                        console.log(`    _${k.dst} = ${k.val};`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "assign_string":
 | 
			
		||||
                        console.log(`    _${k.dst} = "${k.val}";`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "assign_fn": {
 | 
			
		||||
                        const stmt = k.stmt;
 | 
			
		||||
                        if (stmt.kind.type !== "fn") {
 | 
			
		||||
                            throw new Error();
 | 
			
		||||
                        }
 | 
			
		||||
                        console.log(`    _${k.dst} = ${stmt.kind.ident};`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    case "field":
 | 
			
		||||
                        console.log(
 | 
			
		||||
                            `    _${k.dst} = _${k.subject}.${k.ident};`,
 | 
			
		||||
                        );
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "index":
 | 
			
		||||
                        console.log(
 | 
			
		||||
                            `    _${k.dst} = _${k.subject}[_${k.index}];`,
 | 
			
		||||
                        );
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "call_val": {
 | 
			
		||||
                        const args = k.args.map((arg) => `_${arg}`).join(", ");
 | 
			
		||||
                        console.log(`    _${k.dst} = _${k.subject}(${args});`);
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                    case "binary": {
 | 
			
		||||
                        console.log(
 | 
			
		||||
                            `    _${k.dst} = _${k.left} ${k.binaryType} _${k.right};`,
 | 
			
		||||
                        );
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -181,7 +181,7 @@ function vtypeNameGenPart(vtype: VType): string {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class AllFnsCollector implements AstVisitor {
 | 
			
		||||
class AllFnsCollector implements AstVisitor {
 | 
			
		||||
    private allFns = new Map<number, Stmt>();
 | 
			
		||||
 | 
			
		||||
    public collect(ast: Stmt[]): Map<number, Stmt> {
 | 
			
		||||
 | 
			
		||||
@ -330,15 +330,16 @@ export class Parser {
 | 
			
		||||
        return this.parseDelimitedList(this.parseETypeParam, ">", ",");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseETypeParam(index: number): Res<GenericParam> {
 | 
			
		||||
    private veryTemporaryETypeParamIdCounter = 0;
 | 
			
		||||
 | 
			
		||||
    private parseETypeParam(): Res<GenericParam> {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
        if (this.test("ident")) {
 | 
			
		||||
            const ident = this.current().identValue!;
 | 
			
		||||
            this.step();
 | 
			
		||||
            return {
 | 
			
		||||
                ok: true,
 | 
			
		||||
                value: this.astCreator.genericParam({ index, ident, pos }),
 | 
			
		||||
            };
 | 
			
		||||
            const id = this.veryTemporaryETypeParamIdCounter;
 | 
			
		||||
            this.veryTemporaryETypeParamIdCounter += 1;
 | 
			
		||||
            return { ok: true, value: { id, ident, pos } };
 | 
			
		||||
        }
 | 
			
		||||
        this.report("expected generic parameter");
 | 
			
		||||
        return { ok: false };
 | 
			
		||||
@ -349,7 +350,7 @@ export class Parser {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseDelimitedList<T>(
 | 
			
		||||
        parseElem: (this: Parser, index: number) => Res<T>,
 | 
			
		||||
        parseElem: (this: Parser) => Res<T>,
 | 
			
		||||
        endToken: string,
 | 
			
		||||
        delimiter: string,
 | 
			
		||||
    ): T[] {
 | 
			
		||||
@ -358,9 +359,8 @@ export class Parser {
 | 
			
		||||
            this.step();
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
        let i = 0;
 | 
			
		||||
        const elems: T[] = [];
 | 
			
		||||
        const elemRes = parseElem.call(this, i);
 | 
			
		||||
        const elemRes = parseElem.call(this);
 | 
			
		||||
        if (!elemRes.ok) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
@ -370,7 +370,7 @@ export class Parser {
 | 
			
		||||
            if (this.test(endToken)) {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            const elemRes = parseElem.call(this, i);
 | 
			
		||||
            const elemRes = parseElem.call(this);
 | 
			
		||||
            if (!elemRes.ok) {
 | 
			
		||||
                return [];
 | 
			
		||||
            }
 | 
			
		||||
@ -384,7 +384,7 @@ export class Parser {
 | 
			
		||||
        return elems;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private parseParam(index?: number): Res<Param> {
 | 
			
		||||
    private parseParam(): Res<Param> {
 | 
			
		||||
        const pos = this.pos();
 | 
			
		||||
        if (this.test("ident")) {
 | 
			
		||||
            const ident = this.current().identValue!;
 | 
			
		||||
@ -392,15 +392,9 @@ export class Parser {
 | 
			
		||||
            if (this.test(":")) {
 | 
			
		||||
                this.step();
 | 
			
		||||
                const etype = this.parseEType();
 | 
			
		||||
                return {
 | 
			
		||||
                    ok: true,
 | 
			
		||||
                    value: this.astCreator.param({ index, ident, etype, pos }),
 | 
			
		||||
                };
 | 
			
		||||
                return { ok: true, value: { ident, etype, pos } };
 | 
			
		||||
            }
 | 
			
		||||
            return {
 | 
			
		||||
                ok: true,
 | 
			
		||||
                value: this.astCreator.param({ index, ident, pos }),
 | 
			
		||||
            };
 | 
			
		||||
            return { ok: true, value: { ident, pos } };
 | 
			
		||||
        }
 | 
			
		||||
        this.report("expected param");
 | 
			
		||||
        return { ok: false };
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user