Skip to content

Instantly share code, notes, and snippets.

@jamesliu96
Last active April 10, 2024 08:34
Show Gist options
  • Save jamesliu96/1a7e7526dc1fdce617b08197d6f21b66 to your computer and use it in GitHub Desktop.
Save jamesliu96/1a7e7526dc1fdce617b08197d6f21b66 to your computer and use it in GitHub Desktop.
/** 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