From 377c65e56b6be0081f3af35f746f6644eb6c3598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juhani=20Krekel=C3=A4?= Date: Thu, 24 May 2018 00:11:25 +0300 Subject: [PATCH] Implement the VM --- gir.js | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 168 insertions(+), 6 deletions(-) diff --git a/gir.js b/gir.js index bd055b0..dddb751 100644 --- a/gir.js +++ b/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; +}