Implement the VM
This commit is contained in:
parent
7186a895d2
commit
377c65e56b
174
gir.js
174
gir.js
|
@ -33,12 +33,14 @@ const jumpIfNonZero = Symbol('jumpIfNonZero');
|
|||
|
||||
// TODO: Add extensions from Eldis
|
||||
|
||||
class ParsingError extends Error {}
|
||||
|
||||
class UnknownIRError extends Error {}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Parsing
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
class ParsingError extends Error {}
|
||||
|
||||
// (string) → [commandObjects]
|
||||
// May throw ParsingError
|
||||
function parse(program) {
|
||||
|
@ -224,8 +226,6 @@ function prettifyIR(parsed) {
|
|||
// Optimization passes
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
class UnknownIRError extends Error {}
|
||||
|
||||
// ([commandObjects]) → [commandObjects]
|
||||
function joinAdjacentOps(parsed) {
|
||||
// ([commandObjects], commandType) → [commandObjects]
|
||||
|
@ -420,7 +420,7 @@ function flattenLoops(offsetted) {
|
|||
return worker(offsetted);
|
||||
}
|
||||
|
||||
// ([commandObjects]) → [offsetCommandObjects]
|
||||
// ([commandObjects]) → [flatCommandObjects]
|
||||
function optimize(parsed) {
|
||||
const optimizations = [
|
||||
joinAdjacentOps,
|
||||
|
@ -436,4 +436,166 @@ function optimize(parsed) {
|
|||
// Virtual machine
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
// TODO: Implement a brainfuck VM for running the optimized programs
|
||||
// ([flatCommandObject]) → girVMState
|
||||
function newVM(program, input) {
|
||||
return {
|
||||
// Initial state for the machine
|
||||
program: program,
|
||||
ip: 0,
|
||||
|
||||
memory: Object.create(null),
|
||||
tapeHead: 0,
|
||||
|
||||
input: input,
|
||||
output: ''
|
||||
};
|
||||
}
|
||||
|
||||
// (girVMState, int) → {state: girVMState, reachedLimit: bool}
|
||||
// reachedLimit is set to true if the program ran for maxCycles and did not
|
||||
// terminate
|
||||
// If maxCycles is null, the program runs until completion
|
||||
function runVM(state, maxCycles = null) {
|
||||
let program = state.program;
|
||||
let ip = state.ip;
|
||||
|
||||
// Create a copy of the memory, since we're going to modify it
|
||||
// TODO: Make memory into a Proxied thing that returns 0 if it
|
||||
// doesn't have the requested cell
|
||||
let memory = Object.create(null);
|
||||
for(let key in state.memory) {
|
||||
memory[key] = state.memory[key];
|
||||
}
|
||||
let tapeHead = state.tapeHead;
|
||||
|
||||
let input = state.input;
|
||||
let output = state.output;
|
||||
|
||||
let reachedLimit = true;
|
||||
for(let cycle = 0; maxCycles === null || cycle < maxCycles; cycle++) {
|
||||
// Exit the loop if we run to the end of the program
|
||||
if(ip >= program.length) {
|
||||
// Did not reach limit, program finished
|
||||
reachedLimit = false;
|
||||
break;
|
||||
}
|
||||
|
||||
let command = program[ip];
|
||||
|
||||
// See if we need to make sure the cell we're on exists and
|
||||
// calculate the index into the array of the cell we're
|
||||
// accessing
|
||||
let index = tapeHead;
|
||||
switch(command.type) {
|
||||
case add:
|
||||
case writeByte:
|
||||
case readByte:
|
||||
case clear:
|
||||
// These have an offset property, add it
|
||||
index += command.offset;
|
||||
// Fall through
|
||||
case jumpIfZero:
|
||||
case jumpIfNonZero:
|
||||
// Ensure the cell exists
|
||||
if(!(index in memory)) {
|
||||
memory[index] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Run the command
|
||||
switch(command.type) {
|
||||
case add:
|
||||
if(!(index in memory)) memory[index] = 0;
|
||||
memory[index] += command.value;
|
||||
// Implement wraparound
|
||||
memory[index] = memory[index] & 0xFF;
|
||||
ip++;
|
||||
break;
|
||||
|
||||
case moveHead:
|
||||
tapeHead += command.value;
|
||||
ip++;
|
||||
break;
|
||||
|
||||
case writeByte:
|
||||
if(!(index in memory)) memory[index] = 0;
|
||||
// TODO: utf-8
|
||||
output += String.fromCodePoint(memory[index]);
|
||||
ip++;
|
||||
break;
|
||||
|
||||
case readByte:
|
||||
// TODO: utf-8
|
||||
// Have we reached EOF?
|
||||
if(input.length == 0) {
|
||||
// Yes, return 0
|
||||
memory[index] = 0;
|
||||
} else {
|
||||
// No, return character
|
||||
memory[index] = input.codePointAt(0);
|
||||
// FIXME: This only works for BMP
|
||||
input = input.slice(1);
|
||||
}
|
||||
ip++;
|
||||
break;
|
||||
|
||||
case clear:
|
||||
memory[index] = 0;
|
||||
ip++;
|
||||
break;
|
||||
|
||||
case jumpIfZero:
|
||||
if(!(index in memory)) memory[index] = 0;
|
||||
if(memory[index] == 0) {
|
||||
ip = command.target;
|
||||
} else {
|
||||
ip++;
|
||||
}
|
||||
break;
|
||||
|
||||
case jumpIfNonZero:
|
||||
if(!(index in memory)) memory[index] = 0;
|
||||
if(memory[index] != 0) {
|
||||
ip = command.target;
|
||||
} else {
|
||||
ip++;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unknown command type
|
||||
throw new UnknownIRError();
|
||||
}
|
||||
}
|
||||
|
||||
let newState = {
|
||||
program,
|
||||
ip,
|
||||
|
||||
memory,
|
||||
tapeHead,
|
||||
|
||||
input,
|
||||
output
|
||||
};
|
||||
|
||||
return {state: newState, reachedLimit: reachedLimit};
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// User-facing functions
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
// (string) → [flatCommandObjects]
|
||||
function compile(program) {
|
||||
return optimize(parse(program));
|
||||
}
|
||||
|
||||
// (string, string) → string
|
||||
function run(program, input) {
|
||||
// TODO: Allow setting cycle maximum
|
||||
// TODO; Cache programs
|
||||
let compiled = compile(program);
|
||||
let vm = newVM(compiled, input);
|
||||
return runVM(vm).state.output;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue