Last active
April 6, 2023 12:18
-
-
Save frodo821/a244195c935db39dd38ed92dc33367db to your computer and use it in GitHub Desktop.
Brainfuck interpreter implementation in JavaScript
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
const insts = Object.freeze({ | |
'>': '1', | |
'<': 'f', | |
'+': '2', | |
'-': 'd', | |
'.': '3', | |
',': 'e', | |
'[': '$', | |
']': '_', | |
}); | |
const rinsts = Object.freeze( | |
Object.fromEntries( | |
Object.entries(insts).map(([a, b]) => [b, a]), | |
), | |
); | |
/** | |
* convert standard Brainfuck code to javascript identifier-safe brainfuck code | |
* @param {string} code a Brainfuck code | |
* @returns javascript identifier-safe brainfuck code | |
*/ | |
function toInstructions(code) { | |
return '$' + code.replace(/\s/g, '').split('').map(i => (insts[i] || i)).join(''); | |
} | |
/** | |
* creates and returns a Brainfuck executor | |
* @param {{enableWrite?: boolean, async?: boolean}} [opts] enable writing to console instead of returning a string | |
* @returns {{[key: string]: string | Promise<string> | undefined}} a brainfuck executor | |
*/ | |
function Brainfuck({enableWrite, async}) { | |
const bf = { | |
ptr: 0, | |
buffer: [0], | |
labels: [], | |
strbuf: '', | |
readbuf: '', | |
pc: 0, | |
code: '', | |
['<']() { | |
if (this.ptr == 0) { | |
this.buffer.unshift(0); | |
return; | |
} | |
this.ptr--; | |
}, | |
['>']() { | |
this.ptr++; | |
if (this.buffer.length <= this.ptr) { | |
this.buffer.push(0); | |
} | |
}, | |
['+']() { | |
this.buffer[this.ptr]++; | |
}, | |
['-']() { | |
this.buffer[this.ptr]--; | |
}, | |
['.']() { | |
this.strbuf = `${this.strbuf}${String.fromCharCode(this.buffer[this.ptr])}`; | |
if (enableWrite && this.strbuf.endsWith('\n')) { | |
console.log(this.strbuf); | |
this.strbuf = ''; | |
} | |
}, | |
[',']() { | |
if (this.readbuf.length === 0) { | |
this.readbuf = (prompt('reading a char') || '').concat('\n'); | |
} | |
this.readbuf.charCodeAt(0); | |
this.readbuf = this.readbuf.substring(1); | |
}, | |
['[']() { | |
this.labels.unshift(this.pc); | |
if (this.buffer[this.ptr] === 0) { | |
const jmpEnd = insts["]"]; | |
const jmpBegin = insts["["]; | |
const begin = this.pc; | |
while (true) { | |
const instr = this.code[this.pc]; | |
switch (instr) { | |
case undefined: | |
return; | |
case jmpBegin: | |
this.labels.unshift(this.pc); | |
break; | |
case jmpEnd: | |
const at = this.labels[0]; | |
this.labels.shift(); | |
if (at === begin) { | |
this.pc++; | |
return; | |
} | |
default: | |
break; | |
} | |
this.pc++; | |
} | |
} | |
}, | |
[']']() { | |
const jumpTo = this.labels[0]; | |
if (jumpTo === undefined) { | |
return; | |
} | |
if (this.buffer[this.ptr] !== 0) { | |
this.pc = jumpTo; | |
} else { | |
this.labels.shift(); | |
} | |
}, | |
next() { | |
let insts; | |
if (this.code.length <= this.pc) { | |
return false; | |
} | |
while((insts = rinsts[this.code[this.pc++]]) === undefined) { | |
if (this.code.length <= this.pc) { | |
return false; | |
} | |
} | |
this[insts](); | |
return true; | |
}, | |
reinitialize(code) { | |
this.ptr = 0; | |
this.buffer = [0]; | |
this.labels = []; | |
this.strbuf = ''; | |
this.readbuf = ''; | |
this.pc = 0; | |
this.code = code; | |
}, | |
} | |
let prox = new Proxy({}, { | |
has() { return true; }, | |
get(_, prop) { | |
prop = prop.toString(); | |
if (!async) { | |
bf.reinitialize(prop.substring(1)); | |
while(bf.next()); | |
if (enableWrite) { | |
if (bf.strbuf) { | |
console.log(bf.strbuf); | |
} | |
} else { | |
return bf.strbuf; | |
} | |
return; | |
} | |
return new Promise((res) => { | |
const executor = Object.assign({}, bf); | |
executor.reinitialize(prop.substring(1)); | |
function internal() { | |
if(executor.next()) { | |
setTimeout(internal, 0); | |
} else { | |
if (enableWrite) { | |
if (executor.strbuf) { | |
console.log(executor.strbuf); | |
} | |
res(undefined); | |
} else { | |
res(executor.strbuf); | |
} | |
} | |
} | |
internal(); | |
}); | |
}, | |
}); | |
return prox; | |
} | |
bf = Brainfuck({async:true}); | |
// get this name by | |
// toInstructions('+++++++++[>++++++++>+++++++++++>+++>+<<<<-]>.>++.+++++++..+++.>+++++.<<+++++++++++++++.>.+++.------.--------.>+.>+.'); | |
hw = bf.$222222222$122222222122222222222122212ffffd_13122322222223322231222223ff2222222222222223132223dddddd3dddddddd3123123; | |
hw.then(k => console.log(k)); |
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 library, each cell is not limited (values can take both positive and negative) */const insts=Object.freeze({">":"1","<":"f","+":"2","-":"d",".":"3",",":"e","[":"$","]":"_"}),rinsts=Object.freeze(Object.fromEntries(Object.entries(insts).map(([t,s])=>[s,t])));function toInstructions(t){return"$"+t.replace(/\s/g,"").split("").map(t=>insts[t]||t).join("")}function Brainfuck({enableWrite:t,async:s}){let i={ptr:0,buffer:[0],labels:[],strbuf:"",readbuf:"",pc:0,code:"","<"(){if(0==this.ptr){this.buffer.unshift(0);return}this.ptr--},">"(){this.ptr++,this.buffer.length<=this.ptr&&this.buffer.push(0)},"+"(){this.buffer[this.ptr]++},"-"(){this.buffer[this.ptr]--},"."(){this.strbuf=`${this.strbuf}${String.fromCharCode(this.buffer[this.ptr])}`,t&&this.strbuf.endsWith("\n")&&(console.log(this.strbuf),this.strbuf="")},","(){0===this.readbuf.length&&(this.readbuf=(prompt("reading a char")||"").concat("\n")),this.readbuf.charCodeAt(0),this.readbuf=this.readbuf.substring(1)},"["(){if(this.labels.unshift(this.pc),0===this.buffer[this.ptr]){let t=insts["]"],s=insts["["],i=this.pc;for(;;){let r=this.code[this.pc];switch(r){case void 0:return;case s:this.labels.unshift(this.pc);break;case t:let e=this.labels[0];if(this.labels.shift(),e===i){this.pc++;return}}this.pc++}}},"]"(){let t=this.labels[0];void 0!==t&&(0!==this.buffer[this.ptr]?this.pc=t:this.labels.shift())},next(){let t;if(this.code.length<=this.pc)return!1;for(;void 0===(t=rinsts[this.code[this.pc++]]);)if(this.code.length<=this.pc)return!1;return this[t](),!0},reinitialize(t){this.ptr=0,this.buffer=[0],this.labels=[],this.strbuf="",this.readbuf="",this.pc=0,this.code=t}};return new Proxy({},{has:()=>!0,get(r,e){if(e=e.toString(),!s){for(i.reinitialize(e.substring(1));i.next(););return t?void(i.strbuf&&console.log(i.strbuf)):i.strbuf}return new Promise(s=>{let r=Object.assign({},i);r.reinitialize(e.substring(1)),!function i(){r.next()?setTimeout(i,0):t?(r.strbuf&&console.log(r.strbuf),s(void 0)):s(r.strbuf)}()})}})} |
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 library, each cell is limited to 8 bit unsigned integer */const insts = Object.freeze({ ">": "1", "<": "f", "+": "2", "-": "d", ".": "3", ",": "e", "[": "$", "]": "_" }), rinsts = Object.freeze(Object.fromEntries(Object.entries(insts).map(([t, s]) => [s, t]))); function toInstructions(t) { return "$" + t.replace(/\s/g, "").split("").map(t => insts[t] || t).join("") } function Brainfuck({ enableWrite: t, async: s }) { let i = { ptr: 0, buffer: [0], labels: [], strbuf: "", readbuf: "", pc: 0, code: "", "<"() { if (0 == this.ptr) { this.buffer.unshift(0); return } this.ptr-- }, ">"() { this.ptr++, this.buffer.length <= this.ptr && this.buffer.push(0) }, "+"() { this.buffer[this.ptr] = (this.buffer[this.ptr]+1) % 256 }, "-"() { this.buffer[this.ptr]=(this.buffer[this.ptr]+255)%256 }, "."() { this.strbuf = `${this.strbuf}${String.fromCharCode(this.buffer[this.ptr])}`, t && this.strbuf.endsWith("\n") && (console.log(this.strbuf), this.strbuf = "") }, ","() { 0 === this.readbuf.length && (this.readbuf = (prompt("reading a char") || "").concat("\n")), this.readbuf.charCodeAt(0), this.readbuf = this.readbuf.substring(1) }, "["() { if (this.labels.unshift(this.pc), 0 === this.buffer[this.ptr]) { let t = insts["]"], s = insts["["], i = this.pc; for (; ;) { let r = this.code[this.pc]; switch (r) { case void 0: return; case s: this.labels.unshift(this.pc); break; case t: let e = this.labels[0]; if (this.labels.shift(), e === i) { this.pc++; return } }this.pc++ } } }, "]"() { let t = this.labels[0]; void 0 !== t && (0 !== this.buffer[this.ptr] ? this.pc = t : this.labels.shift()) }, next() { let t; if (this.code.length <= this.pc) return !1; for (; void 0 === (t = rinsts[this.code[this.pc++]]);)if (this.code.length <= this.pc) return !1; return this[t](), !0 }, reinitialize(t) { this.ptr = 0, this.buffer = [0], this.labels = [], this.strbuf = "", this.readbuf = "", this.pc = 0, this.code = t } }; return new Proxy({}, { has: () => !0, get(r, e) { if (e = e.toString(), !s) { for (i.reinitialize(e.substring(1)); i.next();); return t ? void (i.strbuf && console.log(i.strbuf)) : i.strbuf } return new Promise(s => { let r = Object.assign({}, i); r.reinitialize(e.substring(1)), !function i() { r.next() ? setTimeout(i, 0) : t ? (r.strbuf && console.log(r.strbuf), s(void 0)) : s(r.strbuf) }() }) } }) } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment