diff --git a/demo.hlcsl b/demo.hlcsl index 21a9019..eb90264 100644 --- a/demo.hlcsl +++ b/demo.hlcsl @@ -1,5 +1,5 @@ -(def nand (a b vin) (r) ( +(def nand (a b) (r) ( (let (a_and_b) (relay_default_off a b)) (set r (relay_default_on a_and_b vin)) )) @@ -45,3 +45,4 @@ (set carry_out e1) )) + diff --git a/src/front.ts b/src/front.ts index f2748f0..ee3a9e5 100644 --- a/src/front.ts +++ b/src/front.ts @@ -26,7 +26,7 @@ export function tokenize(text: string): Tok[] { )[0] .map(({ tok, line }) => ({ ty: tok, text: tok, line })) .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"; } return tok; @@ -263,6 +263,8 @@ class DefResolver { private syms = new Map([ ["relay_default_off", { tag: "Builtin" }], ["relay_default_on", { tag: "Builtin" }], + ["gnd", { tag: "Builtin" }], + ["vin", { tag: "Builtin" }], ]); public resols = new Map(); diff --git a/src/main.ts b/src/main.ts index 315dbfb..73b81e3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,31 @@ import * as front from "./front.ts"; import * as ast from "./ast.ts"; +class ColorPrinter { + private process: Deno.ChildProcess; + private writer: WritableStreamDefaultWriter>; + + 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 { + await this.writer.ready; + await this.writer.write(new TextEncoder().encode(text)); + } + + async close(): Promise { + await this.writer.close(); + await this.process.output(); + } +} + class Ins { constructor( public line: number, @@ -9,26 +34,33 @@ class Ins { } type InsKind = + | { tag: "Gnd" | "Vin" } | { tag: "Input"; idx: number } | { 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: "Elem"; value: Ins; idx: number }; class InsCx { 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 { return this.make(line, { tag: "Input", idx }); } makeSet(line: number, idx: number, value: Ins): Ins { return this.make(line, { tag: "Set", idx, value }); } - makeRelayDefaultOff(line: number, args: Ins[]): Ins { - return this.make(line, { tag: "RelayDefaultOff", args }); + makeRelayDefaultOff(line: number, a: Ins, b: Ins): Ins { + return this.make(line, { tag: "RelayDefaultOff", a, b }); } - makeRelayDefaultOn(line: number, args: Ins[]): Ins { - return this.make(line, { tag: "RelayDefaultOn", args }); + makeRelayDefaultOn(line: number, a: Ins, b: Ins): Ins { + return this.make(line, { tag: "RelayDefaultOn", a, b }); } makeCall(line: number, def: ast.Def, args: Ins[]): Ins { return this.make(line, { tag: "Call", def, args }); @@ -42,9 +74,16 @@ class InsCx { this.insts.push(ins); return ins; } +} + +class Component { + constructor( + public def: ast.Def, + public insts: Ins[], + ) {} pretty(): string { - let result = ""; + let result = `${this.def.ident}:\n`; let regIds = 0; const insRegs = new Map(); @@ -57,6 +96,12 @@ class InsCx { for (const ins of this.insts) { switch (ins.kind.tag) { + case "Gnd": + result += ` ${r(ins)} = gnd\n`; + break; + case "Vin": + result += ` ${r(ins)} = vin\n`; + break; case "Input": result += ` ${r(ins)} = input ${ins.kind.idx}\n`; break; @@ -65,24 +110,26 @@ class InsCx { break; case "RelayDefaultOff": result += ` ${r(ins)} = relay_default_off ${ - r(ins.kind.args[0]) - } ${r(ins.kind.args[1])}\n`; + r(ins.kind.a) + }, ${r(ins.kind.b)}\n`; break; case "RelayDefaultOn": result += ` ${r(ins)} = relay_default_on ${ - r(ins.kind.args[0]) - } ${r(ins.kind.args[1])}\n`; + r(ins.kind.a) + }, ${r(ins.kind.b)}\n`; break; case "Call": - result += ` ${r(ins)} = call @${ins.kind.def.ident} ${ - r(ins.kind.args[0]) - } ${r(ins.kind.args[1])}\n`; + result += ` ${r(ins)} = call @${ins.kind.def.ident}, ${ + ins.kind.args.map((a) => r(a)).join(", ") + }\n`; break; case "Elem": result += ` ${r(ins)} = elem ${ r(ins.kind.value) - } ${ins.kind.idx}\n`; + }, ${ins.kind.idx}\n`; break; + default: + ins.kind satisfies never; } } return result; @@ -90,19 +137,19 @@ class InsCx { } class DefLowerer { - private symIns = new Map(); + private cx = new InsCx(); private letIns = new Map(); constructor( - private cx: InsCx, private def: ast.Def, private resols: Map, ) {} - lower() { + lower(): Component { for (const stmt of this.def.stmts) { this.lowerStmt(stmt); } + return new Component(this.def, this.cx.insts); } lowerStmt(stmt: ast.Stmt) { @@ -114,7 +161,7 @@ class DefLowerer { this.cx.makeSet( stmt.line, re.idx, - this.lowerExpr(stmt.kind.expr), + this.lowerExprToVal(stmt.kind.expr), ); break; 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 { switch (expr.kind.tag) { case "Ident": { @@ -150,7 +208,15 @@ class DefLowerer { case "Node": { 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 "Def": throw new Error(); @@ -168,16 +234,14 @@ class DefLowerer { case "relay_default_off": return this.cx.makeRelayDefaultOff( expr.line, - expr.kind.args.map((expr) => - this.lowerExpr(expr) - ), + this.lowerExprToVal(expr.kind.args[0]), + this.lowerExprToVal(expr.kind.args[1]), ); case "relay_default_on": return this.cx.makeRelayDefaultOn( expr.line, - expr.kind.args.map((expr) => - this.lowerExpr(expr) - ), + this.lowerExprToVal(expr.kind.args[0]), + this.lowerExprToVal(expr.kind.args[1]), ); default: throw new Error(); @@ -188,7 +252,9 @@ class DefLowerer { return this.cx.makeCall( expr.line, re.def, - expr.kind.args.map((expr) => this.lowerExpr(expr)), + expr.kind.args.map((expr) => + this.lowerExprToVal(expr) + ), ); break; } @@ -206,15 +272,117 @@ class DefLowerer { } } +class IrInterpreter { + constructor( + private components: Map, + ) {} + + 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(); + const vals = new Map(); + 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 toks = front.tokenize(text); const sexprs = front.parseSExprs(toks); const defs = front.parseAst(sexprs); const resols = front.resolve(defs); +const printer = new ColorPrinter("llvm"); + +const components = new Map(); for (const def of defs) { - console.log(`${def.ident}:`); - const cx = new InsCx(); - new DefLowerer(cx, def, resols.get(def)!).lower(); - console.log(cx.pretty()); + const component = new DefLowerer(def, resols.get(def)!).lower(); + components.set(def, component); + await printer.print(component.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}`); }