Unverified Commit d0b152f3 authored by Beka Westberg's avatar Beka Westberg Committed by GitHub

Add for each in dictionary block (#2026)

parent 621acf15
......@@ -269,6 +269,8 @@ Blockly.procedureParameterPrefix = "input"; // For names introduced by procedure
Blockly.handlerParameterPrefix = "input"; // For names introduced by event handlers
Blockly.localNamePrefix = "local"; // For names introduced by local variable declarations
Blockly.loopParameterPrefix = "item"; // For names introduced by for loops
Blockly.loopKeyParameterPrefix = 'key'; // For keys introduced by dict for loops.
Blockly.loopValueParameterPrefix = 'value'; // For values introduced by dict for loops.
Blockly.loopRangeParameterPrefix = "counter"; // For names introduced by for range loops
Blockly.menuSeparator = " "; // Separate prefix from name with this. E.g., space in "param x"
......
......@@ -416,6 +416,129 @@ Blockly.Blocks['controls_forEach'] = {
typeblock: [{translatedName: Blockly.Msg.LANG_CONTROLS_FOREACH_INPUT_ITEM}]
};
Blockly.Blocks['controls_for_each_dict'] = {
category: 'Control',
helpUrl: Blockly.Msg.LANG_CONTROLS_FOREACH_DICT_HELPURL,
init: function() {
this.setColour(Blockly.CONTROL_CATEGORY_HUE);
var checkTypeDict = Blockly.Blocks.Utilities.YailTypeToBlocklyType(
'dictionary', Blockly.Blocks.Utilities.INPUT);
var keyField = new Blockly.FieldParameterFlydown(
Blockly.Msg.LANG_CONTROLS_FOREACH_DICT_INPUT_KEY,
true, Blockly.FieldFlydown.DISPLAY_BELOW);
var valueField = new Blockly.FieldParameterFlydown(
Blockly.Msg.LANG_CONTROLS_FOREACH_DICT_INPUT_VALUE,
true, Blockly.FieldFlydown.DISPLAY_BELOW);
this.interpolateMsg(Blockly.Msg.LANG_CONTROLS_FOREACH_DICT_INPUT,
['KEY', keyField],
['VALUE', valueField],
['DICT', checkTypeDict, Blockly.ALIGN_LEFT],
Blockly.ALIGN_LEFT);
this.appendStatementInput('DO')
.appendField(Blockly.Msg.LANG_CONTROLS_FOREACH_DICT_INPUT_DO);
this.setInputsInline(false);
this.setPreviousStatement(true);
this.setNextStatement(true);
this.setTooltip(Blockly.Msg.LANG_CONTROLS_FOREACH_DICT_TOOLTIP);
},
getVars: function () {
return [this.getFieldValue('KEY'), this.getFieldValue('VALUE')];
},
blocksInScope: function () {
var doBlock = this.getInputTargetBlock('DO');
if (doBlock) {
return [doBlock];
} else {
return [];
}
},
declaredNames: function () {
return [this.getFieldValue('KEY'), this.getFieldValue('VALUE')];
},
renameVar: function (oldName, newName) {
if (Blockly.Names.equals(oldName, this.getFieldValue('KEY'))) {
this.setFieldValue(newName, 'KEY');
}
if (Blockly.Names.equals(oldName, this.getFieldValue('VALUE'))) {
this.setFieldValue(newName, 'VALUE');
}
},
renameBound: function (boundSubstitution, freeSubstitution) {
Blockly.LexicalVariable.renameFree(
this.getInputTargetBlock('DICT'), freeSubstitution);
var varFieldIds = ['KEY', 'VALUE'];
for (var i = 0, fieldId; (fieldId = varFieldIds[i]); i++) {
var oldVar = this.getFieldValue(fieldId);
var newVar = boundSubstitution.apply(oldVar);
if (newVar !== oldVar) {
this.renameVar(oldVar, newVar);
var keySubstitution = Blockly.Substitution.simpleSubstitution(
oldVar, newVar);
freeSubstitution = freeSubstitution.extend(keySubstitution);
} else {
freeSubstitution = freeSubstitution.remove([oldVar]);
}
}
Blockly.LexicalVariable.renameFree(this.getInputTargetBlock('DO'), modifiedSubstitution);
if (this.nextConnection) {
var nextBlock = this.nextConnection.targetBlock();
Blockly.LexicalVariable.renameFree(nextBlock, freeSubstitution);
}
},
renameFree: function (freeSubstitution) {
var bodyFreeVars = Blockly.LexicalVariable.freeVariables(
this.getInputTargetBlock('DO'));
var varFieldIds = ['KEY', 'VALUE'];
var boundSubstitution = new Blockly.Substitution();
for (var i = 0, fieldId; (fieldId = varFieldIds[i]); i++) {
var oldVar = this.getFieldValue(fieldId);
bodyFreeVars.deleteName(oldVar);
var renamedBodyFreeVars = bodyFreeVars.renamed(freeSubstitution);
if (renamedBodyFreeVars.isMember(oldVar)) {
var newVar = Blockly.FieldLexicalVariable.nameNotIn(
oldVar, renamedBodyFreeVars.toList());
var substitution = Blockly.Substitution.simpleSubstitution(
oldVar, newVar);
boundSubstitution.extend(substitution);
}
}
},
freeVariables: function () { // return the free variables of this block
var result = Blockly.LexicalVariable.freeVariables(
this.getInputTargetBlock('DO'));
// Remove bound variables from body free vars.
result.deleteName(this.getFieldValue('KEY'));
result.deleteName(this.getFieldValue('VALUE'));
result.unite(Blockly.LexicalVariable.freeVariables(
this.getInputTargetBlock('DICT')));
if (this.nextConnection) {
var nextBlock = this.nextConnection.targetBlock();
result.unite(Blockly.LexicalVariable.freeVariables(nextBlock));
}
return result;
},
typeblock: [{translatedName: Blockly.Msg.LANG_CONTROLS_FOREACH_DICT_TITLE}]
};
Blockly.Blocks['controls_while'] = {
// While condition.
category: 'Control',
......
......@@ -93,6 +93,57 @@ Blockly.Yail['controls_forEach'] = function() {
+ listCode + Blockly.Yail.YAIL_CLOSE_COMBINATION;
};
Blockly.Yail['controls_for_each_dict'] = function() {
var yail = Blockly.Yail;
var generator = Blockly.Yail['controls_for_each_dict'];
var prefix = Blockly.usePrefixInYail ? 'local_' : '';
var keyName = yail.YAIL_LOCAL_VAR_TAG + prefix + this.getFieldValue('KEY');
var valueName = yail.YAIL_LOCAL_VAR_TAG + prefix + this.getFieldValue('VALUE');
var loopIndexName = 'item';
var loopIndexCommandAndName = yail.getVariableCommandAndName(loopIndexName);
loopIndexName = loopIndexCommandAndName[1];
var getListCode = loopIndexCommandAndName[0] + loopIndexName +
yail.YAIL_CLOSE_COMBINATION;
var getKeyCode = generator.generateGetListItemCode(getListCode, 1);
var getValueCode = generator.generateGetListItemCode(getListCode, 2);
var setKeyCode = generator.generateSetVarCode(keyName, getKeyCode);
var setValueCode = generator.generateSetVarCode(valueName, getValueCode);
var letCode = yail.YAIL_LET + yail.YAIL_OPEN_COMBINATION + yail.YAIL_SPACER
+ setKeyCode + yail.YAIL_SPACER + setValueCode
+ yail.YAIL_CLOSE_COMBINATION;
var bodyCode = yail.statementToCode(this, 'DO') || yail.YAIL_FALSE;
var dictionaryCode = yail.valueToCode(this, 'DICT', yail.ORDER_NONE)
|| yail.YAIL_EMPTY_DICT;
return yail.YAIL_FOREACH + loopIndexName + yail.YAIL_SPACER
+ letCode + bodyCode + yail.YAIL_CLOSE_COMBINATION
+ yail.YAIL_SPACER + dictionaryCode + yail.YAIL_CLOSE_COMBINATION;
};
Blockly.Yail['controls_for_each_dict'].generateGetListItemCode =
function(getListCode, index) {
var yail = Blockly.Yail;
return yail.YAIL_CALL_YAIL_PRIMITIVE + 'yail-list-get-item' + yail.YAIL_SPACER
+ yail.YAIL_OPEN_COMBINATION + yail.YAIL_LIST_CONSTRUCTOR
+ yail.YAIL_SPACER + getListCode
+ yail.YAIL_SPACER + index.toString() + yail.YAIL_CLOSE_COMBINATION
+ yail.YAIL_SPACER + yail.YAIL_QUOTE + yail.YAIL_OPEN_COMBINATION
+ 'list number' + yail.YAIL_CLOSE_COMBINATION + yail.YAIL_SPACER
+ yail.YAIL_DOUBLE_QUOTE + 'select list item' + yail.YAIL_DOUBLE_QUOTE
+ yail.YAIL_CLOSE_COMBINATION;
};
Blockly.Yail['controls_for_each_dict'].generateSetVarCode =
function(varName, getVarCode) {
var yail = Blockly.Yail;
return yail.YAIL_OPEN_COMBINATION
+ varName + yail.YAIL_SPACER + getVarCode
+ yail.YAIL_CLOSE_COMBINATION;
};
// In general break could take a value to return from the loop, but
// none of our block language loops return values, so we won't use that capability.
......
......@@ -212,6 +212,17 @@ Blockly.Msg.en.switch_language_to_english = {
Blockly.Msg.LANG_CONTROLS_FOREACH_TOOLTIP = 'Runs the blocks in the \'do\' section for each item in '
+ 'the list. Use the given variable name to refer to the current list item.';
Blockly.Msg.LANG_CONTROLS_FOREACH_DICT_HELPURL = '/reference/blocks/control.html#foreachdict';
Blockly.Msg.LANG_CONTROLS_FOREACH_DICT_INPUT = 'for each %1 with %2 in dictionary %3';
Blockly.Msg.LANG_CONTROLS_FOREACH_DICT_INPUT_DO = 'do';
Blockly.Msg.LANG_CONTROLS_FOREACH_DICT_INPUT_KEY = 'key';
Blockly.Msg.LANG_CONTROLS_FOREACH_DICT_INPUT_VALUE = 'value';
// Used by the typeblock system.
Blockly.Msg.LANG_CONTROLS_FOREACH_DICT_TITLE = 'for each in dictionary';
Blockly.Msg.LANG_CONTROLS_FOREACH_DICT_TOOLTIP =
'Runs the blocks in the \'do\' section for each key-value entry in the dictionary.'
+ ' Use the given variable names to refer to the key/value of the current dictionary item.';
Blockly.Msg.LANG_CONTROLS_GET_HELPURL = '/reference/blocks/control.html#get';
......
......@@ -1806,7 +1806,10 @@ Blockly.Versioning.AllUpgradeMaps =
27: "noUpgrade",
// AI2: Added dictionaries
28: "noUpgrade"
28: "noUpgrade",
// AI2: Added "for each in dictionary" block.
29: "noUpgrade"
}, // End Language upgraders
......@@ -1925,7 +1928,7 @@ Blockly.Versioning.AllUpgradeMaps =
// - The FillOpacity and StrokeOpacity properties were added
3: "noUpgrade"
}, // End Marker upgraders
"Polygon": {
// AI2:
// - The FillOpacity and StrokeOpacity properties were added
......@@ -2627,7 +2630,7 @@ Blockly.Versioning.AllUpgradeMaps =
// AI2: Added method XMLTextDecode
4: "noUpgrade",
// AI2: Added method UriDecode
5: "noUpgrade",
......
......@@ -386,10 +386,20 @@ Blockly.WarningHandler.prototype["checkIsNotInLoop"] = function(block) {
}
};
Blockly_loopBlockTypes =
// add more later
["controls_forEach", "controls_forRange", "controls_while"] ;
// TODO: Maybe change this to a property of the block, instead of maintaining
// a list. Check how this interacts with extensions first.
Blockly_loopBlockTypes = [
"controls_forEach",
"controls_for_each_dict",
"controls_forRange",
"controls_while"
];
// TODO: Maybe place this on an object. Options:
// - block.js
// - warningHandler.js
// - a utilities file.
// Check how blockly core handles this.
Blockly_containedInLoop = function(block) {
var enclosingBlock = block.getSurroundParent();
if (enclosingBlock == null) {
......
......@@ -605,6 +605,48 @@ public class YailEvalTest extends TestCase {
}
}
public void testForEachDict() throws Throwable {
/* test for_each_dict block */
String schemeInputString = "(begin " +
"(def x 0) " +
"(foreach y " +
" (let " +
" ( " +
" ($key " +
" (call-yail-primitive yail-list-get-item " +
" (*list-for-runtime* (lexical-value y) 1) '(list number) \"select list item\" " +
" ) " +
" ) " +
" ($value " +
" (call-yail-primitive yail-list-get-item " +
" (*list-for-runtime* (lexical-value y) 2) '(list number) \"select list item\" " +
" ) " +
" ) " +
" ) " +
" (set-var! x " +
" (call-yail-primitive + " +
" (*list-for-runtime* (get-var x) (lexical-value $key) (lexical-value $value) )" +
" '(number number number ) \"+\"" +
" ) " +
" ) " +
" ) " +
" (call-yail-primitive make-yail-dictionary " +
" (*list-for-runtime* " +
" (call-yail-primitive make-dictionary-pair " +
" (*list-for-runtime* 1 2 ) '(key any) \"make a pair\" " +
" ) " +
" (call-yail-primitive make-dictionary-pair " +
" (*list-for-runtime* 3 4 ) '(key any) \"make a pair\" " +
" ) " +
" ) '(pair pair ) \"make a dictionary\"" +
" ) " +
" ) " +
" (get-var x) " +
") ";
String schemeResultString = "10";
assertEquals(schemeResultString, scheme.eval(schemeInputString).toString());
}
public void testForRange() throws Throwable {
/* test forrange */
String schemeInputString = "(begin " +
......
......@@ -493,6 +493,8 @@ public class YaVersion {
// - WEB_COMPONENT_VERSION was incremented to 7
// For YOUNG_ANDROID_VERSION 198:
// - IMAGE_COMPONENT_VERSION was incremented to 4
// For YOUNG_ANDROID)_VERSION 199:
// - BLOCKS_LANGUAGE_VERSION was incremented to 29
public static final int YOUNG_ANDROID_VERSION = 198;
......@@ -568,8 +570,10 @@ public class YaVersion {
// - The text compare block was modified to include the not-equal operator
// For BLOCKS_LANGUAGE_VERSION 28
// - The dictionaries blocks were added.
// For BLOCKS_LANGUAGE_VERSION 29
// - The for-each-in-dictionary block was added.
public static final int BLOCKS_LANGUAGE_VERSION = 28;
public static final int BLOCKS_LANGUAGE_VERSION = 29;
// ................................. Target SDK Version Number ..................................
......
......@@ -129,6 +129,7 @@
<li><a href="#if">if &amp; else if</a></li>
<li><a href="#forrange">for each number from to</a></li>
<li><a href="#foreach">for each item in list</a></li>
<li><a href="#foreachdict">for each key with value in dictionary</a></li>
<li><a href="#while">while</a></li>
<li><a href="#choose">if then else</a></li>
<li><a href="#doreturn">do with result</a></li>
......@@ -168,6 +169,12 @@
<p>Runs the blocks in the do section for each item in the list. Use the given variable name, <code class="variable block highlighter-rouge">item</code>, to refer to the current list item. You can change the name <code class="variable block highlighter-rouge">item</code> to something else if you wish.</p>
<h3 id="foreachdict">for each key with value in dictionary</h3>
<p><img src="images/control/foreachdict.png" alt="" /></p>
<p>Runs the blocks in the do section for each key-value entry in the dictionary. Use the given variables, <code class="variable block highlighter-rouge">key</code> and <code class="variable block highlighter-rouge">value</code>, to refer to the key and value of the current dictionary entry. You can change the names <code class="variable block highlighter-rouge">key</code> and <code class="variable block highlighter-rouge">value</code> to something else if you wish.</p>
<h3 id="while">while</h3>
<p><img src="images/control/while.png" alt="" /></p>
......
......@@ -6,6 +6,7 @@ layout: documentation
* [if & else if](#if)
* [for each number from to](#forrange)
* [for each item in list](#foreach)
* [for each key with value in dictionary](#foreachdict)
* [while](#while)
* [if then else](#choose)
* [do with result](#doreturn)
......@@ -47,6 +48,12 @@ Runs the blocks in the do section for each numeric value in the range starting f
Runs the blocks in the do section for each item in the list. Use the given variable name, `item`{:.variable.block}, to refer to the current list item. You can change the name `item`{:.variable.block} to something else if you wish.
### for each key with value in dictionary {#foreachdict}
![](images/control/foreachdict.png)
Runs the blocks in the do section for each key-value entry in the dictionary. Use the given variables, `key`{:.variable.block} and `value`{:.variable.block}, to refer to the key and value of the current dictionary entry. You can change the names `key`{:.variable.block} and `value`{:.variable.block} to something else if you wish.
### while {#while}
![](images/control/while.png)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment