Compare commits

..

No commits in common. "916c2376a0723a8777634649cd3096931944bcdc" and "cbb5a02bd9e31a1645e2e9d9f57d246ee75818fb" have entirely different histories.

5 changed files with 166 additions and 347 deletions

View File

@ -5,121 +5,38 @@ export type Ins = Ops | number;
export type Ops = typeof Ops; export type Ops = typeof Ops;
export const Ops = { export const Ops = {
Nop: 0x00, Nop: 0,
PushNull: 0x01, PushNull: 1,
PushInt: 0x02, PushInt: 2,
PushBool: 0x03, PushBool: 3,
PushString: 0x04, PushString: 4,
PushPtr: 0x05, PushPtr: 5,
Pop: 0x06, Pop: 6,
ReserveStatic: 0x07, LoadLocal: 7,
LoadStatic: 0x08, StoreLocal: 8,
StoreStatic: 0x09, Call: 9,
LoadLocal: 0x0a, Return: 10,
StoreLocal: 0x0b, Jump: 11,
Call: 0x0c, JumpIfTrue: 12,
Return: 0x0d, Add: 13,
Jump: 0x0e, Subtract: 14,
JumpIfTrue: 0x0f, Multiply: 15,
Builtin: 0x10, Divide: 16,
Add: 0x20, Remainder: 17,
Subtract: 0x21, Equal: 18,
Multiply: 0x22, LessThan: 19,
Divide: 0x23, And: 20,
Remainder: 0x24, Or: 21,
Equal: 0x25, Xor: 22,
LessThan: 0x26, Not: 23,
And: 0x27, SourceMap: 24,
Or: 0x28, Builtin: 25,
Xor: 0x29, ReserveStatics: 26,
Not: 0x2a, LoadStatic: 27,
SourceMap: 0x30, StoreStatic: 28,
} as const; } as const;
export type Builtins = typeof Builtins; export type Builtins = typeof Builtins;
export const Builtins = { export const Builtins = {
StringConcat: 0x10, StringAdd: 0,
StringEqual: 0x11,
ArraySet: 0x20,
StructSet: 0x30,
} as const; } as const;
export function opToString(op: number): string {
switch (op) {
case Ops.Nop:
return "Nop";
case Ops.PushNull:
return "PushNull";
case Ops.PushInt:
return "PushInt";
case Ops.PushBool:
return "PushBool";
case Ops.PushString:
return "PushString";
case Ops.PushPtr:
return "PushPtr";
case Ops.Pop:
return "Pop";
case Ops.ReserveStatic:
return "ReserveStatic";
case Ops.LoadStatic:
return "LoadStatic";
case Ops.StoreStatic:
return "StoreStatic";
case Ops.LoadLocal:
return "LoadLocal";
case Ops.StoreLocal:
return "StoreLocal";
case Ops.Call:
return "Call";
case Ops.Return:
return "Return";
case Ops.Jump:
return "Jump";
case Ops.JumpIfTrue:
return "JumpIfTrue";
case Ops.Builtin:
return "Builtin";
case Ops.Add:
return "Add";
case Ops.Subtract:
return "Subtract";
case Ops.Multiply:
return "Multiply";
case Ops.Divide:
return "Divide";
case Ops.Remainder:
return "Remainder";
case Ops.Equal:
return "Equal";
case Ops.LessThan:
return "LessThan";
case Ops.And:
return "And";
case Ops.Or:
return "Or";
case Ops.Xor:
return "Xor";
case Ops.Not:
return "Not";
case Ops.SourceMap:
return "SourceMap";
default:
return `<unknown Op ${op}>`;
}
}
export function builtinToString(builtin: number): string {
switch (builtin) {
case Builtins.StringConcat:
return "StringConcat";
case Builtins.StringEqual:
return "StringEqual";
case Builtins.ArraySet:
return "ArraySet";
case Builtins.StructSet:
return "StructSet";
default:
return `<unknown Builtin ${builtin}>`;
}
}

