Implement the VM

This commit is contained in:
Juhani Krekelä 2018-05-24 00:11:25 +03:00
parent 7186a895d2
commit 377c65e56b
1 changed files with 168 additions and 6 deletions

174
gir.js
View File

@ -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;
}