Step by step with await function

424 views
Skip to first unread message

C Ben

unread,
Apr 15, 2021, 5:49:38 PM4/15/21
to Blockly

Dear Blockly's users,
I produce javascript program with Blockly and I'd like to implement a step by step debugger similar to the codelabs example ( https://google.github.io/blockly-samples/examples/interpreter-demo/step-execution.html ).
From my side, the main difference is that the code generated by Blockly is run into the native browser and not in the JS interpreter (because I have to import external JS libraries to run the code).

To implement my step by step debugger, I've chosen to use the STATEMENT_SUFFIX with an await function :
Blockly.JavaScript.STATEMENT_SUFFIX = `workspace.highlightBlock(%1);\n
await btnClick(runNextStep); \n`;

The "await" waits for the debugger button "Next Step" to be clicked. After a click on it, the program continues to the next block and wait when another await is met etc.
It works very well but I still face an issue with function() inside generated code :

below an example with simplified code generated with my debug mode :
(async() => {
//display function generated by a display block
function display() {
   alert('Hello again')
   await btnClick(runNextStep);
}

alert('hello');
await btnClick(runNextStep);
display();
})();

All Blockly.JavaScript.STATEMENT_SUFFIX are placed in every block generations. But in this case, it causes an error when it is added into inline block generator : "await is only valid in async functions and async generators", which is normal because display() function is not async, the top level function is the only one async function.
How could I control statement_suffix more precisely or just add suffix in my top level script to avoid this issue.
I may not follow the right approach at all to do that, so feel free to propose another one ;)
Thanks a lot,

Chris

C Ben

unread,
Apr 16, 2021, 4:37:38 AM4/16/21
to Blockly
I come back with a beginning of solution. I've figured out by looking in blockly src (generator.js) and at the end it is documented here ( https://developers.google.com/blockly/reference/js/Blockly.Block#suppressPrefixSuffix ), the suppressPrefixSuffix attribute of a block.

Thus, I could update my block generators to set block.suppressPrefixSuffix = true to not add a suffix to the block if one of its parent is not a statement block (it turns out that blockly will not add the suffix if the code generated will be a inside a function).
It works well with my own blocks but how can I tell to standard Blockly's block to have the same behaviour : e.g the "for" loop block ?
Thanks,
Chris

Beka Westberg

unread,
Apr 16, 2021, 3:47:04 PM4/16/21
to blo...@googlegroups.com
Hello,

I think that your best bet would be to monkey-patch the core blocks (not something I generally recommend, but I think it would be safe here).

I'm not exactly sure what you mean by "I could update my block generators to set block.suppressPrefixSuffix = true to not add a suffix to the block if one of its parent is not a statement block" so I'll go over both possibilities =)

If you want to set the .suppressPrefixSuffix property to be true for all "for" blocks (for example) you can patch the block definition. For example:
```
// Put this somewhere near the rest of your block definitions.
// Should run *after* you load the built-in block definitions.
Blockly.Blocks['controls_repeat_ext'].supressPrefixSuffix = true;
```

If you want to set the .suppressPrefixSuffix property from within the block-code generator, you can just override the built-in generator to add that. For example:
```
Blockly.JavaScript['controls_repeat_ext'] = function(block) {
  if (block.getField('TIMES')) {
    var repeats = String(Number(block.getFieldValue('TIMES')));
  } else {
    var repeats = Blockly.JavaScript.valueToCode(block, 'TIMES',
        Blockly.JavaScript.ORDER_ASSIGNMENT) || '0';
  }
  // etc...
  code += 'for (var ' + loopVar + ' = 0; ' +
      loopVar + ' < ' + endVar + '; ' +
      loopVar + '++) {\n' +
      branch + '}\n';
  // Add your custom supressPrefixSuffix logic.
  return code;
};
```

I hope that helps! If you have any further questions (or if I didn't understand your original question haha) please reply!
--Beka


--
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/14ea38fc-4d81-4f19-beb2-892e94c3eb6bn%40googlegroups.com.

Maribeth Bottorff

unread,
Apr 16, 2021, 6:36:29 PM4/16/21
to Blockly
Thanks, Beka!

One thing I wanted to add is that running untrusted code directly in the browser (not sandboxed) is a pretty dangerous idea. If you wanted to avoid that and stick with js-interpreter, there is a way to use external libraries with js-interpreter. The documentation is a little sparse, but there's some info here under External APIs, and you can always ask over at that repo if you need additional help.

But if you continue with changing the code generator, in addition to Beka's ideas above, you may want to look into changing the generator itself. e.g. you could look at doing this work possibly in `scrub_` (in `generators/javascript.js`) or `statementToCode` or `valueToCode` (in `core/generator.js`). This may be easier than having to override every problematic block.

If you haven't already, the Custom Generator codelab might be useful to learn how these functions work. Good luck!

Maribeth

C Ben

unread,
Apr 17, 2021, 8:50:10 AM4/17/21
to Blockly
Thanks a lot Beka and Maribeth,
Why do you think running code directly in the browser is a dangerous idea ? The code generated comes only from my blockly authoring tool and I assume that it could raise bug but they will be limited to the webpage context, no ? The reason why I've chosen this approach in the beginning is because I need to modify the current DOM when I run the code, and I use JS ES6 ES7 commands as well. More over some quite heavy libraries as MathJax ... It seems to be difficult to do that with JSInterpreter, but I may miss something. If you think it's feasible, I will reinvestigate JS interpreter option.

Nevertheless, concerning my initial issue, I think I've figured out a means to do it simply without tweak too much my own and blockly default blocks.
Thus, I've slightly modified  the blockly generator.js from blockly/core. All suffixes and prefixes are, in the end, apply to the code, thanks to injectId function. Hence, I've modified injectId function as follows :

Blockly.Generator.prototype.injectId = function (msg, block) {
if (!hasAscendantValueBlock(block)) {
var id = block.id.replace(/\$/g, "$$$$"); // Issue 251.
return msg.replace(/%1/g, "'" + id + "'");
} else {
return "";
}
};

So, it doesn't add the suffix or prefix, if the concerned block has at least one ascendant as a value Block (because if one of its parent is an inline block the await suffix will be included in a function(), which raises an error : I explained it in my first message).

For info, I share with you the function hasAscendantValueBlock I've implemented. By the way it could be useful to have this kind of function in Block class :  It searches recursively from all ascendants and test is parents have a previousBlock() : which means it's a value block.

function hasAscendantValueBlock(block) {
if (block != null) {
if (!block.getPreviousBlock()) {
   return true;
} else {
   return hasAscendantValueBlock(block.getSurroundParent());
}
} else {
   return false;
}
}

Kind regards,
Chris
Reply all
Reply to author
Forward
0 new messages