diff options
| author | Elizabeth Hunt <me@liz.coffee> | 2026-01-07 19:29:30 -0800 |
|---|---|---|
| committer | Elizabeth Hunt <me@liz.coffee> | 2026-01-07 19:29:30 -0800 |
| commit | 91b7598b22f89319f64054daf42c950de3eb6451 (patch) | |
| tree | b337ad01c75e7ee88f287eda05522e72dd9a8dd5 /src/toys/godel/js/compiler.js | |
| parent | 49012297ea792a69501b74d8d83bd4be44d177da (diff) | |
| download | lizdotcoffee-91b7598b22f89319f64054daf42c950de3eb6451.tar.gz lizdotcoffee-91b7598b22f89319f64054daf42c950de3eb6451.zip | |
Adding some of my favorite toys
Diffstat (limited to 'src/toys/godel/js/compiler.js')
| -rw-r--r-- | src/toys/godel/js/compiler.js | 226 |
1 files changed, 226 insertions, 0 deletions
diff --git a/src/toys/godel/js/compiler.js b/src/toys/godel/js/compiler.js new file mode 100644 index 0000000..d15c0f4 --- /dev/null +++ b/src/toys/godel/js/compiler.js @@ -0,0 +1,226 @@ +const INDENT_SIZE = 2; + +class CodeBuilder { + constructor(indentSize = INDENT_SIZE) { + this.indentSize = indentSize; + this.indentLevel = 0; + this.parts = []; + } + + addLine(line = "") { + const indent = " ".repeat(this.indentLevel * this.indentSize); + this.parts.push(line ? `${indent}${line}` : ""); + } + + open(line) { + if (line) { + this.addLine(line); + } + this.indentLevel += 1; + } + + close(line) { + this.indentLevel = Math.max(0, this.indentLevel - 1); + if (line) { + this.addLine(line); + } + } + + toString() { + return this.parts.join("\n"); + } +} + +const compileGoto = (gotoNode, builder) => { + builder.addLine(`this.followGoto("${gotoNode.label.symbol}");`); + builder.addLine("return;"); +}; + +const compileConditional = (conditionalNode, builder) => { + const variable = conditionalNode.variable.symbol; + builder.addLine(`if (this.get("${variable}") !== 0) {`); + builder.open(); + compileGoto(conditionalNode.goto, builder); + builder.close("}"); +}; + +const compileAssignment = (assignmentNode, builder) => { + const variable = assignmentNode.variable.symbol; + const expr = assignmentNode.expr || {}; + + if (expr.opr === "+") { + builder.addLine(`this.addOne("${variable}");`); + } else if (expr.opr === "-") { + builder.addLine(`this.subtractOne("${variable}");`); + } else { + builder.addLine("// noop"); + } +}; + +const compileInstruction = (instruction, builder) => { + if (instruction.goto) { + compileGoto(instruction.goto, builder); + return; + } + + if (instruction.conditional) { + compileConditional(instruction.conditional, builder); + } else if (instruction.assignment) { + compileAssignment(instruction.assignment, builder); + } + + builder.addLine("this.instructionPointer++;"); +}; + +const emitMethod = (builder, signature, bodyFn) => { + builder.addLine(`${signature} {`); + builder.open(); + bodyFn(); + builder.close("}"); + builder.addLine(""); +}; + +const emitConstructor = (builder, ast, godelSequence, methodCatalog) => { + emitMethod(builder, "constructor()", () => { + builder.addLine("this.variables = new Map();"); + builder.addLine("this.labelInstructions = new Map();"); + builder.addLine("this.instructions = new Map();"); + builder.addLine("this.instructions.set(0, () => this.main());"); + builder.addLine("this.instructionPointer = 0;"); + builder.addLine('this.variables.set("Y", 0);'); + builder.addLine(`this.finalInstruction = ${ast.instructions.length + 1};`); + builder.addLine('this.labelInstructions.set("E1", this.finalInstruction);'); + builder.addLine(""); + builder.addLine("// instruction bindings"); + + ast.instructions.forEach((entry, index) => { + const instructionNode = entry.instruction; + const instructionIdx = index + 1; + godelSequence.push(entry.godel); + + if (instructionNode.label) { + const labelName = instructionNode.label.symbol; + builder.addLine( + `this.instructions.set(${instructionIdx}, () => this.${labelName}());` + ); + builder.addLine( + `this.labelInstructions.set("${labelName}", ${instructionIdx});` + ); + methodCatalog.push({ + name: labelName, + index: instructionIdx, + node: instructionNode.instruction + }); + } else { + const methodName = `instruction${instructionIdx}`; + builder.addLine( + `this.instructions.set(${instructionIdx}, () => this.${methodName}());` + ); + methodCatalog.push({ + name: methodName, + index: instructionIdx, + node: instructionNode + }); + } + }); + }); +}; + +const emitRuntimeHelpers = (builder, instructionCount) => { + emitMethod(builder, "get(variable)", () => { + builder.addLine("if (!this.variables.has(variable)) {"); + builder.open(); + builder.addLine("this.variables.set(variable, 0);"); + builder.close("}"); + builder.addLine("return this.variables.get(variable);"); + }); + + emitMethod(builder, "addOne(variable)", () => { + builder.addLine("const val = this.get(variable);"); + builder.addLine("this.variables.set(variable, val + 1);"); + }); + + emitMethod(builder, "subtractOne(variable)", () => { + builder.addLine("const val = this.get(variable);"); + builder.addLine("this.variables.set(variable, val - 1);"); + }); + + emitMethod(builder, "followGoto(label)", () => { + builder.addLine("this.instructionPointer = this.labelInstructions.get(label);"); + }); + + emitMethod(builder, "step()", () => { + builder.addLine("if (!this.isCompleted()) {"); + builder.open(); + builder.addLine("const procedure = this.instructions.get(this.instructionPointer);"); + builder.addLine("procedure();"); + builder.close("}"); + builder.addLine("return this.instructionPointer;"); + }); + + emitMethod(builder, "isCompleted()", () => { + builder.addLine("return this.instructionPointer === this.finalInstruction;"); + }); + + emitMethod(builder, "getResult()", () => { + builder.addLine('return this.variables.get("Y");'); + }); + + emitMethod(builder, "run(maxIter = 500_000)", () => { + builder.addLine("let iter = 0;"); + builder.addLine("while (!this.isCompleted() && ++iter < maxIter) {"); + builder.open(); + builder.addLine("this.step();"); + builder.close("}"); + builder.addLine("if (iter < maxIter) {"); + builder.open(); + builder.addLine("return this.getResult();"); + builder.close("}"); + builder.addLine( + 'throw new Error("Program exceeded iteration limit. Try optimizing your instructions or increasing the cap.");' + ); + }); + + emitMethod(builder, "main()", () => { + if (instructionCount > 0) { + builder.addLine("this.instructionPointer = 1;"); + } else { + builder.addLine("this.instructionPointer = this.finalInstruction;"); + } + }); +}; + +const emitInstructionMethods = (builder, catalog) => { + catalog.forEach((entry) => { + emitMethod(builder, `${entry.name}()`, () => { + builder.addLine(`this.instructionPointer = ${entry.index};`); + compileInstruction(entry.node, builder); + }); + }); +}; + +export const compileProgram = (ast) => { + const builder = new CodeBuilder(); + const godelSequence = []; + const methodCatalog = []; + + builder.addLine("class Program {"); + builder.open(); + emitConstructor(builder, ast, godelSequence, methodCatalog); + emitRuntimeHelpers(builder, ast.instructions.length); + emitInstructionMethods(builder, methodCatalog); + builder.close("}"); + builder.addLine(""); + builder.addLine("// bootstrap"); + builder.addLine("const program = new Program();"); + builder.addLine('// program.variables.set("X1", 2);'); + builder.addLine('// program.variables.set("X2", 3);'); + builder.addLine("program.run();"); + builder.addLine("console.log(program.variables);"); + builder.addLine("program.getResult();"); + + return { + js: builder.toString(), + godelSequence + }; +}; |
