2023-11-16 21:06:35 -05:00
|
|
|
class StringBuilder {
|
|
|
|
constructor() {
|
|
|
|
this.stringPieces = [];
|
|
|
|
}
|
|
|
|
add(s) {
|
|
|
|
this.stringPieces.push(s);
|
|
|
|
}
|
|
|
|
build() {
|
|
|
|
return this.stringPieces.join("");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const compileGoto = (gotoNode, stringBuilder) => {
|
|
|
|
stringBuilder.add(`this.followGoto("${gotoNode.label}");\nreturn;\n`);
|
|
|
|
};
|
|
|
|
|
|
|
|
const compileConditional = (conditionalNode, stringBuilder) => {
|
|
|
|
const { variable, goto: gotoNode } = conditionalNode;
|
|
|
|
|
|
|
|
stringBuilder.add(`if (this.get("${variable}") != 0) {\n`);
|
|
|
|
compileGoto(gotoNode, stringBuilder);
|
|
|
|
stringBuilder.add(`}\n`);
|
|
|
|
};
|
|
|
|
|
|
|
|
const compileAssignment = (assignmentNode, stringBuilder) => {
|
|
|
|
const { variable, expr } = assignmentNode;
|
|
|
|
if (expr.opr) {
|
|
|
|
if (expr.opr == "+") stringBuilder.add(`this.addOne("${variable}");\n`);
|
|
|
|
else if (expr.opr == "-")
|
|
|
|
stringBuilder.add(`this.subtractOne("${variable}");\n`);
|
|
|
|
} else {
|
|
|
|
stringBuilder.add("// noop \n");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const compileInstruction = (instruction, stringBuilder) => {
|
|
|
|
if (instruction.goto) {
|
|
|
|
compileGoto(instruction.goto, stringBuilder);
|
|
|
|
return; // ignore unreachable addition to instructionPointer
|
|
|
|
}
|
|
|
|
|
|
|
|
if (instruction.conditional) {
|
|
|
|
compileConditional(instruction.conditional, stringBuilder);
|
|
|
|
} else if (instruction.assignment) {
|
|
|
|
compileAssignment(instruction.assignment, stringBuilder);
|
|
|
|
}
|
|
|
|
|
|
|
|
stringBuilder.add("this.instructionPointer++;\n");
|
|
|
|
};
|
|
|
|
|
|
|
|
const compile = (ast) => {
|
|
|
|
const stringBuilder = new StringBuilder();
|
|
|
|
stringBuilder.add(`
|
|
|
|
class Program {
|
|
|
|
constructor() {
|
|
|
|
this.variables = new Map(); // variable -> natural number val
|
|
|
|
this.labelInstructions = new Map(); // labels to instruction indices
|
|
|
|
this.instructions = new Map(); // instruction indices to procedures
|
|
|
|
|
|
|
|
this.instructions.set(0, () => this.main());
|
|
|
|
this.instructionPointer = 0;
|
|
|
|
this.variables.set("Y", 0);
|
|
|
|
|
|
|
|
// -- program-specific state init --
|
|
|
|
this.finalInstruction = ${
|
|
|
|
ast.instructions.length + 1
|
|
|
|
}; // instruction of the implied "exit" label
|
2023-11-17 12:04:55 -05:00
|
|
|
// "E" is the exit label
|
|
|
|
this.labelInstructions.set("E", this.finalInstruction);
|
2023-11-16 21:06:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
get(variable) {
|
|
|
|
if (!this.variables.has(variable)) {
|
|
|
|
this.variables.set(variable, 0);
|
|
|
|
}
|
|
|
|
return this.variables.get(variable);
|
|
|
|
}
|
|
|
|
|
|
|
|
addOne(variable) {
|
|
|
|
const val = this.get(variable);
|
|
|
|
this.variables.set(variable, val + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
subtractOne(variable) {
|
|
|
|
const val = this.get(variable);
|
|
|
|
this.variables.set(variable, val - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
followGoto(label) {
|
|
|
|
this.instructionPointer = this.labelInstructions.get(label);
|
|
|
|
}
|
|
|
|
|
|
|
|
step() {
|
|
|
|
if (!this.isCompleted()) {
|
|
|
|
const procedure = this.instructions.get(this.instructionPointer);
|
|
|
|
procedure();
|
|
|
|
|
|
|
|
}
|
|
|
|
return this.instructionPointer;
|
|
|
|
}
|
|
|
|
|
|
|
|
isCompleted() {
|
|
|
|
return this.instructionPointer == this.finalInstruction;
|
|
|
|
}
|
|
|
|
|
|
|
|
getResult() {
|
|
|
|
return this.variables.get("Y");
|
|
|
|
}
|
|
|
|
|
2023-11-17 12:04:55 -05:00
|
|
|
run(maxIter=500_000) {
|
2023-11-16 21:06:35 -05:00
|
|
|
let iter = 0;
|
|
|
|
while (!this.isCompleted() && (++iter) < maxIter) this.step();
|
|
|
|
if (iter < maxIter) {
|
|
|
|
return this.getResult();
|
|
|
|
}
|
2023-11-17 12:04:55 -05:00
|
|
|
throw new Error("Too many iterations. To resolve, please ask"
|
|
|
|
+ " Turing how we can find if a program will halt during compilation.");
|
2023-11-16 21:06:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
main() {
|
|
|
|
`);
|
|
|
|
|
|
|
|
stringBuilder.add("// -- build label -> instruction map --\n");
|
|
|
|
for (let i = 0; i < ast.instructions.length; i++) {
|
|
|
|
const line = ast.instructions[i];
|
|
|
|
const instructionIdx = i + 1;
|
|
|
|
if (line.label) {
|
|
|
|
stringBuilder.add(
|
2023-11-17 12:04:55 -05:00
|
|
|
`this.instructions.set(${instructionIdx}, () => this.${line.label}());\n`
|
2023-11-16 21:06:35 -05:00
|
|
|
);
|
|
|
|
stringBuilder.add(
|
2023-11-17 12:04:55 -05:00
|
|
|
`this.labelInstructions.set("${line.label}", ${instructionIdx});\n`
|
2023-11-16 21:06:35 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
stringBuilder.add("// -- compiled instructions --\n");
|
|
|
|
for (let i = 0; i < ast.instructions.length; i++) {
|
|
|
|
let instruction = ast.instructions[i];
|
|
|
|
const instructionIdx = i + 1;
|
|
|
|
if (instruction.label) {
|
|
|
|
stringBuilder.add(
|
2023-11-17 12:04:55 -05:00
|
|
|
` this.followGoto("${instruction.label}");\n}\n\n${instruction.label}() {\n`
|
2023-11-16 21:06:35 -05:00
|
|
|
);
|
|
|
|
stringBuilder.add(`this.instructionPointer = ${instructionIdx};\n`);
|
|
|
|
instruction = instruction.instruction;
|
|
|
|
}
|
|
|
|
|
|
|
|
compileInstruction(instruction, stringBuilder);
|
|
|
|
}
|
|
|
|
|
|
|
|
stringBuilder.add(` }\n}\n`);
|
|
|
|
stringBuilder.add("// -- \n");
|
|
|
|
stringBuilder.add("const program = new Program();\n\n");
|
|
|
|
stringBuilder.add("// set the initial Snapshot here\n");
|
|
|
|
stringBuilder.add('// program.variables.set("X1", 2);\n\n');
|
2023-11-17 12:04:55 -05:00
|
|
|
stringBuilder.add("program.run();\n");
|
|
|
|
stringBuilder.add("console.log(program.variables);\n");
|
|
|
|
stringBuilder.add("program.getResult();\n");
|
2023-11-16 21:06:35 -05:00
|
|
|
|
|
|
|
return js_beautify(stringBuilder.build(), {
|
|
|
|
indent_size: 2,
|
2023-11-17 12:04:55 -05:00
|
|
|
wrap_line_length: 100,
|
2023-11-16 21:06:35 -05:00
|
|
|
});
|
|
|
|
};
|