View File

@ -1,113 +0,0 @@
import { Ops, opToString } from "./arch.ts";
export type Line = { labels?: string[]; ins: Ins };
export type Ins = Lit[];
export type Label = { label: string };
export type Lit = number | string | boolean | Label;
export class Assembler {
private lines: Line[] = [];
private addedLabels: string[] = [];
private labelCounter = 0;
public add(...ins: Ins): Assembler {
if (this.addedLabels.length > 0) {
this.lines.push({ ins, labels: this.addedLabels });
this.addedLabels = [];
return this;
}
this.lines.push({ ins });
return this;
}
public concat(assembler: Assembler) {
if (assembler.lines.length < 0) {
return;
}
if (assembler.lines[0].labels !== undefined) {
this.addedLabels.push(...assembler.lines[0].labels);
}
this.add(...assembler.lines[0].ins);
this.lines.push(...assembler.lines.slice(1));
}
public makeLabel(): Label {
return { label: `.L${(this.labelCounter++).toString()}` };
}
public setLabel({ label }: Label) {
this.addedLabels.push(label);
}
public assemble(): number[] {
let ip = 0;
const output: number[] = [];
const locs: { [key: string]: number } = {};
const refs: { [key: number]: string } = {};
for (const line of this.lines) {
for (const label of line.labels ?? []) {
locs[label] = ip;
}
for (const lit of line.ins as Lit[]) {
if (typeof lit === "number") {
output.push(lit);
ip += 1;
} else if (typeof lit === "boolean") {
output.push(lit ? 1 : 0);
ip += 1;
} else if (typeof lit === "string") {
for (let i = 0; i < lit.length; ++i) {
output.push(lit.charCodeAt(i));
ip += 1;
}
} else {
output.push(0);
refs[ip] = lit.label;
ip += 1;
}
}
}
for (let i = 0; i < output.length; ++i) {
if (!(i in refs)) {
continue;
}
if (!(refs[i] in locs)) {
console.error(
`Assembler: label '${refs[i]}' used at ${i} not defined`,
);
continue;
}
output[i] = locs[refs[i]];
}
return output;
}
public printProgram() {
for (const line of this.lines) {
for (const label of line.labels ?? []) {
console.log(`${label}:`);
}
const op = opToString(line.ins[0] as number)
.padEnd(13, " ");
const args = (line.ins.slice(1) as Lit[]).map((lit) => {
if (typeof lit === "number") {
return lit;
} else if (typeof lit === "boolean") {
return lit.toString();
} else if (typeof lit === "string") {
return '"' +
lit.replaceAll("\\", "\\\\").replaceAll("\0", "\\0")
.replaceAll("\n", "\\n").replaceAll("\t", "\\t")
.replaceAll("\r", "\\r") +
'"';
} else {
return lit.label;
}
}).join(", ");
console.log(` ${op} ${args}`);
}
}
}

View File

