interpreter

This commit is contained in:
sfja 2026-04-10 16:23:49 +02:00
parent 1298b69388
commit 7741896a98
3 changed files with 203 additions and 32 deletions

View File

@ -1,5 +1,5 @@
(def nand (a b vin) (r) ( (def nand (a b) (r) (
(let (a_and_b) (relay_default_off a b)) (let (a_and_b) (relay_default_off a b))
(set r (relay_default_on a_and_b vin)) (set r (relay_default_on a_and_b vin))
)) ))
@ -45,3 +45,4 @@
(set carry_out e1) (set carry_out e1)
)) ))

View File

@ -26,7 +26,7 @@ export function tokenize(text: string): Tok[] {
)[0] )[0]
.map(({ tok, line }) => ({ ty: tok, text: tok, line })) .map(({ tok, line }) => ({ ty: tok, text: tok, line }))
.map((tok) => { .map((tok) => {
if (/^[a-zA-Z_][a-zA-Z_0-9]*$/.test(tok.text)) { if (/^[a-zA-Z_0-9]+$/.test(tok.text)) {
tok.ty = "ident"; tok.ty = "ident";
} }
return tok; return tok;
@ -263,6 +263,8 @@ class DefResolver {
private syms = new Map<string, Sym>([ private syms = new Map<string, Sym>([
["relay_default_off", { tag: "Builtin" }], ["relay_default_off", { tag: "Builtin" }],
["relay_default_on", { tag: "Builtin" }], ["relay_default_on", { tag: "Builtin" }],
["gnd", { tag: "Builtin" }],
["vin", { tag: "Builtin" }],
]); ]);
public resols = new Map<Expr, Sym>(); public resols = new Map<Expr, Sym>();

View File

@ -1,6 +1,31 @@
import * as front from "./front.ts"; import * as front from "./front.ts";
import * as ast from "./ast.ts"; import * as ast from "./ast.ts";
class ColorPrinter {
private process: Deno.ChildProcess;
private writer: WritableStreamDefaultWriter<Uint8Array<ArrayBufferLike>>;
constructor(language: string, theme = "github-dark") {
this.process = new Deno.Command("pygmentize", {
args: ["-l", language, "-O", `style=${theme}`],
stdin: "piped",
stdout: "inherit",
})
.spawn();
this.writer = this.process.stdin.getWriter();
}
async print(text: string): Promise<void> {
await this.writer.ready;
await this.writer.write(new TextEncoder().encode(text));
}
async close(): Promise<void> {
await this.writer.close();
await this.process.output();
}
}
class Ins { class Ins {
constructor( constructor(
public line: number, public line: number,
@ -9,26 +34,33 @@ class Ins {
} }
type InsKind = type InsKind =
| { tag: "Gnd" | "Vin" }
| { tag: "Input"; idx: number } | { tag: "Input"; idx: number }
| { tag: "Set"; idx: number; value: Ins } | { tag: "Set"; idx: number; value: Ins }
| { tag: "RelayDefaultOff" | "RelayDefaultOn"; args: Ins[] } | { tag: "RelayDefaultOff" | "RelayDefaultOn"; a: Ins; b: Ins }
| { tag: "Call"; def: ast.Def; args: Ins[] } | { tag: "Call"; def: ast.Def; args: Ins[] }
| { tag: "Elem"; value: Ins; idx: number }; | { tag: "Elem"; value: Ins; idx: number };
class InsCx { class InsCx {
public insts: Ins[] = []; public insts: Ins[] = [];
makeGnd(line: number): Ins {
return this.make(line, { tag: "Gnd" });
}
makeVin(line: number): Ins {
return this.make(line, { tag: "Vin" });
}
makeInput(line: number, idx: number): Ins { makeInput(line: number, idx: number): Ins {
return this.make(line, { tag: "Input", idx }); return this.make(line, { tag: "Input", idx });
} }
makeSet(line: number, idx: number, value: Ins): Ins { makeSet(line: number, idx: number, value: Ins): Ins {
return this.make(line, { tag: "Set", idx, value }); return this.make(line, { tag: "Set", idx, value });
} }
makeRelayDefaultOff(line: number, args: Ins[]): Ins { makeRelayDefaultOff(line: number, a: Ins, b: Ins): Ins {
return this.make(line, { tag: "RelayDefaultOff", args }); return this.make(line, { tag: "RelayDefaultOff", a, b });
} }
makeRelayDefaultOn(line: number, args: Ins[]): Ins { makeRelayDefaultOn(line: number, a: Ins, b: Ins): Ins {
return this.make(line, { tag: "RelayDefaultOn", args }); return this.make(line, { tag: "RelayDefaultOn", a, b });
} }
makeCall(line: number, def: ast.Def, args: Ins[]): Ins { makeCall(line: number, def: ast.Def, args: Ins[]): Ins {
return this.make(line, { tag: "Call", def, args }); return this.make(line, { tag: "Call", def, args });
@ -42,9 +74,16 @@ class InsCx {
this.insts.push(ins); this.insts.push(ins);
return ins; return ins;
} }
}
class Component {
constructor(
public def: ast.Def,
public insts: Ins[],
) {}
pretty(): string { pretty(): string {
let result = ""; let result = `${this.def.ident}:\n`;
let regIds = 0; let regIds = 0;
const insRegs = new Map<Ins, number>(); const insRegs = new Map<Ins, number>();
@ -57,6 +96,12 @@ class InsCx {
for (const ins of this.insts) { for (const ins of this.insts) {
switch (ins.kind.tag) { switch (ins.kind.tag) {
case "Gnd":
result += ` ${r(ins)} = gnd\n`;
break;
case "Vin":
result += ` ${r(ins)} = vin\n`;
break;
case "Input": case "Input":
result += ` ${r(ins)} = input ${ins.kind.idx}\n`; result += ` ${r(ins)} = input ${ins.kind.idx}\n`;
break; break;
@ -65,24 +110,26 @@ class InsCx {
break; break;
case "RelayDefaultOff": case "RelayDefaultOff":
result += ` ${r(ins)} = relay_default_off ${ result += ` ${r(ins)} = relay_default_off ${
r(ins.kind.args[0]) r(ins.kind.a)
} ${r(ins.kind.args[1])}\n`; }, ${r(ins.kind.b)}\n`;
break; break;
case "RelayDefaultOn": case "RelayDefaultOn":
result += ` ${r(ins)} = relay_default_on ${ result += ` ${r(ins)} = relay_default_on ${
r(ins.kind.args[0]) r(ins.kind.a)
} ${r(ins.kind.args[1])}\n`; }, ${r(ins.kind.b)}\n`;
break; break;
case "Call": case "Call":
result += ` ${r(ins)} = call @${ins.kind.def.ident} ${ result += ` ${r(ins)} = call @${ins.kind.def.ident}, ${
r(ins.kind.args[0]) ins.kind.args.map((a) => r(a)).join(", ")
} ${r(ins.kind.args[1])}\n`; }\n`;
break; break;
case "Elem": case "Elem":
result += ` ${r(ins)} = elem ${ result += ` ${r(ins)} = elem ${
r(ins.kind.value) r(ins.kind.value)
} ${ins.kind.idx}\n`; }, ${ins.kind.idx}\n`;
break; break;
default:
ins.kind satisfies never;
} }
} }
return result; return result;
@ -90,19 +137,19 @@ class InsCx {
} }
class DefLowerer { class DefLowerer {
private symIns = new Map<front.Sym, Ins>(); private cx = new InsCx();
private letIns = new Map<ast.Stmt, Ins[]>(); private letIns = new Map<ast.Stmt, Ins[]>();
constructor( constructor(
private cx: InsCx,
private def: ast.Def, private def: ast.Def,
private resols: Map<ast.Expr, front.Sym>, private resols: Map<ast.Expr, front.Sym>,
) {} ) {}
lower() { lower(): Component {
for (const stmt of this.def.stmts) { for (const stmt of this.def.stmts) {
this.lowerStmt(stmt); this.lowerStmt(stmt);
} }
return new Component(this.def, this.cx.insts);
} }
lowerStmt(stmt: ast.Stmt) { lowerStmt(stmt: ast.Stmt) {
@ -114,7 +161,7 @@ class DefLowerer {
this.cx.makeSet( this.cx.makeSet(
stmt.line, stmt.line,
re.idx, re.idx,
this.lowerExpr(stmt.kind.expr), this.lowerExprToVal(stmt.kind.expr),
); );
break; break;
case "Builtin": case "Builtin":
@ -140,6 +187,17 @@ class DefLowerer {
} }
} }
lowerExprToVal(expr: ast.Expr): Ins {
if (expr.kind.tag === "Call") {
return this.cx.makeElem(
expr.line,
this.lowerExpr(expr),
0,
);
}
return this.lowerExpr(expr);
}
lowerExpr(expr: ast.Expr): Ins { lowerExpr(expr: ast.Expr): Ins {
switch (expr.kind.tag) { switch (expr.kind.tag) {
case "Ident": { case "Ident": {
@ -150,7 +208,15 @@ class DefLowerer {
case "Node": { case "Node": {
return this.letIns.get(re.stmt)![re.idx]; return this.letIns.get(re.stmt)![re.idx];
} }
case "Builtin": case "Builtin": {
switch (expr.kind.ident) {
case "gnd":
return this.cx.makeGnd(expr.line);
case "vin":
return this.cx.makeVin(expr.line);
}
break;
}
case "Output": case "Output":
case "Def": case "Def":
throw new Error(); throw new Error();
@ -168,16 +234,14 @@ class DefLowerer {
case "relay_default_off": case "relay_default_off":
return this.cx.makeRelayDefaultOff( return this.cx.makeRelayDefaultOff(
expr.line, expr.line,
expr.kind.args.map((expr) => this.lowerExprToVal(expr.kind.args[0]),
this.lowerExpr(expr) this.lowerExprToVal(expr.kind.args[1]),
),
); );
case "relay_default_on": case "relay_default_on":
return this.cx.makeRelayDefaultOn( return this.cx.makeRelayDefaultOn(
expr.line, expr.line,
expr.kind.args.map((expr) => this.lowerExprToVal(expr.kind.args[0]),
this.lowerExpr(expr) this.lowerExprToVal(expr.kind.args[1]),
),
); );
default: default:
throw new Error(); throw new Error();
@ -188,7 +252,9 @@ class DefLowerer {
return this.cx.makeCall( return this.cx.makeCall(
expr.line, expr.line,
re.def, re.def,
expr.kind.args.map((expr) => this.lowerExpr(expr)), expr.kind.args.map((expr) =>
this.lowerExprToVal(expr)
),
); );
break; break;
} }
@ -206,15 +272,117 @@ class DefLowerer {
} }
} }
class IrInterpreter {
constructor(
private components: Map<ast.Def, Component>,
) {}
eval(com: Component, inputs: boolean[]): boolean[] {
if (inputs.length !== com.def.inputs.length) {
throw new Error(
`incorrect arguments to component '${com.def.ident}'. expected ${com.def.inputs.length}, got ${inputs.length}`,
);
}
const outputs = new Array(com.def.outputs.length).fill(false);
const tups = new Map<Ins, boolean[]>();
const vals = new Map<Ins, boolean>();
for (const ins of com.insts) {
switch (ins.kind.tag) {
case "Gnd":
vals.set(ins, false);
break;
case "Vin":
vals.set(ins, true);
break;
case "Input":
vals.set(ins, inputs[ins.kind.idx]);
break;
case "Set":
outputs[ins.kind.idx] = vals.get(ins.kind.value);
break;
case "RelayDefaultOff":
tups.set(
ins,
[
vals.get(ins.kind.a)! &&
vals.get(ins.kind.b)!,
],
);
break;
case "RelayDefaultOn":
tups.set(
ins,
[
!vals.get(ins.kind.a)! &&
vals.get(ins.kind.b)!,
],
);
break;
case "Call":
tups.set(
ins,
this.eval(
this.components.get(ins.kind.def)!,
ins.kind.args.map((arg) => vals.get(arg)!),
),
);
break;
case "Elem":
vals.set(
ins,
tups.get(ins.kind.value)![ins.kind.idx],
);
break;
default:
ins.kind satisfies never;
}
}
return outputs;
}
}
const text = await Deno.readTextFile(Deno.args[0]); const text = await Deno.readTextFile(Deno.args[0]);
const toks = front.tokenize(text); const toks = front.tokenize(text);
const sexprs = front.parseSExprs(toks); const sexprs = front.parseSExprs(toks);
const defs = front.parseAst(sexprs); const defs = front.parseAst(sexprs);
const resols = front.resolve(defs); const resols = front.resolve(defs);
const printer = new ColorPrinter("llvm");
const components = new Map<ast.Def, Component>();
for (const def of defs) { for (const def of defs) {
console.log(`${def.ident}:`); const component = new DefLowerer(def, resols.get(def)!).lower();
const cx = new InsCx(); components.set(def, component);
new DefLowerer(cx, def, resols.get(def)!).lower(); await printer.print(component.pretty());
console.log(cx.pretty()); }
await printer.close();
const interpreter = new IrInterpreter(components);
while (true) {
const line = prompt(">");
if (!line) {
break;
}
const s = front.parseSExprs(front.tokenize("(" + line + ")"))[0];
const ident = s.exprs?.[0].ident;
const args = s.exprs
?.slice(1)
?.map((s) =>
s.ident && /^[01]$/.test(s.ident) ? s.ident === "1" : null
);
if (!ident || !args || args.some((a) => a === null)) {
console.error(`error: malformed expression`);
continue;
}
const com = components.values().find((v) => v.def.ident === ident);
if (!com) {
throw new Error(`no component '${ident}'`);
}
const output = interpreter.eval(com, args as boolean[]);
const result = output.map((v) => v ? "1" : "0").join(" ");
console.log(`= ${result}`);
} }