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
|
// TODO: Add extensions from Eldis
|
||||||
|
|
||||||
|
class ParsingError extends Error {}
|
||||||
|
|
||||||
|
class UnknownIRError extends Error {}
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
// Parsing
|
// Parsing
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
class ParsingError extends Error {}
|
|
||||||
|
|
||||||
// (string) → [commandObjects]
|
// (string) → [commandObjects]
|
||||||
// May throw ParsingError
|
// May throw ParsingError
|
||||||
function parse(program) {
|
function parse(program) {
|
||||||
|
@ -224,8 +226,6 @@ function prettifyIR(parsed) {
|
||||||
// Optimization passes
|
// Optimization passes
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
class UnknownIRError extends Error {}
|
|
||||||
|
|
||||||
// ([commandObjects]) → [commandObjects]
|
// ([commandObjects]) → [commandObjects]
|
||||||
function joinAdjacentOps(parsed) {
|
function joinAdjacentOps(parsed) {
|
||||||
// ([commandObjects], commandType) → [commandObjects]
|
// ([commandObjects], commandType) → [commandObjects]
|
||||||
|
@ -420,7 +420,7 @@ function flattenLoops(offsetted) {
|
||||||
return worker(offsetted);
|
return worker(offsetted);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ([commandObjects]) → [offsetCommandObjects]
|
// ([commandObjects]) → [flatCommandObjects]
|
||||||
function optimize(parsed) {
|
function optimize(parsed) {
|
||||||
const optimizations = [
|
const optimizations = [
|
||||||
joinAdjacentOps,
|
joinAdjacentOps,
|
||||||
|
@ -436,4 +436,166 @@ function optimize(parsed) {
|
||||||
// Virtual machine
|
// 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