Compare commits

...

3 Commits

Author SHA1 Message Date
76fff577b1 add middle 2025-01-02 09:14:24 +01:00
b402e981e7 desugar param 2025-01-02 06:46:51 +01:00
b6e6234ff6 param ids, indices 2025-01-02 04:40:09 +01:00
10 changed files with 694 additions and 36 deletions

View File

@ -111,6 +111,8 @@ export type Field = {
};
export type Param = {
id: number;
index?: number;
ident: string;
etype?: EType;
pos: Pos;
@ -156,6 +158,7 @@ export type ETypeKind =
export type GenericParam = {
id: number;
index: number;
ident: string;
pos: Pos;
vtype?: VType;
@ -171,21 +174,34 @@ export class AstCreator {
private nextNodeId = 0;
public stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt {
const id = this.nextNodeId;
this.nextNodeId += 1;
const id = this.genId();
return { kind, pos, details, id };
}
public expr(kind: ExprKind, pos: Pos): Expr {
const id = this.nextNodeId;
this.nextNodeId += 1;
const id = this.genId();
return { kind, pos, id };
}
public etype(kind: ETypeKind, pos: Pos): EType {
const id = this.genId();
return { kind, pos, id };
}
public param(val: Omit<Param, "id">): Param {
const id = this.genId();
return { ...val, id };
}
public genericParam(val: Omit<GenericParam, "id">): GenericParam {
const id = this.genId();
return { ...val, id };
}
private genId(): number {
const id = this.nextNodeId;
this.nextNodeId += 1;
return { kind, pos, id };
return id;
}
}

View File

@ -10,11 +10,13 @@ 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 * as path from "jsr:@std/path";
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";
export type CompileResult = {
program: number[];
fnNames: FnNamesMap;
@ -29,28 +31,32 @@ export class Compiler {
}
public async compile(): Promise<CompileResult> {
const mod = new ModTree(
const { ast } = new ModTree(
this.startFilePath,
this.astCreator,
this.reporter,
).resolve();
new SpecialLoopDesugarer(this.astCreator).desugar(mod.ast);
new ArrayLiteralDesugarer(this.astCreator).desugar(mod.ast);
new StructLiteralDesugarer(this.astCreator).desugar(mod.ast);
new SpecialLoopDesugarer(this.astCreator).desugar(ast);
new ArrayLiteralDesugarer(this.astCreator).desugar(ast);
new StructLiteralDesugarer(this.astCreator).desugar(ast);
new Resolver(this.reporter).resolve(mod.ast);
new Resolver(this.reporter).resolve(ast);
new CompoundAssignDesugarer(this.astCreator).desugar(mod.ast);
new CompoundAssignDesugarer(this.astCreator).desugar(ast);
new Checker(this.reporter).check(mod.ast);
new Checker(this.reporter).check(ast);
const mir = new AstLowerer(ast).lower();
printMir(mir);
if (this.reporter.errorOccured()) {
console.error("Errors occurred, stopping compilation.");
Deno.exit(1);
}
const { monoFns, callMap } = new Monomorphizer(mod.ast).monomorphize();
const { monoFns, callMap } = new Monomorphizer(ast).monomorphize();
const lastPos = await lastPosInTextFile(this.startFilePath);

View File

@ -49,10 +49,10 @@ export class ArrayLiteralDesugarer implements AstVisitor {
stmts: [
Stmt({
type: "let",
param: {
param: this.astCreator.param({
ident: "::value",
pos: npos,
},
}),
value: Expr({
type: "call",
subject: Expr({

View File

@ -70,12 +70,18 @@ export class SpecialLoopDesugarer implements AstVisitor {
stmts: [
Stmt({
type: "let",
param: { ident: "::values", pos: npos },
param: this.astCreator.param({
ident: "::values",
pos: npos,
}),
value: expr.kind.value,
}),
Stmt({
type: "let",
param: { ident: "::length", pos: npos },
param: this.astCreator.param({
ident: "::length",
pos: npos,
}),
value: Expr({
type: "call",
subject: Expr({
@ -96,7 +102,10 @@ export class SpecialLoopDesugarer implements AstVisitor {
}),
Stmt({
type: "let",
param: { ident: "::index", pos: npos },
param: this.astCreator.param({
ident: "::index",
pos: npos,
}),
value: Expr({ type: "int", value: 0 }),
}, expr.pos),
Stmt({

View File

@ -52,10 +52,10 @@ export class StructLiteralDesugarer implements AstVisitor {
stmts: [
Stmt({
type: "let",
param: {
param: this.astCreator.param({
ident: "::value",
pos: npos,
},
}),
value: Expr({
type: "call",
subject: Expr({

View File

@ -0,0 +1,469 @@
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;
}
}

152
compiler/middle/mir.ts Normal file
View File

@ -0,0 +1,152 @@
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;
}
}
}
}
}
}

View File

@ -181,7 +181,7 @@ function vtypeNameGenPart(vtype: VType): string {
}
}
class AllFnsCollector implements AstVisitor {
export class AllFnsCollector implements AstVisitor {
private allFns = new Map<number, Stmt>();
public collect(ast: Stmt[]): Map<number, Stmt> {

View File

@ -330,16 +330,15 @@ export class Parser {
return this.parseDelimitedList(this.parseETypeParam, ">", ",");
}
private veryTemporaryETypeParamIdCounter = 0;
private parseETypeParam(): Res<GenericParam> {
private parseETypeParam(index: number): Res<GenericParam> {
const pos = this.pos();
if (this.test("ident")) {
const ident = this.current().identValue!;
this.step();
const id = this.veryTemporaryETypeParamIdCounter;
this.veryTemporaryETypeParamIdCounter += 1;
return { ok: true, value: { id, ident, pos } };
return {
ok: true,
value: this.astCreator.genericParam({ index, ident, pos }),
};
}
this.report("expected generic parameter");
return { ok: false };
@ -350,7 +349,7 @@ export class Parser {
}
private parseDelimitedList<T>(
parseElem: (this: Parser) => Res<T>,
parseElem: (this: Parser, index: number) => Res<T>,
endToken: string,
delimiter: string,
): T[] {
@ -359,8 +358,9 @@ export class Parser {
this.step();
return [];
}
let i = 0;
const elems: T[] = [];
const elemRes = parseElem.call(this);
const elemRes = parseElem.call(this, i);
if (!elemRes.ok) {
return [];
}
@ -370,7 +370,7 @@ export class Parser {
if (this.test(endToken)) {
break;
}
const elemRes = parseElem.call(this);
const elemRes = parseElem.call(this, i);
if (!elemRes.ok) {
return [];
}
@ -384,7 +384,7 @@ export class Parser {
return elems;
}
private parseParam(): Res<Param> {
private parseParam(index?: number): Res<Param> {
const pos = this.pos();
if (this.test("ident")) {
const ident = this.current().identValue!;
@ -392,9 +392,15 @@ export class Parser {
if (this.test(":")) {
this.step();
const etype = this.parseEType();
return { ok: true, value: { ident, etype, pos } };
return {
ok: true,
value: this.astCreator.param({ index, ident, etype, pos }),
};
}
return { ok: true, value: { ident, pos } };
return {
ok: true,
value: this.astCreator.param({ index, ident, pos }),
};
}
this.report("expected param");
return { ok: false };

View File

@ -130,7 +130,7 @@ export function vtypeToString(vtype: VType): string {
`${param.ident}: ${vtypeToString(param.vtype)}`
)
.join(", ");
return `fn (${paramString}) -> ${vtypeToString(vtype.returnType)}`;
return `fn(${paramString}) -> ${vtypeToString(vtype.returnType)}`;
}
if (vtype.type === "generic") {
return `generic`;