diff --git a/bf.css b/bf.css index 59ea1de..c9a4812 100644 --- a/bf.css +++ b/bf.css @@ -435,8 +435,7 @@ body>.wrapper>*{ #ioblock .combined>.iocontent{ flex:1 1 0px; - height:100%; - width:100%; + overflow:hidden; } #ioblock .combined>.iocontent .terminal{ @@ -450,6 +449,7 @@ body>.wrapper>*{ user-select: initial; font:16px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace; padding:0 4px; + box-sizing:border-box; } body>.wrapper>.horizontal-spacer{ diff --git a/bf.js b/bf.js index 854e2cb..6265320 100644 --- a/bf.js +++ b/bf.js @@ -108,6 +108,33 @@ window.addEventListener("load",function(){ codeCompiled=false; }); + var compilemodes_changed=function(){ + executemodes_changed(); + if(processHandlerTerminator){ + processHandlerTerminator(); + processHandlerTerminator=undefined; + } + compilationSpan.firstChild.nodeValue=""; + codeCompiled=false; + isCompiling=false; + }; + + var executemodes_changed=function(){ + if(runTerminator){ + runTerminator(); + runTerminator=undefined; + compilemodes_changed(); + return; + } + executionSpan.firstChild.nodeValue=""; + if(interactive){ + interactiveConsole.clear(); + } + else{ + outputEditor.setValue(""); + } + } + compilebutton.addEventListener("click",function(){ if(processHandlerTerminator){ processHandlerTerminator(); @@ -123,9 +150,10 @@ window.addEventListener("load",function(){ processHandler.initialize(function(){ if(!to_terminate){ executionSpan.firstChild.nodeValue=""; - compilationSpan.firstChild.nodeValue="Compiling…"; + compilationSpan.firstChild.nodeValue=""; + if(compilemode!=="debug")compilationSpan.firstChild.nodeValue="Compiling…"; var start_time=Date.now(); - processHandler.compile(codeEditor.getValue(),{},function(message){ + processHandler.compile(codeEditor.getValue(),{debug:(compilemode==="debug")},function(message){ if(!to_terminate){ isCompiling=false; processHandlerTerminator=undefined; @@ -133,7 +161,7 @@ window.addEventListener("load",function(){ codeCompiled=true; var end_time=Date.now(); console.log("Compiled in "+Math.round(end_time-start_time)+" ms."); - compilationSpan.firstChild.nodeValue="Compiled in "+Math.round(end_time-start_time)+" ms."; + if(compilemode!=="debug")compilationSpan.firstChild.nodeValue="Compiled in "+Math.round(end_time-start_time)+" ms."; if(toRunAfterCompiling){ toRunAfterCompiling=false; runbutton.click(); @@ -148,6 +176,9 @@ window.addEventListener("load",function(){ }); }); + var breakpointBuffer=undefined; + var globalPauseBuffer=undefined; + runbutton.addEventListener("click",function(){ if(!codeCompiled){ toRunAfterCompiling=true; @@ -171,8 +202,8 @@ window.addEventListener("load",function(){ }; executionSpan.firstChild.nodeValue="Executing…"; var start_time=Date.now(); - if(radio_interactive_no.checked){ - processHandler.execute(inputEditor.getValue(),{},function(message){ + if(!interactive){ + processHandler.execute(inputEditor.getValue(),{debug:(compilemode==="debug")},function(message){ if(!to_terminate){ runTerminator=undefined; if(message.success){ @@ -188,31 +219,71 @@ window.addEventListener("load",function(){ }); } else{ - interactiveConsole.clear(); - var interactiveObj=processHandler.executeInteractive({},function(){ - if(!to_terminate){ - interactiveConsole.read(function(text){ - interactiveObj.inputAddedCallback(text); - }); - } - },function(outputText){ - if(!to_terminate){ - interactiveConsole.write(outputText); - } - },function(message){ - if(!to_terminate){ - runTerminator=undefined; - if(message.success){ - var end_time=Date.now(); - outputEditor.setValue(message.output,1); - console.log("Executed in "+Math.round(end_time-start_time)+" ms."); - executionSpan.firstChild.nodeValue="Executed in "+Math.round(end_time-start_time)+" ms."; + if(compilemode!=="debug"){ + interactiveConsole.clear(); + interactiveConsole.focus(); + var interactiveObj=processHandler.executeInteractive({debug:(compilemode==="debug")},function(){ + if(!to_terminate){ + interactiveConsole.read(function(text){ + interactiveObj.inputAddedCallback(text); + }); + } + },function(outputText){ + if(!to_terminate){ + interactiveConsole.write(outputText); + } + },function(message){ + if(!to_terminate){ + runTerminator=undefined; + if(message.success){ + var end_time=Date.now(); + outputEditor.setValue(message.output,1); + console.log("Executed in "+Math.round(end_time-start_time)+" ms."); + executionSpan.firstChild.nodeValue="Executed in "+Math.round(end_time-start_time)+" ms."; + } + else{ + executionSpan.firstChild.nodeValue="Execution failed."; + } + } + }); + } + else{ + interactiveConsole.clear(); + interactiveConsole.focus(); + breakpointBuffer=new SharedArrayBuffer(codeEditor.getValue().length); + globalPauseBuffer=new SharedArrayBuffer(1); + var interactiveObj=processHandler.executeInteractive({debug:(compilemode==="debug"),sourcecode:codeEditor.getValue(),breakpointBuffer:breakpointBuffer,globalPauseBuffer:globalPauseBuffer},function(){ + if(!to_terminate){ + interactiveConsole.read(function(text){ + interactiveObj.inputAddedCallback(text); + }); + } + },function(outputText){ + if(!to_terminate){ + interactiveConsole.write(outputText); + } + },function(message){ + if(!to_terminate){ + runTerminator=undefined; + if(message.success){ + var end_time=Date.now(); + outputEditor.setValue(message.output,1); + console.log("Executed in "+Math.round(end_time-start_time)+" ms."); + executionSpan.firstChild.nodeValue="Executed in "+Math.round(end_time-start_time)+" ms."; + } + else{ + executionSpan.firstChild.nodeValue="Execution failed."; + } + } + },function(options){ + if(options.breakpoint){ + alert("breakpoint hit"); } else{ - executionSpan.firstChild.nodeValue="Execution failed."; + alert("paused"); } - } - }); + }); + } } } }); @@ -228,12 +299,16 @@ window.addEventListener("load",function(){ radio_interactive_yes.addEventListener("change",function(){ separate_ioblock.classList.remove("selected"); combined_ioblock.classList.add("selected"); - localStorage.setItem("option-interactive","yes"); + interactive="yes"; + localStorage.setItem("option-interactive",interactive); + executemodes_changed(); }); radio_interactive_no.addEventListener("change",function(){ combined_ioblock.classList.remove("selected"); separate_ioblock.classList.add("selected"); - localStorage.setItem("option-interactive","no"); + interactive="no"; + localStorage.setItem("option-interactive",interactive); + executemodes_changed(); }); var interactive=localStorage.getItem("option-interactive"); @@ -246,6 +321,34 @@ window.addEventListener("load",function(){ radio_interactive_yes.dispatchEvent(new Event("change")); } + if(!window.SharedArrayBuffer){ + radio_interactive_yes.disabled=true; + } + + var radio_compilemode_debug=document.getElementById("radio-compilemode-debug"); + var radio_compilemode_release=document.getElementById("radio-compilemode-release"); + + radio_compilemode_debug.addEventListener("change",function(){ + compilemode="debug"; + localStorage.setItem("option-compilemode",compilemode); + compilemodes_changed(); + }); + radio_compilemode_release.addEventListener("change",function(){ + compilemode="release"; + localStorage.setItem("option-compilemode",compilemode); + compilemodes_changed(); + }); + + var compilemode=localStorage.getItem("option-compilemode"); + if(compilemode==="debug"){ + radio_compilemode_debug.checked=true; + radio_compilemode_debug.dispatchEvent(new Event("change")); + } + else{ + radio_compilemode_release.checked=true; + radio_compilemode_release.dispatchEvent(new Event("change")); + } + // splitters Array.prototype.forEach.call(document.getElementById("ioblock").getElementsByClassName("vertical-spacer"),function(el){ diff --git a/interactive-console.js b/interactive-console.js index 2f17c99..2996ffe 100644 --- a/interactive-console.js +++ b/interactive-console.js @@ -3,6 +3,7 @@ var InteractiveConsole=function(el){ this.flushEveryChar=false; this.newLineChar='\n'; this.wrappingElement.setAttribute("tabindex","0"); + this.wrappingElement.style.setProperty("overflow-y","auto"); this.lineDivs=[]; this.inputBuffer=""; var that=this; @@ -27,6 +28,17 @@ var InteractiveConsole=function(el){ } } }); + this.wrappingElement.addEventListener("focus",function(){ + if(this.caret)this.showCaret(); + }); + + this.wrappingElement.addEventListener("blur",function(){ + if(this.caret){ + if(this.caretTimeout!==undefined)window.clearTimeout(this.caretTimeout); + this.caretTimeout=undefined; + this.caret.firstChild.nodeValue=" "; + } + }); this.clear(); @@ -35,6 +47,7 @@ var InteractiveConsole=function(el){ InteractiveConsole.prototype.makeNewLine=function(){ var lineDiv=document.createElement("div"); lineDiv.classList.add("interactive-console-line"); + lineDiv.style.setProperty("white-space","pre"); lineDiv.appendChild(document.createTextNode("")); return lineDiv; } @@ -42,15 +55,45 @@ InteractiveConsole.prototype.makeNewLine=function(){ InteractiveConsole.prototype.makeInputSpan=function(){ var lineDiv=document.createElement("span"); lineDiv.classList.add("interactive-console-input"); + lineDiv.style.setProperty("white-space","pre"); lineDiv.appendChild(document.createTextNode("")); return lineDiv; } +InteractiveConsole.prototype.showCaret=function(){ + if(!this.caretTimeout){ + this.caret.firstChild.nodeValue="█"; + var blink=function(){ + if(document.body.contains(this.caret)&&this.caretTimeout!==undefined){ + if(this.caret.firstChild.nodeValue==="█"){ + this.caret.firstChild.nodeValue=" "; + } + else{ + this.caret.firstChild.nodeValue="█"; + } + this.caretTimeout=window.setTimeout(blink,500); + } + }; + this.caretTimeout=window.setTimeout(blink,500); + } +}; + +InteractiveConsole.prototype.makeCaret=function(){ + var lineDiv=document.createElement("span"); + lineDiv.classList.add("interactive-console-caret"); + lineDiv.appendChild(document.createTextNode(" ")); + + return lineDiv; +} + InteractiveConsole.prototype.read=function(callback){ var lastLineDiv=this.lineDivs[this.lineDivs.length-1]; this.inputSpan=this.makeInputSpan(); + this.caret=this.makeCaret(); this.inputCallback=callback; lastLineDiv.appendChild(this.inputSpan); + lastLineDiv.appendChild(this.caret); + this.showCaret(); this.notifyReader(); }; @@ -60,20 +103,26 @@ InteractiveConsole.prototype.notifyReader=function(){ this.inputBuffer=this.inputBuffer.substr(1); if(this.flushEveryChar||newChar===this.newLineChar){ var callbackText=this.inputSpan.firstChild.nodeValue+newChar; + if(this.caretTimeout!==undefined)window.clearTimeout(this.caretTimeout); + this.caretTimeout=undefined; this.inputSpan.parentNode.removeChild(this.inputSpan); - this.inputSpan===undefined; + this.caret.parentNode.removeChild(this.caret); + this.inputSpan=undefined; + this.caret=undefined; this.write(callbackText); this.inputCallback(callbackText); } else{ this.inputSpan.firstChild.nodeValue+=newChar; } + this.wrappingElement.scrollTop=this.wrappingElement.scrollHeight; } }; InteractiveConsole.prototype.attemptBackspace=function(){ if(this.inputSpan&&this.inputSpan.firstChild.nodeValue.length>0){ this.inputSpan.firstChild.nodeValue=this.inputSpan.firstChild.nodeValue.slice(0,-1); + this.wrappingElement.scrollTop=this.wrappingElement.scrollHeight; } } @@ -88,6 +137,7 @@ InteractiveConsole.prototype.write=function(text){ var lastLineDiv=this.lineDivs[this.lineDivs.length-1]; lastLineDiv.firstChild.nodeValue+=lines[i]; } + this.wrappingElement.scrollTop=this.wrappingElement.scrollHeight; }; InteractiveConsole.prototype.clear=function(){ @@ -95,8 +145,14 @@ InteractiveConsole.prototype.clear=function(){ this.inputBuffer=""; this.inputSpan=undefined; this.inputCallback=undefined; + this.caret=undefined; + this.caretTimeout=undefined; while(this.wrappingElement.firstChild)this.wrappingElement.removeChild(this.wrappingElement.firstChild); var lineDiv=this.makeNewLine(); this.lineDivs.push(lineDiv); this.wrappingElement.appendChild(lineDiv); -} \ No newline at end of file +} + +InteractiveConsole.prototype.focus=function(){ + this.wrappingElement.focus(); +}; \ No newline at end of file diff --git a/jelly-bf-interpreter.js b/jelly-bf-interpreter.js new file mode 100644 index 0000000..7855736 --- /dev/null +++ b/jelly-bf-interpreter.js @@ -0,0 +1,88 @@ +JellyBFInterpreter=function(codeString,get_input,put_output,breakpointuint8array,globalpauseuint8array){ + this.code=codeString; + this.get_input=get_input; + this.put_output=put_output; + this.breakpointuint8array=breakpointuint8array; + this.globalpauseuint8array=globalpauseuint8array; + this.memory_cells=30000; + this.memory=new Uint8Array(this.memory_cells); + this.memory_ptr=0; + this.next_instruction_index=[]; + this.loop_pair=[]; + var loop_stack=[]; + var last_instruction_index=Number.MIN_SAFE_INTEGER; + this.entry_point=Number.MIN_SAFE_INTEGER; + for(var i=0;i+-[],.".indexOf(this.code[i])!==-1){ + if(last_instruction_index!==Number.MIN_SAFE_INTEGER)this.next_instruction_index[last_instruction_index]=i; + else this.entry_point=i; + last_instruction_index=i; + } + } + if(loop_stack.length!==0)throw JellyBFInterpreter.CompileError.LOOPS_IMBALANCED; + this.next_instruction_index[last_instruction_index]=Number.MAX_SAFE_INTEGER; + this.instruction_ptr=this.entry_point; +}; +JellyBFInterpreter.CompileError={ + LOOPS_IMBALANCED:1 +}; +JellyBFInterpreter.RuntimeError={ + INVALID_MEMORY_ACCESS:1, + INTEGER_OVERFLOW:2 +}; +JellyBFInterpreter.RunResult={ + PROGRAM_TERMINATED:1, + PAUSED_AT_BREAKPOINT:2, + PAUSED_WITHOUT_BREAKPOINT:3 +}; +// all runtime errors are checked *before* executing the instruction +JellyBFInterpreter.prototype.run=function(){ + while(this.instruction_ptr!=Number.MAX_SAFE_INTEGER){ + if(this.code[this.instruction_ptr]==="<"){ + if(this.memory_ptr===0)throw JellyBFInterpreter.RuntimeError.INVALID_MEMORY_ACCESS; + --this.memory_ptr; + } + else if(this.code[this.instruction_ptr]===">"){ + if(this.memory_ptr+1===this.memory_cells)throw JellyBFInterpreter.RuntimeError.INVALID_MEMORY_ACCESS; + ++this.memory_ptr; + } + else if(this.code[this.instruction_ptr]==="+"){ + this.memory[this.memory_ptr]=(this.memory[this.memory_ptr]+1)&255; + } + else if(this.code[this.instruction_ptr]==="-"){ + this.memory[this.memory_ptr]=(this.memory[this.memory_ptr]-1)&255; + } + else if(this.code[this.instruction_ptr]==="["){ + if(this.memory[this.memory_ptr]===0){ + this.instruction_ptr=this.loop_pair[this.instruction_ptr]; + } + } + else if(this.code[this.instruction_ptr]==="]"){ + if(this.memory[this.memory_ptr]!==0){ + this.instruction_ptr=this.loop_pair[this.instruction_ptr]; + } + } + else if(this.code[this.instruction_ptr]===","){ + this.memory[this.memory_ptr]=this.get_input(); + } + else if(this.code[this.instruction_ptr]==="."){ + this.put_output(this.memory[this.memory_ptr]); + } + else{ + throw "Internal error!"; + } + this.instruction_ptr=this.next_instruction_index[this.instruction_ptr]; + if(Atomics.load(this.breakpointuint8array,this.instruction_ptr)!==0&&this.instruction_ptr!=Number.MAX_SAFE_INTEGER)return {type:JellyBFInterpreter.RunResult.PAUSED_AT_BREAKPOINT,index:this.instruction_ptr}; + if(Atomics.load(this.globalpauseuint8array,0)!==0&&this.instruction_ptr!=Number.MAX_SAFE_INTEGER)return {type:JellyBFInterpreter.RunResult.PAUSED_WITHOUT_BREAKPOINT}; + } + return {type:JellyBFInterpreter.RunResult.PROGRAM_TERMINATED}; +}; \ No newline at end of file diff --git a/jelly-bf-processhandler.js b/jelly-bf-processhandler.js index dd28bbb..4ec25e0 100644 --- a/jelly-bf-processhandler.js +++ b/jelly-bf-processhandler.js @@ -22,27 +22,37 @@ JellyBFProcessHandler.prototype.initialize=function(callback){ }; JellyBFProcessHandler.prototype.compile=function(sourcecode,options,callback){ - this.worker.postMessage({type:"compile",sourcecode:sourcecode,options:options}); - wait_for_message(this.worker,"compiled",function(message){ + if(options.debug){ callback({success:true}); - }); - wait_for_message(this.worker,"compileerror",function(message){ - callback({success:false}); - }); + } + else{ + this.worker.postMessage({type:"compile",sourcecode:sourcecode,options:options}); + wait_for_message(this.worker,"compiled",function(message){ + callback({success:true}); + }); + wait_for_message(this.worker,"compileerror",function(message){ + callback({success:false}); + }); + } }; JellyBFProcessHandler.prototype.execute=function(inputstr,options,callback){ - var encodedinput=new TextEncoder().encode(inputstr); - this.worker.postMessage({type:"execute",inputuint8array:encodedinput,options:options},[encodedinput.buffer]); - wait_for_message(this.worker,"executed",function(message){ - callback({success:true,output:new TextDecoder().decode(message.outputuint8array)}); - }); - wait_for_message(this.worker,"executeerror",function(message){ - callback({success:false}); - }); + if(options.debug){ + + } + else{ + var encodedinput=new TextEncoder().encode(inputstr); + this.worker.postMessage({type:"execute",inputuint8array:encodedinput,options:options},[encodedinput.buffer]); + wait_for_message(this.worker,"executed",function(message){ + callback({success:true,output:new TextDecoder().decode(message.outputuint8array)}); + }); + wait_for_message(this.worker,"executeerror",function(message){ + callback({success:false}); + }); + } }; -JellyBFProcessHandler.prototype.executeInteractive=function(options,inputRequestCallback,outputCallback,doneCallback){ +JellyBFProcessHandler.prototype.executeInteractive=function(options,inputRequestCallback,outputCallback,doneCallback,pausedCallback){ var WaitArrayId={ READ_HEAD:0, WRITE_HEAD:1, @@ -145,17 +155,66 @@ JellyBFProcessHandler.prototype.executeInteractive=function(options,inputRequest this.worker.addEventListener("message",outputUpdatedHandler); - var that=this; - wait_for_message(this.worker,"executed",function(message){ - that.worker.removeEventListener("message",outputUpdatedHandler); - doneCallback({success:true}); - }); - wait_for_message(this.worker,"executeerror",function(message){ - that.worker.removeEventListener("message",outputUpdatedHandler); - doneCallback({success:false}); - }); - - this.worker.postMessage({type:"execute-interactive",inputbuffer:inputBuffer,outputbuffer:outputBuffer,inputwaitbuffer:inputWaitBuffer,outputwaitbuffer:outputWaitBuffer,options:options}); + if(options.debug){ + var that=this; + var sourcecode=options.sourcecode; + var breakpointBuffer=options.breakpointBuffer; + var globalpauseBuffer=options.globalPauseBuffer; + delete options.sourcecode; + delete options.breakpointBuffer; + delete options.globalPauseBuffer; + + var resumer=function(){ + that.worker.postMessage({type:"interpret-continue"}); + }; + + var interpretHandler=function(e){ + if(e.data.type==="interpret-breakpoint"){ + pausedCallback({breakpoint:true,resume:resumer}); + } + else if(e.data.type==="interpret-paused"){ + pausedCallback({breakpoint:false,resume:resumer}); + } + }; + + this.worker.addEventListener("message",interpretHandler); + + wait_for_message(this.worker,"parsecomplete",function(message){ + resumer(); + }); + + wait_for_message(this.worker,"parseerror",function(message){ + that.worker.removeEventListener("message",interpretHandler); + that.worker.removeEventListener("message",outputUpdatedHandler); + doneCallback({success:false,message:message}); + }); + + wait_for_message(this.worker,"interpreted",function(message){ + that.worker.removeEventListener("message",interpretHandler); + that.worker.removeEventListener("message",outputUpdatedHandler); + doneCallback({success:true}); + }); + wait_for_message(this.worker,"interpreterror",function(message){ + that.worker.removeEventListener("message",interpretHandler); + that.worker.removeEventListener("message",outputUpdatedHandler); + doneCallback({success:false}); + }); + + this.worker.postMessage({type:"interpret-interactive",sourcecode:sourcecode,inputbuffer:inputBuffer,outputbuffer:outputBuffer,inputwaitbuffer:inputWaitBuffer,outputwaitbuffer:outputWaitBuffer,breakpointbuffer:breakpointBuffer,globalpausebuffer:globalpauseBuffer,options:options}); + } + else{ + var that=this; + wait_for_message(this.worker,"executed",function(message){ + that.worker.removeEventListener("message",outputUpdatedHandler); + doneCallback({success:true}); + }); + wait_for_message(this.worker,"executeerror",function(message){ + that.worker.removeEventListener("message",outputUpdatedHandler); + doneCallback({success:false}); + }); + + this.worker.postMessage({type:"execute-interactive",inputbuffer:inputBuffer,outputbuffer:outputBuffer,inputwaitbuffer:inputWaitBuffer,outputwaitbuffer:outputWaitBuffer,options:options}); + } return {inputAddedCallback:inputAdded}; }; diff --git a/jelly-bf-sync.js b/jelly-bf-sync.js index a14f62b..174d831 100644 --- a/jelly-bf-sync.js +++ b/jelly-bf-sync.js @@ -83,5 +83,62 @@ var JellyBFSync={ instance.exports.main(); terminate_output(); return true; + }, + interpretInteractive:function(str,inputuint8array,outputuint8array,inputwaitint32array,outputwaitint32array,breakpointuint8array,globalpauseuint8array,options,updatedOutputCallback,requestInputCallback){ + var WaitArrayId={ + READ_HEAD:0, + WRITE_HEAD:1, + TERMINATED_FLAG:2 + }; + options.bufferlength=options.bufferlength||1024; // 1024 element buffer by default + options.eof_value=options.eof_value||0; + // two elements - next read index, next write index + // TODO: loading & storing from the data arrays may not need to be done atomically, due to the barriers issued by the wait array. + var input_read_head=0,input_write_head=0,input_terminated=false; // cache values + var get_input=function(){ + if(input_read_head===input_write_head){ + requestInputCallback(input_read_head); + console.log(Atomics.wait(inputwaitint32array,WaitArrayId.WRITE_HEAD,input_write_head)); + input_write_head=Atomics.load(inputwaitint32array,WaitArrayId.WRITE_HEAD); + if(!input_terminated){ + input_terminated=(Atomics.load(inputwaitint32array,WaitArrayId.TERMINATED_FLAG)!==0); + } + } + if(!input_terminated||input_read_head+1+-[],.".indexOf(this.code[i]) !== -1) { + if (last_instruction_index !== Number.MIN_SAFE_INTEGER) this.next_instruction_index[last_instruction_index] = i; else this.entry_point = i; + last_instruction_index = i; + } + } + if (loop_stack.length !== 0) throw JellyBFInterpreter.CompileError.LOOPS_IMBALANCED; + this.next_instruction_index[last_instruction_index] = Number.MAX_SAFE_INTEGER; + this.instruction_ptr = this.entry_point; +}; + +JellyBFInterpreter.CompileError = { + LOOPS_IMBALANCED: 1 +}; + +JellyBFInterpreter.RuntimeError = { + INVALID_MEMORY_ACCESS: 1, + INTEGER_OVERFLOW: 2 +}; + +JellyBFInterpreter.RunResult = { + PROGRAM_TERMINATED: 1, + PAUSED_AT_BREAKPOINT: 2, + PAUSED_WITHOUT_BREAKPOINT: 3 +}; + +JellyBFInterpreter.prototype.run = function() { + while (this.instruction_ptr != Number.MAX_SAFE_INTEGER) { + if (this.code[this.instruction_ptr] === "<") { + if (this.memory_ptr === 0) throw JellyBFInterpreter.RuntimeError.INVALID_MEMORY_ACCESS; + --this.memory_ptr; + } else if (this.code[this.instruction_ptr] === ">") { + if (this.memory_ptr + 1 === this.memory_cells) throw JellyBFInterpreter.RuntimeError.INVALID_MEMORY_ACCESS; + ++this.memory_ptr; + } else if (this.code[this.instruction_ptr] === "+") { + this.memory[this.memory_ptr] = this.memory[this.memory_ptr] + 1 & 255; + } else if (this.code[this.instruction_ptr] === "-") { + this.memory[this.memory_ptr] = this.memory[this.memory_ptr] - 1 & 255; + } else if (this.code[this.instruction_ptr] === "[") { + if (this.memory[this.memory_ptr] === 0) { + this.instruction_ptr = this.loop_pair[this.instruction_ptr]; + } + } else if (this.code[this.instruction_ptr] === "]") { + if (this.memory[this.memory_ptr] !== 0) { + this.instruction_ptr = this.loop_pair[this.instruction_ptr]; + } + } else if (this.code[this.instruction_ptr] === ",") { + this.memory[this.memory_ptr] = this.get_input(); + } else if (this.code[this.instruction_ptr] === ".") { + this.put_output(this.memory[this.memory_ptr]); + } else { + throw "Internal error!"; + } + this.instruction_ptr = this.next_instruction_index[this.instruction_ptr]; + if (Atomics.load(this.breakpointuint8array, this.instruction_ptr) !== 0 && this.instruction_ptr != Number.MAX_SAFE_INTEGER) return { + type: JellyBFInterpreter.RunResult.PAUSED_AT_BREAKPOINT, + index: this.instruction_ptr + }; + if (Atomics.load(this.globalpauseuint8array, 0) !== 0 && this.instruction_ptr != Number.MAX_SAFE_INTEGER) return { + type: JellyBFInterpreter.RunResult.PAUSED_WITHOUT_BREAKPOINT + }; + } + return { + type: JellyBFInterpreter.RunResult.PROGRAM_TERMINATED + }; +}; + var JellyBFSync = { compile: function(str, options) { return new WebAssembly.Module(JellyBFCompiler.compile(str, options)); @@ -1621,11 +1710,66 @@ var JellyBFSync = { instance.exports.main(); terminate_output(); return true; + }, + interpretInteractive: function(str, inputuint8array, outputuint8array, inputwaitint32array, outputwaitint32array, breakpointuint8array, globalpauseuint8array, options, updatedOutputCallback, requestInputCallback) { + var WaitArrayId = { + READ_HEAD: 0, + WRITE_HEAD: 1, + TERMINATED_FLAG: 2 + }; + options.bufferlength = options.bufferlength || 1024; + options.eof_value = options.eof_value || 0; + var input_read_head = 0, input_write_head = 0, input_terminated = false; + var get_input = function() { + if (input_read_head === input_write_head) { + requestInputCallback(input_read_head); + console.log(Atomics.wait(inputwaitint32array, WaitArrayId.WRITE_HEAD, input_write_head)); + input_write_head = Atomics.load(inputwaitint32array, WaitArrayId.WRITE_HEAD); + if (!input_terminated) { + input_terminated = Atomics.load(inputwaitint32array, WaitArrayId.TERMINATED_FLAG) !== 0; + } + } + if (!input_terminated || input_read_head + 1 < input_write_head) { + var val = Atomics.load(inputuint8array, input_read_head++ % options.bufferlength); + Atomics.store(inputwaitint32array, WaitArrayId.READ_HEAD, input_read_head); + return val; + } else { + return options.eof_value; + } + }; + var output_read_head = 0, output_write_head = 0, output_terminated = false; + var put_output = function(byte) { + if (output_read_head + options.bufferlength === output_write_head) { + Atomics.wait(outputwaitint32array, WaitArrayId.READ_HEAD, output_read_head); + output_read_head = Atomics.load(outputwaitint32array, WaitArrayId.READ_HEAD); + } + Atomics.store(outputuint8array, output_write_head++ % options.bufferlength, byte); + Atomics.store(outputwaitint32array, WaitArrayId.WRITE_HEAD, output_write_head); + updatedOutputCallback(); + }; + var terminate_output = function() { + if (output_read_head + options.bufferlength === output_write_head) { + Atomics.wait(outputwaitint32array, WaitArrayId.READ_HEAD, output_read_head); + output_read_head = Atomics.load(outputwaitint32array, WaitArrayId.READ_HEAD); + } + Atomics.store(outputwaitint32array, WaitArrayId.TERMINATED_FLAG, 1); + Atomics.store(outputwaitint32array, WaitArrayId.WRITE_HEAD, output_write_head + 1); + updatedOutputCallback(); + }; + var instance = new JellyBFInterpreter(str, get_input, put_output, breakpointuint8array, globalpauseuint8array); + return { + run: function() { + var res = instance.run(); + if (res === JellyBFInterpreter.RunResult.PROGRAM_TERMINATED) terminate_output(); + return res; + } + }; } }; (function() { var module = undefined; + var interpretstate = undefined; self.addEventListener("message", function(e) { var message = e.data; switch (message.type) { @@ -1689,6 +1833,70 @@ var JellyBFSync = { }); } break; + + case "interpret-interactive": + var sourcecode = message.sourcecode; + var inputbuffer = message.inputbuffer; + var outputbuffer = message.outputbuffer; + var inputwaitbuffer = message.inputwaitbuffer; + var outputwaitbuffer = message.outputwaitbuffer; + var options = message.options; + var breakpointbuffer = message.breakpointbuffer; + var globalpausebuffer = message.globalpausebuffer; + try { + interpretstate = JellyBFSync.interpretInteractive(sourcecode, new Uint8Array(inputbuffer), new Uint8Array(outputbuffer), new Int32Array(inputwaitbuffer), new Int32Array(outputwaitbuffer), new Uint8Array(breakpointbuffer), new Uint8Array(globalpausebuffer), options, function() { + self.postMessage({ + type: "output-updated" + }); + }, function(readhead) { + self.postMessage({ + type: "input-requested", + readhead: readhead + }); + }); + self.postMessage({ + type: "parsecomplete" + }); + } catch (e) { + console.log(e); + self.postMessage({ + type: "parseerror", + kind: e + }); + } + break; + + case "interpret-continue": + var ret; + try { + ret = interpretstate.run(); + } catch (e) { + console.log(e); + self.postMessage({ + type: "runtimeerror", + kind: e + }); + break; + } + if (ret.type === JellyBFInterpreter.RunResult.PROGRAM_TERMINATED) { + self.postMessage({ + type: "interpreted" + }); + interpretstate = undefined; + } else if (ret.type === JellyBFInterpreter.RunResult.PAUSED_AT_BREAKPOINT) { + self.postMessage({ + type: "interpret-breakpoint" + }); + } else if (ret.type === JellyBFInterpreter.RunResult.PAUSED_WITHOUT_BREAKPOINT) { + self.postMessage({ + type: "interpret-paused" + }); + } else { + self.postMessage({ + type: "interpreterror" + }); + } + break; } }); self.postMessage({ diff --git a/join-worker.bat b/join-worker.bat index 4c644fe..a02a8f5 100644 --- a/join-worker.bat +++ b/join-worker.bat @@ -1 +1 @@ -call uglifyjs wasm32codegen.max.js jelly-bf-compiler.js jelly-bf-sync.js jelly-bf-worker.js --beautify --output jelly-bf-worker.max.js \ No newline at end of file +call uglifyjs wasm32codegen.max.js jelly-bf-compiler.js jelly-bf-interpreter.js jelly-bf-sync.js jelly-bf-worker.js --beautify --output jelly-bf-worker.max.js \ No newline at end of file