Javascript nested loops awaiting user input

I created the C interpreter in C # some time ago and now I started to convert it to Javascript. Everything went well until I realized that js has no sleep function. My interpreter uses a recursive parser, and it pauses user input while it nested several functions in depth (in C # I used waithandle in the second thread). I looked at setInterval and setTimeout, but they are asynchronous / not blocked; Of course, the question of the lesson was out of the question, and I looked at the implementation of timed_queue, which I found on SO, but no luck. I tried the parser both in the main window and in the webworker. I am using jQuery. I have limited experience with js and am looking for ideas to continue. I don't know much about continuing through style or damage, and I wonder if they can hold the key. Here's a bit of code cut to show some of the control characters. Any ideas please ...

var STATE = { START: "START", RUN: "RUN", //take continuous steps at waitTime delay STEP: "STEP", //take 1 step PAUSE: "PAUSE",//wait for next step command STOP: "STOP", ERROR: "ERROR" } var state = state.STOP; function parsing_process() //long process we may want to pause or wait in { while(token !== end_of_file)// { //do lots of stuff - much of it recursive //the call to getNextToken will be encountered a lot in the recursion getNextToken(); if (state === STATE.STOP) break; } } function getNextToken() { //retrieve next token from lexer array if (token === end_of_line) { //tell the gui to highlight the current line if (state === STATE.STOP) return; if (state === STATE.STEP)//wait for next step { //mimick wait for user input by using annoying alert alert("click me to continue") } if (state === STATE.RUN) { //a delay here - set by a slider in the window //a busy wait haults processing of the window } } } 

I got this to work in Firefox using task.js

 <html> <head> <title>task.js examples: sleep</title> <script type="application/javascript" src="task.js"></script> </head> <body> Only works in FIREFOX <button onclick="step()">Step</button> <button onclick="run()">Run</button> <button onclick="stop()">Stop</button> <pre style="border: solid 1px black; width: 300px; height: 200px;" id="out"> </pre> <script type="application/javascript;version=1.8"> function start() { process(); } function step() { if (state === STATE.STOP) start(); state = STATE.STEP; } function run() { if (state === STATE.STOP) start(); state = STATE.RUN; } function stop() { state = STATE.STOP; } var STATE = { START: "START", RUN: "RUN", //take continuous steps at sleepTime delay STEP: "STEP", //take 1 step PAUSE: "PAUSE",//wait for next step command STOP: "STOP", ERROR: "ERROR" } var state = STATE.STOP; var sleepTime = 500; function process() { var { spawn, choose, sleep } = task; var out = document.getElementById("out"); var i=0; out.innerHTML = "i="+i; var sp = spawn(function() { while(state !== STATE.STOP) { i++; out.innerHTML = "i="+i; if (state === STATE.RUN) { yield sleep(sleepTime); } if (state === STATE.STEP) state = STATE.PAUSE; while (state===STATE.PAUSE) { yield; } } }); } </script> </body> </html> 

I would appreciate it if anyone who knew something about promises could give me some more clues. My application is not consumer, but it would be nice if it worked in Firefox,

+6
source share
3 answers

As the author of JSCPP , I ran into the same problem when I ran a debugger that pauses and continues to interpret the fly program. In the end, I decided to use the generator functions from es6, but I would like to share my thought process here.

A general way is to first compile the target code into bytecode with a low level of recursive byte. You label each statement, and then process the entire control flow with unconditional jump and conditional jump . Then, the byte code interpreter is started instead. This is a good option if you do not mind that all of this compilation work is done.

Another way is the call stack / call stack workflow. When you need to pause the interpretation, you recursively push all arguments and all local variables into the custom stack all the way to the end. When you need to continue execution, you recursively load all of these arguments and local variables. Your code will be converted from

 AddExpression.prototype.visit = function(param) { var leftVal = visit(this.left, param); var rightVal = visit(this.right, param); return leftVal + rightVal; } 

to

 AddExpression.prototype.visit = function(param) { if (needToStop) { stack.push({ method: AddExpression.prototype.visit, _this: this, params: [param], locals: {}, step: 0 }); return; } if (recoverFromStop && stack.top().step === 0) { var thisCall = stack.pop(); if (stack.length > 0) { var nextCall = stack.top(); nextCall.method.apply(nextCall._this, params); } } var leftvalue = visit(this.left, param); if (needToStop) { stack.push({ method: AddExpression.prototype.visit, _this: this, params: [], locals: { leftvalue: leftvalue }, step: 1 }); return; } if (recoverFromStop && stack.top().step === 1) { var thisCall = stack.pop(); leftvalue = thisCall.locals.leftvalue; if (stack.length > 0) { var nextCall = stack.top(); nextCall.method.apply(nextCall._this, params); } } var rightvalue = visit(this.right, param); if (needToStop) { stack.push({ method: AddExpression.prototype.visit, _this: this, params: [], locals: { leftvalue: leftvalue, rightvalue: rightvalue }, step: 2 }); return; } if (recoverFromStop && stack.top().step === 2) { var thisCall = stack.pop(); leftvalue = thisCall.locals.leftvalue; rightvalue = thisCall.locals.rightvalue; if (stack.length > 0) { var nextCall = stack.top(); nextCall.method.apply(nextCall._this, params); } } return leftvalue + rightvalue; }; 

This method does not change the core logic of your interpreter, but you can see for yourself how crazy the code is for the simple A + B syntax.

Finally, I decided to use generators. Generators are not designed for interactively changing program execution, but rather for lazy evaluation. But with some simple hacking, we can use lazy evaluations of our statements after receiving the β€œcontinue” command.

 function interpret(mainNode, param) { var step; var gen = visit(mainNode); do { step = gen.next(); } while(!step.done); return step.value; } function visit*(node, param) { return (yield* node.visit(param)); } AddExpression.prototype.visit = function*(param) { var leftvalue = yield* visit(this.left, param); var rightvalue = yield* visit(this.right, param); return leftvalue + rightvalue; } 

Here function* indicates that the AddExpression.visit function should be a generator function. yield* , followed by visit call facility visit The function itself is a recursive generator function.

This solution seems perfect at first glance, but it suffers from a huge decrease in performance due to the use of generators ( http://jsperf.com/generator-performance ) and that it is from es6 and not many browsers support it.

In conclusion, you have three different ways to achieve intermittent execution:

  • compile low level code:
    • Pros: common practice, individual problems, easy to optimize and maintain.
    • Cons: too much work
  • save stack / boot stack:
    • Pros: relatively fast, preserves interpretation logic
    • Cons: hard to maintain
  • generator:
    • Pros: ease of maintenance, perfectly retains the logic of interpretation.
    • Cons: slow, es6 required for es5 transpiling
+1
source

If you use your script in a browser and must wait for user input (click event, field change event, etc.) then you cannot use "while" and "pause" to wait for a browser event. The event handler will be called asynchronously, and by then the while loop may even finish reading the token list. You should probably try reading the token using the token and based on its value - call the next action.

Try this example: http://jsbin.com/puniquduqa/1/edit?js,console,output

+2
source

The work done here https://github.com/felixhao28/JSCPP is very useful, it uses generators and works in both Chrome and Firefox.

+1
source

Source: https://habr.com/ru/post/988673/


All Articles