@ -2,14 +2,13 @@ import { Builtins } from "./arch.ts";
import { BinaryType, Expr, Stmt } from "./ast.ts"; import { BinaryType, Expr, Stmt } from "./ast.ts";
import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts"; import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts";
import { Ops } from "./mod.ts"; import { Ops } from "./mod.ts";
import { Assembler, Label } from "./assembler.ts"; import { Assembler } from "./program_builder.ts";
import { VType, vtypeToString } from "./vtype.ts"; import { VType, vtypeToString } from "./vtype.ts";
export class Lowerer { export class Lowerer {
private program = new Assembler(); private program = new Assembler();
private locals: Locals = new LocalsFnRoot(); private locals: Locals = new LocalsFnRoot();
private fnStmtIdLabelMap: { [key: number]: string } = {}; private fnStmtIdAddrMap: { [key: number]: number } = {};
private breakStack: Label[] = [];
public lower(stmts: Stmt[]) { public lower(stmts: Stmt[]) {
for (const stmt of stmts) { for (const stmt of stmts) {
@ -38,9 +37,7 @@ export class Lowerer {
private lowerStmt(stmt: Stmt) { private lowerStmt(stmt: Stmt) {
switch (stmt.kind.type) { switch (stmt.kind.type) {
case "error": case "error":
break;
case "break": case "break":
return this.lowerBreakStmt(stmt);
case "return": case "return":
break; break;
case "fn": case "fn":
@ -48,63 +45,19 @@ export class Lowerer {
case "let": case "let":
return this.lowerLetStmt(stmt); return this.lowerLetStmt(stmt);
case "assign": case "assign":
return this.lowerAssignStmt(stmt); break;
case "expr": case "expr":
this.lowerExpr(stmt.kind.expr); this.lowerExpr(stmt.kind.expr);
this.program.add(Ops.Pop); this.program.push(Ops.Pop);
return; return;
} }
throw new Error(`unhandled stmt '${stmt.kind.type}'`); 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.value);
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 lowerBreakStmt(stmt: Stmt) {
if (stmt.kind.type !== "break") {
throw new Error();
}
if (stmt.kind.expr) {
this.lowerExpr(stmt.kind.expr);
}
this.program.add(Ops.Jump, this.breakStack.at(-1)!);
}
private lowerFnStmt(stmt: Stmt) { private lowerFnStmt(stmt: Stmt) {
if (stmt.kind.type !== "fn") { if (stmt.kind.type !== "fn") {
throw new Error(); throw new Error();
} }
const label = `${stmt.kind.ident}_${stmt.id}`;
this.fnStmtIdLabelMap[stmt.id] = label;
this.program.setLabel({ label });
const outerLocals = this.locals; const outerLocals = this.locals;
this.locals = new LocalsFnRoot(outerLocals); this.locals = new LocalsFnRoot(outerLocals);
const outerProgram = this.program; const outerProgram = this.program;
@ -112,13 +65,13 @@ export class Lowerer {
for (const { ident } of stmt.kind.params) { for (const { ident } of stmt.kind.params) {
this.locals.allocSym(ident); this.locals.allocSym(ident);
this.program.add( this.program.push(
Ops.StoreLocal, Ops.StoreLocal,
this.locals.symId(ident), this.locals.symId(ident),
); );
} }
this.lowerExpr(stmt.kind.body); this.lowerExpr(stmt.kind.body);
this.program.add(Ops.Return); this.program.push(Ops.Return);
this.locals = outerLocals; this.locals = outerLocals;
outerProgram.concat(this.program); outerProgram.concat(this.program);
@ -130,11 +83,9 @@ export class Lowerer {
throw new Error(); throw new Error();
} }
this.lowerExpr(stmt.kind.value); this.lowerExpr(stmt.kind.value);
this.locals.allocSym(stmt.kind.param.ident); this.locals.allocSym(stmt.kind.param.ident),
this.program.add( this.program.push(Ops.StoreLocal);
Ops.StoreLocal, this.program.push(this.locals.symId(stmt.kind.param.ident));
this.locals.symId(stmt.kind.param.ident),
);
} }
private lowerExpr(expr: Expr) { private lowerExpr(expr: Expr) {
@ -168,7 +119,7 @@ export class Lowerer {
case "if": case "if":
return this.lowerIfExpr(expr); return this.lowerIfExpr(expr);
case "loop": case "loop":
return this.lowerLoopExpr(expr); break;
case "block": case "block":
return this.lowerBlockExpr(expr); return this.lowerBlockExpr(expr);
} }
@ -180,22 +131,22 @@ export class Lowerer {
throw new Error(); throw new Error();
} }
if (expr.kind.sym.type === "let") { if (expr.kind.sym.type === "let") {
this.program.add( this.program.push(
Ops.LoadLocal, Ops.LoadLocal,
this.locals.symId(expr.kind.ident), this.locals.symId(expr.kind.ident),
); );
return; return;
} }
if (expr.kind.sym.type === "fn_param") { if (expr.kind.sym.type === "fn_param") {
this.program.add( this.program.push(
Ops.LoadLocal, Ops.LoadLocal,
this.locals.symId(expr.kind.ident), this.locals.symId(expr.kind.ident),
); );
return; return;
} }
if (expr.kind.sym.type === "fn") { if (expr.kind.sym.type === "fn") {
const label = this.fnStmtIdLabelMap[expr.kind.sym.stmt.id]; const addr = this.fnStmtIdAddrMap[expr.kind.sym.stmt.id];
this.program.add(Ops.PushPtr, label); this.program.push(Ops.PushPtr, addr);
return; return;
} }
throw new Error(`unhandled sym type '${expr.kind.sym.type}'`); throw new Error(`unhandled sym type '${expr.kind.sym.type}'`);
@ -205,14 +156,17 @@ export class Lowerer {
if (expr.kind.type !== "int") { if (expr.kind.type !== "int") {
throw new Error(); throw new Error();
} }
this.program.add(Ops.PushInt, expr.kind.value); this.program.push(Ops.PushInt, expr.kind.value);
} }
private lowerStringExpr(expr: Expr) { private lowerStringExpr(expr: Expr) {
if (expr.kind.type !== "string") { if (expr.kind.type !== "string") {
throw new Error(); throw new Error();
} }
this.program.add(Ops.PushString, expr.kind.value); this.program.push(Ops.PushString);
for (let i = 0; i < expr.kind.value.length; ++i) {
this.program.push(expr.kind.value.charCodeAt(i));
}
} }
private lowerBinaryExpr(expr: Expr) { private lowerBinaryExpr(expr: Expr) {
@ -221,43 +175,34 @@ export class Lowerer {
} }
this.lowerExpr(expr.kind.left); this.lowerExpr(expr.kind.left);
this.lowerExpr(expr.kind.right); this.lowerExpr(expr.kind.right);
const vtype = expr.kind.left.vtype!; if (expr.vtype!.type === "int") {
if (vtype.type === "int") {
switch (expr.kind.binaryType) { switch (expr.kind.binaryType) {
case "+": case "+":
this.program.add(Ops.Add); this.program.push(Ops.Add);
return; return;
case "*": case "*":
this.program.add(Ops.Multiply); this.program.push(Ops.Multiply);
return; return;
case "==": case "==":
this.program.add(Ops.Equal); case "-":
return; case "/":
case "!=":
case "<":
case ">":
case "<=":
case ">=": case ">=":
this.program.add(Ops.LessThan); case "or":
this.program.add(Ops.Not); case "and":
return;
default:
} }
} }
if (vtype.type === "string") { if (expr.vtype!.type === "string") {
if (expr.kind.binaryType === "+") { if (expr.kind.binaryType === "+") {
this.program.add(Ops.Builtin, Builtins.StringConcat); this.program.push(Ops.Builtin, Builtins.StringAdd);
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; return;
} }
} }
throw new Error( throw new Error(
`unhandled binaryType` + `unhandled binaryType` +
` '${vtypeToString(expr.vtype!)}' aka. ` +
` '${vtypeToString(expr.kind.left.vtype!)}'` + ` '${vtypeToString(expr.kind.left.vtype!)}'` +
` ${expr.kind.binaryType}` + ` ${expr.kind.binaryType}` +
` '${vtypeToString(expr.kind.left.vtype!)}'`, ` '${vtypeToString(expr.kind.left.vtype!)}'`,
@ -284,42 +229,21 @@ export class Lowerer {
this.lowerExpr(expr.kind.cond); this.lowerExpr(expr.kind.cond);
this.program.add(Ops.Not); this.program.push(Ops.Not, Ops.JumpIfTrue);
this.program.add(Ops.JumpIfTrue, falseLabel); this.program.addRef(falseLabel);
this.lowerExpr(expr.kind.truthy); this.lowerExpr(expr.kind.truthy);
this.program.add(Ops.Jump, doneLabel); this.program.push(Ops.Jump);
this.program.addRef(doneLabel);
this.program.setLabel(falseLabel); this.program.setLabel(falseLabel);
if (expr.kind.falsy) {
this.lowerExpr(expr.kind.falsy!); this.lowerExpr(expr.kind.falsy!);
}
this.program.setLabel(doneLabel); this.program.setLabel(doneLabel);
} }
private lowerLoopExpr(expr: Expr) {
if (expr.kind.type !== "loop") {
throw new Error();
}
const contineLabel = this.program.makeLabel();
const breakLabel = this.program.makeLabel();
this.breakStack.push(breakLabel);
this.program.setLabel(contineLabel);
this.lowerExpr(expr.kind.body);
this.program.add(Ops.Jump, breakLabel);
this.program.setLabel(breakLabel);
if (expr.vtype!.type === "null") {
this.program.add(Ops.PushNull);
}
this.breakStack.pop();
}
private lowerBlockExpr(expr: Expr) { private lowerBlockExpr(expr: Expr) {
if (expr.kind.type !== "block") { if (expr.kind.type !== "block") {
throw new Error(); throw new Error();
@ -332,12 +256,8 @@ export class Lowerer {
if (expr.kind.expr) { if (expr.kind.expr) {
this.lowerExpr(expr.kind.expr); this.lowerExpr(expr.kind.expr);
} else { } else {
this.program.add(Ops.PushNull); this.program.push(Ops.PushNull);
} }
this.locals = outerLocals; this.locals = outerLocals;
} }
public printProgram() {
this.program.printProgram();
}
} }

View File

@ -15,7 +15,5 @@ new Checker().check(ast);
// console.log(JSON.stringify(ast, null, 4)) // console.log(JSON.stringify(ast, null, 4))
const lowerer = new Lowerer(); const lowerer = new Lowerer();
lowerer.lower(ast); lowerer.lower(ast);
lowerer.printProgram();
const program = lowerer.finish(); const program = lowerer.finish();
//console.log(JSON.stringify(program, null, 4)); console.log(JSON.stringify(program, null, 4));
console.log(JSON.stringify(program));

View File

@ -0,0 +1,97 @@
import { Ops } from "./arch.ts";
export type Line = {
labels?: number[];
} & LineKind;
export type Label = { label: number };
export type LineKind =
| { type: "op"; op: number }
| { type: "lit"; val: number }
| { type: "ref"; label: number };
export class Assembler {
private lines: Line[] = [];
private labelCounter = 0;
public push(...values: number[]) {
for (const value of values) {
this.addLit(value);
}
}
public addOp(op: number): Assembler {
this.lines.push({ type: "op", op });
return this;
}
public addLit(val: number): Assembler {
this.lines.push({ type: "lit", val });
return this;
}
public addRef({ label }: Label): Assembler {
this.lines.push({ type: "ref", label });
return this;
}
public setLabel({ label }: Label): Assembler {
const line = this.lines.at(-1);
if (line === undefined) {
return this;
}
if (line.labels === undefined) {
line.labels = [];
}
line.labels.push(label);
return this;
}
public makeLabel(): Label {
const label = this.labelCounter;
this.labelCounter += 1;
return { label };
}
public concat(assembler: Assembler) {
this.lines.push(...assembler.lines);
}
public assemble(): number[] {
let ip = 0;
const output: number[] = [];
const locs: { [key: number]: number } = {};
const refs: { [key: number]: number } = {};
for (const line of this.lines) {
switch (line.type) {
case "op":
output.push(line.op);
ip += 1;
break;
case "lit":
output.push(line.val);
ip += 1;
break;
case "ref":
output.push(0);
refs[ip] = line.label;
ip += 1;
break;
}
}
for (let i = 0; i < output.length; ++i) {
if (!(i in refs)) {
continue;
}
if (!(refs[i] in locs)) {
console.error(
`Assembler: label '${refs[i]}' used at ${i} not defined`,
);
continue;
}
output[i] = locs[refs[i]];
}
return output;
}
}