Js interpreter running extra steps

107 views
Skip to first unread message

Usaid Khan

unread,
Jul 13, 2023, 6:06:11 AM7/13/23
to Blockly
Hello,

On workspace, I have connected three blocks i.e three functions/steps for js-interpreter to interpret.

Generated Javascript Code:
rightMovement()
downMovement()
downMovement().

When I send this Javascript code to interpreter it should run 3 steps but instead it is running so many steps which I am not understanding why. You can see below.

 const runNextStep = () => {
        console.log('steps')
        if (myInterpreter.step()) {
            stepPID = setTimeout(runNextStep, 10);
        } else {
            console.log('no more steps')
        }
}

const executeCode = () => {
        console.log('code', javascriptCode)
        try {
            myInterpreter = new Interpreter(javascriptCode, initJSInterpreterApi);
            runNextStep();
        } catch (error: any) {
        console.log('error', error)
}
}


output:

jserror.png




Neil Fraser

unread,
Jul 13, 2023, 6:38:42 AM7/13/23
to blo...@googlegroups.com
Working as intended.  Allow me to explain first, then provide an option for obtaining your expected result.

A step in the JS-Interpreter is very fine-grained as it walks the abstract syntax tree.  There should be about 22 steps in your three-line program.  These steps are:
  1. Program (looking up the first line)
  2. Expression Statement (starting the line 'rightMovement()')
  3. Call Expression (starting the function call 'rightMovement()')
  4. Identifier (figuring out what 'rightMovement' refers to)
  5. Call Expression (running the function call 'rightMovement()')
  6. Call Expression (recording the return value of 'rightMovement()')
  7. Expression Statement (finishing the line 'rightMovement()')
  8. Program (looking up the next line)
  9. ...
And so on for all three lines.  It's worth noting that JS-Interpreter is a JavaScript interpreter, not a Blockly interpreter.  So it takes a very pedantic approach to execution.  There are a lot of steps which need to be taken to account for scopes, return values, arguments, etc -- even if your specific program doesn't have any of these.

I'll assume that you really want a user-visible 'step' button that executes one statement block.  Here's a demo:
This demo uses STATEMENT_PREFIX to inject a 'highlightBlock()' function call into the generated code before every line.  This function (which is defined as a native function in the interpreter's setup) does two things.  First it (as the name suggests) highlights the block which is about to execute.  Second, it sets a variable which is used to pause the JS-Interpreter.  Thus the JS-Interpreter runs as many steps as it takes until the variable is set, signaling that the next block has been reached.

In your case you may wish to use STATEMENT_SUFFIX instead of STATEMENT_PREFIX since I suspect you want the movement block to execute, then pause, rather than the reverse.

Your generated code would then look something like this:
rightMovement();
highlightBlock('rs?33k~.t,ER:/)k04n$');
downMovement();
highlightBlock('n!Y|ZMCO4H!fV,EtFd%L');
downMovement();
highlightBlock('j,]w1G6}LI!bILEqTV;_');

Any questions, let us know!

--
You received this message because you are subscribed to the Google Groups "Blockly" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blockly+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/blockly/919c6fd3-4ae2-46fc-b516-45684bae9a26n%40googlegroups.com.


--
Neil Fraser, Switzerland
https://neil.fraser.name

Usaid Khan

unread,
Jul 13, 2023, 12:11:59 PM7/13/23
to Blockly
Thank you Neil, I understood now. But then how can we Handle this case like for example I am implemented maze game

jserror2.png

In this game, level will be cleared when cucumber hit the circular green image (destination). 
suppose we have attached all the blocks according to maze and now cucumber is standing here:

jserror3.png

I only need a right block to clear the stage but here is the case:
When I attached the right block, rightMovement() function will be called which will move the cucumber to right side and make the condition check the condition: 
If(cucumber hits the destination and there are no more blocks ahead){
    level cleared
}else{
    level failed
}

and I am implementing this condition like this

const runNextStep = () => {
        if (myInterpreter.step()) {
            if (cucumberHitsDestination && !myInterpreter.step()) {
//level cleared
                } else {
                    stepPID = setTimeout(runNextStep, 10);
                }
            }
        } else {
//level failed
}
}


but this will not work as you mentioned function calling is the 5th step in interpreter syntax-tree and cucumberHitsDestination becomes true on 5th step but there are still 3 steps residing in interpreter which will make this condition always false.

in order to achieve what I want I update this condition like this:

if (resultPopUp.style.visibility === 'visible') {
                 
const runNextStep = () => {
        if (myInterpreter.step()) {
            if (cucumberHitsDestination && myInterpreter.step() && myInterpreter.step() && myInterpreter.step() && myInterpreter.step()) {
//level failed
                } else {
stepPID = setTimeout(runNextStep, 10);
                }
            }
        } else if (cucumberHitsDestination) {
//level cleared
}else{
//level failed
}
}

I added four times myInterpreter.step() which will cover 5th, 6th, 7th and 8th step of interpreter tree and if 8th step still exists then it means there is another block attached ahead. if 8th step does not exist then there is no more block ahead and level is cleared.

but this approach is not good even though it is working for me. but what would be the neat solution?

Neil Fraser

unread,
Jul 14, 2023, 10:11:26 AM7/14/23
to blo...@googlegroups.com
You don't want to be stepping a set number of times:
  myInterpreter.step() && myInterpreter.step() && myInterpreter.step()
There's no guarantee that every line will take the same number of steps.  This approach will get out of sync pretty quickly.

Instead you want to use the technique I outlined in my previous email.  Specifically:

function initApi(interpreter, globalObject) {
  // Add an API function for highlighting blocks.
  const wrapperHighlight = function(id) {
    return highlightBlock(
String(id || ''));
  };
  interpreter.setProperty(globalObject, 'highlightBlock',
      interpreter.createNativeFunction(wrapperHighlight));
  // Your APIs for rightMovement etc would go here.
}

let highlightPause = false;

// Run one statement block.
// If there is more code, continue after 1s.
function runNextBlock() {
  let hasMoreCode = false;
  do {
    // Keep stepping until we finish the current statement.
    hasMoreCode = myInterpreter.step();
  } while (hasMoreCode && !highlightPause);

  if (hasMoreCode) {
    setTimeout(runNextBlock, 1000);
  } else if (isCucumberOnDestination()) {
    // All code executed.  Level won.
  } else {
    // All code executed.  Level lost.
  }
}

// Highlight the block that just ran.  Set flag to pause here.
function highlightBlock(id) {
  Blockly.getMainWorkspace().highlightBlock(id);
  highlightPause = true;
}

function isCucumberOnDestination() {
  // Returns true if the cucumber is on the destination square.
}

javascript.javascriptGenerator.STATEMENT_SUFFIX = 'highlightBlock(%1);\n';
javascript.javascriptGenerator.addReservedWords('highlightBlock');

let myInterpreter = null;

// Run the entire program.
function run() {
  const code = javascript.javascriptGenerator.workspaceToCode(Blockly.getMainWorkspace());
  myInterpreter = new Interpreter(code, initApi);
  highlightPause = false;
  runNextBlock();
}

Usaid Khan

unread,
Jul 17, 2023, 6:59:12 AM7/17/23
to Blockly
Thank you, got it (y)
Reply all
Reply to author
Forward
0 new messages