Last active
April 10, 2024 08:34
-
-
Save jamesliu96/1a7e7526dc1fdce617b08197d6f21b66 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** Brainfuck VM */ | |
class BFVM { | |
static ParseError = class extends Error { | |
constructor() { | |
super('parse error'); | |
} | |
}; | |
static PtrOutOfRangeError = class extends Error { | |
constructor() { | |
super('ptr out of range'); | |
} | |
}; | |
#ptr = 0; | |
get ptr() { | |
return this.#ptr; | |
} | |
#idx = 0; | |
get idx() { | |
return this.#idx; | |
} | |
#mem; | |
get mem() { | |
return [...this.#mem]; | |
} | |
/** @param {Uint8ArrayConstructor|Uint16ArrayConstructor|Uint32ArrayConstructor} array */ | |
constructor(size = 256, array = Uint8Array) { | |
this.#mem = new array(size); | |
} | |
/** | |
* Run code asynchronously | |
* @param {string} code | |
* @param {number[]} input | |
*/ | |
async run(code, input = []) { | |
return this.runSync(code, input); | |
} | |
/** | |
* Run code synchronously | |
* @param {string} code | |
* @param {number[]} input | |
* @returns {number[]} | |
*/ | |
runSync(code, input = []) { | |
this.#idx = 0; | |
const output = []; | |
[...this.#runOps(this.#parse(code), input, output)]; | |
return output; | |
} | |
/** | |
* Run code op by op | |
* @param {string} code | |
* @param {number[]} input | |
*/ | |
*runStep(code, input = []) { | |
this.#idx = 0; | |
yield* this.#runOps(this.#parse(code), input, []); | |
} | |
/** Reset ptr, idx & mem */ | |
reset() { | |
this.#reset(); | |
} | |
#reset() { | |
this.#ptr = 0; | |
this.#idx = 0; | |
this.#mem.fill(0); | |
} | |
/** @param {string} code */ | |
#preprocess(code) { | |
return code.split(''); | |
} | |
/** @param {string[]} opcs */ | |
#verify(opcs) { | |
let jc = 0; | |
for (const opc of opcs) { | |
if (jc < 0) return false; | |
if (opc === '[') jc++; | |
if (opc === ']') jc--; | |
} | |
return jc === 0; | |
} | |
/** | |
* @param {string} code | |
* @return {{opc:string,jmp:number}[]} | |
*/ | |
#parse(code) { | |
const opcs = this.#preprocess(code); | |
if (!this.#verify(opcs)) throw new BFVM.ParseError(); | |
const ops = []; | |
const jmps = []; | |
for (let i = 0; i < opcs.length; i++) { | |
const opc = opcs[i]; | |
if (opc === '[') { | |
const o = { opc }; | |
jmps.push({ i, o }); | |
ops.push(o); | |
} else if (opc === ']') { | |
const j = jmps.pop(); | |
j.o.jmp = i; | |
ops.push({ opc, jmp: j.i }); | |
} else ops.push({ opc }); | |
} | |
return ops; | |
} | |
get #currentByte() { | |
return this.#mem[this.#ptr]; | |
} | |
set #currentByte(byte) { | |
this.#mem[this.#ptr] = byte; | |
} | |
/** | |
* @param {{opc:string,jmp:number}[]} ops | |
* @param {number[]} input | |
* @param {number[]} output | |
*/ | |
*#runOps(ops, input, output) { | |
let op; | |
while ((op = ops[this.#idx++])) yield this.#runOp(op, input, output); | |
} | |
/** | |
* @param {{opc:string,jmp:number}} op | |
* @param {number[]} input | |
* @param {number[]} output | |
*/ | |
#runOp({ opc, jmp }, input, output) { | |
switch (opc) { | |
case '>': { | |
if (this.#ptr + 1 >= this.#mem.length) | |
throw new BFVM.PtrOutOfRangeError(); | |
this.#ptr++; | |
break; | |
} | |
case '<': { | |
if (this.#ptr - 1 < 0) throw new BFVM.PtrOutOfRangeError(); | |
this.#ptr--; | |
break; | |
} | |
case '+': { | |
this.#currentByte++; | |
break; | |
} | |
case '-': { | |
this.#currentByte--; | |
break; | |
} | |
case '.': { | |
output.push(this.#currentByte); | |
return this.#currentByte; | |
} | |
case ',': { | |
this.#currentByte = input.shift(); | |
break; | |
} | |
case '[': { | |
if (!this.#currentByte) this.#idx = jmp; | |
break; | |
} | |
case ']': { | |
if (this.#currentByte) this.#idx = jmp; | |
break; | |
} | |
default: { | |
break; | |
} | |
} | |
} | |
} | |
// Async call: | |
// new BFVM() | |
// .run( | |
// `++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.H>---.e+++++++.l.l+++.o>>. <-.W<.o+++.r------.l--------.d>>+.!` | |
// ) | |
// .then((output) => console.log('async:', String.fromCharCode(...output))); | |
// Sync call: | |
// console.log( | |
// 'sync:', | |
// String.fromCharCode( | |
// ...new BFVM().runSync( | |
// `++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.H>---.e+++++++.l.l+++.o>>. <-.W<.o+++.r------.l--------.d>>+.!` | |
// ) | |
// ) | |
// ); | |
// Generator call: | |
// console.log( | |
// 'step:', | |
// String.fromCharCode( | |
// ...new BFVM().runStep( | |
// `++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.H>---.e+++++++.l.l+++.o>>. <-.W<.o+++.r------.l--------.d>>+.!` | |
// ) | |
// ) | |
// ); | |
// Step expo: | |
// const code = `++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.H>---.e+++++++.l.l+++.o>>. <-.W<.o+++.r------.l--------.d>>+.!`; | |
// const vm = new BFVM(128); | |
// let iter = 0; | |
// let outStr = ''; | |
// for (const output of vm.runStep(code)) { | |
// console.log('iter:', iter); | |
// if (typeof output !== 'undefined') outStr += String.fromCharCode(output); | |
// console.log('out:', outStr); | |
// console.log('code:'); | |
// console.log(code); | |
// console.log( | |
// code | |
// .split('') | |
// .map((_, idx) => (idx === vm.idx ? '^' : '-')) | |
// .join('') | |
// ); | |
// console.log('mem:'); | |
// console.log( | |
// String.fromCharCode( | |
// ...vm.mem.map((m) => (m >= 0x20 && m <= 0x7e ? m : 0xb7)) | |
// ) | |
// ); | |
// console.log(vm.mem.map((_, idx) => (idx === vm.ptr ? '^' : '-')).join('')); | |
// iter++; | |
// console.log(); | |
// } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment