Prefix and suffix edge cases for flow statements.

Call suffix code on break/continue before executing the break/continue.
Call prefix code for enclosing loop before executing continue.
This commit is contained in:
Neil Fraser
2019-05-13 15:25:52 -07:00
committed by Neil Fraser
parent 0259f8bb48
commit 25adb40e66
16 changed files with 131 additions and 42 deletions

View File

@@ -258,7 +258,7 @@ Blockly.Constants.Loops.CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN = {
* @this Blockly.Block * @this Blockly.Block
*/ */
customContextMenu: function(options) { customContextMenu: function(options) {
if (this.isInFlyout){ if (this.isInFlyout) {
return; return;
} }
var variable = this.getField('VAR').getVariable(); var variable = this.getField('VAR').getVariable();
@@ -303,7 +303,24 @@ Blockly.Constants.Loops.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = {
* Blockly.Constants.Loops.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN.LOOP_TYPES.push('custom_loop'); * Blockly.Constants.Loops.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN.LOOP_TYPES.push('custom_loop');
*/ */
LOOP_TYPES: ['controls_repeat', 'controls_repeat_ext', 'controls_forEach', LOOP_TYPES: ['controls_repeat', 'controls_repeat_ext', 'controls_forEach',
'controls_for', 'controls_whileUntil'], 'controls_for', 'controls_whileUntil'],
/**
* Is the given block enclosed (at any level) by a loop?
* @param {!Blockly.Block} block Current block.
* @return {Blockly.Block} The nearest surrounding loop, or null if none.
*/
getSurroundLoop: function(block) {
// Is the block nested in a loop?
do {
if (Blockly.Constants.Loops.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN.LOOP_TYPES
.indexOf(block.type) != -1) {
return block;
}
block = block.getSurroundParent();
} while (block);
return null;
},
/** /**
* Called whenever anything on the workspace changes. * Called whenever anything on the workspace changes.
@@ -315,17 +332,8 @@ Blockly.Constants.Loops.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = {
if (!this.workspace.isDragging || this.workspace.isDragging()) { if (!this.workspace.isDragging || this.workspace.isDragging()) {
return; // Don't change state at the start of a drag. return; // Don't change state at the start of a drag.
} }
var legal = false; if (Blockly.Constants.Loops.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN
// Is the block nested in a loop? .getSurroundLoop(this)) {
var block = this;
do {
if (this.LOOP_TYPES.indexOf(block.type) != -1) {
legal = true;
break;
}
block = block.getSurroundParent();
} while (block);
if (legal) {
this.setWarningText(null); this.setWarningText(null);
if (!this.isInFlyout) { if (!this.isInFlyout) {
this.setEnabled(true); this.setEnabled(true);

View File

@@ -349,7 +349,7 @@ Blockly.Blocks['procedures_defnoreturn'] = {
* @this Blockly.Block * @this Blockly.Block
*/ */
customContextMenu: function(options) { customContextMenu: function(options) {
if (this.isInFlyout){ if (this.isInFlyout) {
return; return;
} }
// Add option to create caller. // Add option to create caller.
@@ -489,7 +489,7 @@ Blockly.Blocks['procedures_mutatorcontainer'] = {
} }
return; return;
} }
if (event.type != Blockly.Events.BLOCK_CREATE) { if (event.type != Blockly.Events.BLOCK_CREATE) {
return; return;
} }

View File

@@ -100,7 +100,7 @@ Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = {
* @this Blockly.Block * @this Blockly.Block
*/ */
customContextMenu: function(options) { customContextMenu: function(options) {
if (!this.isInFlyout){ if (!this.isInFlyout) {
// Getter blocks have the option to create a setter block, and vice versa. // Getter blocks have the option to create a setter block, and vice versa.
if (this.type == 'variables_get') { if (this.type == 'variables_get') {
var opposite_type = 'variables_set'; var opposite_type = 'variables_set';
@@ -123,7 +123,7 @@ Blockly.Constants.Variables.CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = {
options.push(option); options.push(option);
// Getter blocks have the option to rename or delete that variable. // Getter blocks have the option to rename or delete that variable.
} else { } else {
if (this.type == 'variables_get' || this.type == 'variables_get_reporter'){ if (this.type == 'variables_get' || this.type == 'variables_get_reporter') {
var renameOption = { var renameOption = {
text: Blockly.Msg.RENAME_VARIABLE, text: Blockly.Msg.RENAME_VARIABLE,
enabled: true, enabled: true,

View File

@@ -705,7 +705,7 @@ Blockly.setTheme = function(theme) {
Blockly.refreshTheme_ = function(ws) { Blockly.refreshTheme_ = function(ws) {
// Update all blocks in workspace that have a style name. // Update all blocks in workspace that have a style name.
this.updateBlockStyles_(ws.getAllBlocks().filter( this.updateBlockStyles_(ws.getAllBlocks().filter(
function(block){ function(block) {
return block.getStyleName() !== undefined; return block.getStyleName() !== undefined;
} }
)); ));

View File

@@ -73,7 +73,7 @@ Blockly.Extensions.register = function(name, initFn) {
* registered. * registered.
*/ */
Blockly.Extensions.registerMixin = function(name, mixinObj) { Blockly.Extensions.registerMixin = function(name, mixinObj) {
if (!mixinObj || typeof mixinObj != 'object'){ if (!mixinObj || typeof mixinObj != 'object') {
throw Error('Error: Mixin "' + name + '" must be a object'); throw Error('Error: Mixin "' + name + '" must be a object');
} }
Blockly.Extensions.register(name, function() { Blockly.Extensions.register(name, function() {

View File

@@ -343,7 +343,7 @@ Blockly.Generator.prototype.addLoopTrap = function(branch, block) {
Blockly.Generator.prototype.injectId = function(msg, block) { Blockly.Generator.prototype.injectId = function(msg, block) {
var id = block.id.replace(/\$/g, '$$$$'); // Issue 251. var id = block.id.replace(/\$/g, '$$$$'); // Issue 251.
return msg.replace(/%1/g, '\'' + id + '\''); return msg.replace(/%1/g, '\'' + id + '\'');
} };
/** /**
* Comma-separated list of reserved words. * Comma-separated list of reserved words.

View File

@@ -416,7 +416,7 @@ Blockly.Mutator.prototype.dispose = function() {
Blockly.Mutator.prototype.updateBlockStyle = function() { Blockly.Mutator.prototype.updateBlockStyle = function() {
var ws = this.workspace_; var ws = this.workspace_;
if (ws && ws.getAllBlocks()){ if (ws && ws.getAllBlocks()) {
var workspaceBlocks = ws.getAllBlocks(); var workspaceBlocks = ws.getAllBlocks();
for (var i = 0; i < workspaceBlocks.length; i++) { for (var i = 0; i < workspaceBlocks.length; i++) {
var block = workspaceBlocks[i]; var block = workspaceBlocks[i];

View File

@@ -387,7 +387,7 @@ Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) {
* @param {string} categoryName Name of the toolbox category. * @param {string} categoryName Name of the toolbox category.
* @private * @private
*/ */
Blockly.Toolbox.prototype.setColour_ = function(colourValue, childOut, categoryName){ Blockly.Toolbox.prototype.setColour_ = function(colourValue, childOut, categoryName) {
// Decode the colour for any potential message references // Decode the colour for any potential message references
// (eg. `%{BKY_MATH_HUE}`). // (eg. `%{BKY_MATH_HUE}`).
var colour = Blockly.utils.replaceMessageReferences(colourValue); var colour = Blockly.utils.replaceMessageReferences(colourValue);
@@ -416,7 +416,7 @@ Blockly.Toolbox.prototype.setColour_ = function(colourValue, childOut, categoryN
* @param {string} categoryName Name of the toolbox category. * @param {string} categoryName Name of the toolbox category.
*/ */
Blockly.Toolbox.prototype.setColourFromStyle_ = function( Blockly.Toolbox.prototype.setColourFromStyle_ = function(
styleName, childOut, categoryName){ styleName, childOut, categoryName) {
childOut.styleName = styleName; childOut.styleName = styleName;
if (styleName && Blockly.getTheme()) { if (styleName && Blockly.getTheme()) {
var style = Blockly.getTheme().getCategoryStyle(styleName); var style = Blockly.getTheme().getCategoryStyle(styleName);

View File

@@ -244,8 +244,8 @@ Blockly.Touch.splitEventByTouches = function(e) {
type: e.type, type: e.type,
changedTouches: [e.changedTouches[i]], changedTouches: [e.changedTouches[i]],
target: e.target, target: e.target,
stopPropagation: function(){ e.stopPropagation(); }, stopPropagation: function() { e.stopPropagation(); },
preventDefault: function(){ e.preventDefault(); } preventDefault: function() { e.preventDefault(); }
}; };
events[i] = newEvent; events[i] = newEvent;
} }

View File

@@ -153,11 +153,27 @@ Blockly.Dart['controls_forEach'] = function(block) {
Blockly.Dart['controls_flow_statements'] = function(block) { Blockly.Dart['controls_flow_statements'] = function(block) {
// Flow statements: continue, break. // Flow statements: continue, break.
switch (block.getFieldValue('FLOW')) { var flowType = block.getFieldValue('FLOW');
var xfix = '';
if (Blockly.Dart.STATEMENT_SUFFIX) {
// Inject any statement suffix here since the regular one at the end
// will not get executed if the break/continue is triggered.
xfix += Blockly.Dart.injectId(Blockly.Dart.STATEMENT_SUFFIX, block);
}
if (Blockly.Dart.STATEMENT_PREFIX && flowType == 'CONTINUE') {
var loop = Blockly.Constants.Loops
.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN.getSurroundLoop(block);
if (loop) {
// Inject loop's statement prefix here since the regular one at the end
// of the loop will not get executed if the continue is triggered.
xfix += Blockly.Dart.injectId(Blockly.Dart.STATEMENT_PREFIX, loop);
}
}
switch (flowType) {
case 'BREAK': case 'BREAK':
return 'break;\n'; return xfix + 'break;\n';
case 'CONTINUE': case 'CONTINUE':
return 'continue;\n'; return xfix + 'continue;\n';
} }
throw Error('Unknown flow statement.'); throw Error('Unknown flow statement.');
}; };

View File

@@ -165,11 +165,29 @@ Blockly.JavaScript['controls_forEach'] = function(block) {
Blockly.JavaScript['controls_flow_statements'] = function(block) { Blockly.JavaScript['controls_flow_statements'] = function(block) {
// Flow statements: continue, break. // Flow statements: continue, break.
switch (block.getFieldValue('FLOW')) { var flowType = block.getFieldValue('FLOW');
var xfix = '';
if (Blockly.JavaScript.STATEMENT_SUFFIX) {
// Inject any statement suffix here since the regular one at the end
// will not get executed if the break/continue is triggered.
xfix += Blockly.JavaScript.injectId(Blockly.JavaScript.STATEMENT_SUFFIX,
block);
}
if (Blockly.JavaScript.STATEMENT_PREFIX && flowType == 'CONTINUE') {
var loop = Blockly.Constants.Loops
.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN.getSurroundLoop(block);
if (loop) {
// Inject loop's statement prefix here since the regular one at the end
// of the loop will not get executed if the continue is triggered.
xfix += Blockly.JavaScript.injectId(Blockly.JavaScript.STATEMENT_PREFIX,
loop);
}
}
switch (flowType) {
case 'BREAK': case 'BREAK':
return 'break;\n'; return xfix + 'break;\n';
case 'CONTINUE': case 'CONTINUE':
return 'continue;\n'; return xfix + 'continue;\n';
} }
throw Error('Unknown flow statement.'); throw Error('Unknown flow statement.');
}; };

View File

@@ -106,7 +106,6 @@ Blockly.JavaScript['procedures_ifreturn'] = function(block) {
if (Blockly.JavaScript.STATEMENT_SUFFIX) { if (Blockly.JavaScript.STATEMENT_SUFFIX) {
// Inject any statement suffix here since the regular one at the end // Inject any statement suffix here since the regular one at the end
// will not get executed if the return is triggered. // will not get executed if the return is triggered.
var id = block.id.replace(/\$/g, '$$$$'); // Issue 251.
code += Blockly.JavaScript.prefixLines( code += Blockly.JavaScript.prefixLines(
Blockly.JavaScript.injectId(Blockly.JavaScript.STATEMENT_SUFFIX, block), Blockly.JavaScript.injectId(Blockly.JavaScript.STATEMENT_SUFFIX, block),
Blockly.JavaScript.INDENT); Blockly.JavaScript.INDENT);

View File

@@ -49,6 +49,7 @@ Blockly.Lua.CONTINUE_STATEMENT = 'goto continue\n';
*/ */
Blockly.Lua.addContinueLabel_ = function(branch) { Blockly.Lua.addContinueLabel_ = function(branch) {
if (branch.indexOf(Blockly.Lua.CONTINUE_STATEMENT) != -1) { if (branch.indexOf(Blockly.Lua.CONTINUE_STATEMENT) != -1) {
// False positives are possible (e.g. a string literal), but are harmless.
return branch + Blockly.Lua.INDENT + '::continue::\n'; return branch + Blockly.Lua.INDENT + '::continue::\n';
} else { } else {
return branch; return branch;
@@ -156,11 +157,27 @@ Blockly.Lua['controls_forEach'] = function(block) {
Blockly.Lua['controls_flow_statements'] = function(block) { Blockly.Lua['controls_flow_statements'] = function(block) {
// Flow statements: continue, break. // Flow statements: continue, break.
switch (block.getFieldValue('FLOW')) { var flowType = block.getFieldValue('FLOW');
var xfix = '';
if (Blockly.Lua.STATEMENT_SUFFIX) {
// Inject any statement suffix here since the regular one at the end
// will not get executed if the break/continue is triggered.
xfix += Blockly.Lua.injectId(Blockly.Lua.STATEMENT_SUFFIX, block);
}
if (Blockly.Lua.STATEMENT_PREFIX && flowType == 'CONTINUE') {
var loop = Blockly.Constants.Loops
.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN.getSurroundLoop(block);
if (loop) {
// Inject loop's statement prefix here since the regular one at the end
// of the loop will not get executed if the continue is triggered.
xfix += Blockly.Lua.injectId(Blockly.Lua.STATEMENT_PREFIX, loop);
}
}
switch (flowType) {
case 'BREAK': case 'BREAK':
return 'break\n'; return xfix + 'break\n';
case 'CONTINUE': case 'CONTINUE':
return Blockly.Lua.CONTINUE_STATEMENT; return xfix + Blockly.Lua.CONTINUE_STATEMENT;
} }
throw Error('Unknown flow statement.'); throw Error('Unknown flow statement.');
}; };

View File

@@ -154,11 +154,27 @@ Blockly.PHP['controls_forEach'] = function(block) {
Blockly.PHP['controls_flow_statements'] = function(block) { Blockly.PHP['controls_flow_statements'] = function(block) {
// Flow statements: continue, break. // Flow statements: continue, break.
switch (block.getFieldValue('FLOW')) { var flowType = block.getFieldValue('FLOW');
var xfix = '';
if (Blockly.PHP.STATEMENT_SUFFIX) {
// Inject any statement suffix here since the regular one at the end
// will not get executed if the break/continue is triggered.
xfix += Blockly.PHP.injectId(Blockly.PHP.STATEMENT_SUFFIX, block);
}
if (Blockly.PHP.STATEMENT_PREFIX && flowType == 'CONTINUE') {
var loop = Blockly.Constants.Loops
.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN.getSurroundLoop(block);
if (loop) {
// Inject loop's statement prefix here since the regular one at the end
// of the loop will not get executed if the continue is triggered.
xfix += Blockly.PHP.injectId(Blockly.PHP.STATEMENT_PREFIX, loop);
}
}
switch (flowType) {
case 'BREAK': case 'BREAK':
return 'break;\n'; return xfix + 'break;\n';
case 'CONTINUE': case 'CONTINUE':
return 'continue;\n'; return xfix + 'continue;\n';
} }
throw Error('Unknown flow statement.'); throw Error('Unknown flow statement.');
}; };

View File

@@ -127,7 +127,6 @@ Blockly.PHP['procedures_ifreturn'] = function(block) {
if (Blockly.PHP.STATEMENT_SUFFIX) { if (Blockly.PHP.STATEMENT_SUFFIX) {
// Inject any statement suffix here since the regular one at the end // Inject any statement suffix here since the regular one at the end
// will not get executed if the return is triggered. // will not get executed if the return is triggered.
var id = block.id.replace(/\$/g, '$$$$'); // Issue 251.
code += Blockly.PHP.prefixLines( code += Blockly.PHP.prefixLines(
Blockly.PHP.injectId(Blockly.PHP.STATEMENT_SUFFIX, block), Blockly.PHP.injectId(Blockly.PHP.STATEMENT_SUFFIX, block),
Blockly.PHP.INDENT); Blockly.PHP.INDENT);

View File

@@ -197,11 +197,27 @@ Blockly.Python['controls_forEach'] = function(block) {
Blockly.Python['controls_flow_statements'] = function(block) { Blockly.Python['controls_flow_statements'] = function(block) {
// Flow statements: continue, break. // Flow statements: continue, break.
switch (block.getFieldValue('FLOW')) { var flowType = block.getFieldValue('FLOW');
var xfix = '';
if (Blockly.Python.STATEMENT_SUFFIX) {
// Inject any statement suffix here since the regular one at the end
// will not get executed if the break/continue is triggered.
xfix += Blockly.Python.injectId(Blockly.Python.STATEMENT_SUFFIX, block);
}
if (Blockly.Python.STATEMENT_PREFIX && flowType == 'CONTINUE') {
var loop = Blockly.Constants.Loops
.CONTROL_FLOW_IN_LOOP_CHECK_MIXIN.getSurroundLoop(block);
if (loop) {
// Inject loop's statement prefix here since the regular one at the end
// of the loop will not get executed if the continue is triggered.
xfix += Blockly.Python.injectId(Blockly.Python.STATEMENT_PREFIX, loop);
}
}
switch (flowType) {
case 'BREAK': case 'BREAK':
return 'break\n'; return xfix + 'break\n';
case 'CONTINUE': case 'CONTINUE':
return 'continue\n'; return xfix + 'continue\n';
} }
throw Error('Unknown flow statement.'); throw Error('Unknown flow statement.');
}; };