mirror of
https://github.com/google/blockly.git
synced 2026-01-10 02:17:09 +01:00
Use the Blockly AST for block toString (#3895)
* Use the AST tree to populate block toString, add paranthesis around Number and Boolean connections
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
|
||||
goog.provide('Blockly.Block');
|
||||
|
||||
goog.require('Blockly.ASTNode');
|
||||
goog.require('Blockly.Blocks');
|
||||
goog.require('Blockly.Connection');
|
||||
goog.require('Blockly.Events');
|
||||
@@ -1306,23 +1307,94 @@ Blockly.Block.prototype.setCollapsed = function(collapsed) {
|
||||
Blockly.Block.prototype.toString = function(opt_maxLength, opt_emptyToken) {
|
||||
var text = [];
|
||||
var emptyFieldPlaceholder = opt_emptyToken || '?';
|
||||
for (var i = 0, input; (input = this.inputList[i]); i++) {
|
||||
if (input.name == Blockly.Block.COLLAPSED_INPUT_NAME) {
|
||||
continue;
|
||||
|
||||
// Temporarily set flag to navigate to all fields.
|
||||
var prevNavigateFields = Blockly.ASTNode.NAVIGATE_ALL_FIELDS;
|
||||
Blockly.ASTNode.NAVIGATE_ALL_FIELDS = true;
|
||||
|
||||
var node = Blockly.ASTNode.createBlockNode(this);
|
||||
var rootNode = node;
|
||||
|
||||
/**
|
||||
* Whether or not to add parentheses around an input.
|
||||
* @param {!Blockly.Connection} connection The connection.
|
||||
* @return {boolean} True if we should add parentheses around the input.
|
||||
*/
|
||||
function shouldAddParentheses(connection) {
|
||||
var checks = connection.getCheck();
|
||||
if (!checks && connection.targetConnection) {
|
||||
checks = connection.targetConnection.getCheck();
|
||||
}
|
||||
for (var j = 0, field; (field = input.fieldRow[j]); j++) {
|
||||
text.push(field.getText());
|
||||
return !!checks && (checks.indexOf('Boolean') != -1 ||
|
||||
checks.indexOf('Number') != -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that we haven't circled back to the original root node.
|
||||
*/
|
||||
function checkRoot() {
|
||||
if (node && node.getType() == rootNode.getType() &&
|
||||
node.getLocation() == rootNode.getLocation()) {
|
||||
node = null;
|
||||
}
|
||||
if (input.connection) {
|
||||
var child = input.connection.targetBlock();
|
||||
if (child) {
|
||||
text.push(child.toString(undefined, opt_emptyToken));
|
||||
} else {
|
||||
text.push(emptyFieldPlaceholder);
|
||||
}
|
||||
|
||||
// Traverse the AST building up our text string.
|
||||
while (node) {
|
||||
switch (node.getType()) {
|
||||
case Blockly.ASTNode.types.INPUT:
|
||||
var connection = /** @type {!Blockly.Connection} */ (node.getLocation());
|
||||
if (!node.in()) {
|
||||
text.push(emptyFieldPlaceholder);
|
||||
} else if (shouldAddParentheses(connection)) {
|
||||
text.push('(');
|
||||
}
|
||||
break;
|
||||
case Blockly.ASTNode.types.FIELD:
|
||||
var field = /** @type {Blockly.Field} */ (node.getLocation());
|
||||
if (field.name != Blockly.Block.COLLAPSED_FIELD_NAME) {
|
||||
text.push(field.getText());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
var current = node;
|
||||
node = current.in() || current.next();
|
||||
if (!node) {
|
||||
// Can't go in or next, keep going out until we can go next.
|
||||
node = current.out();
|
||||
checkRoot();
|
||||
while (node && !node.next()) {
|
||||
node = node.out();
|
||||
checkRoot();
|
||||
// If we hit an input on the way up, possibly close out parentheses.
|
||||
if (node && node.getType() == Blockly.ASTNode.types.INPUT &&
|
||||
shouldAddParentheses(
|
||||
/** @type {!Blockly.Connection} */ (node.getLocation()))) {
|
||||
text.push(')');
|
||||
}
|
||||
}
|
||||
if (node) {
|
||||
node = node.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
text = text.join(' ').trim() || '???';
|
||||
|
||||
// Restore state of NAVIGATE_ALL_FIELDS.
|
||||
Blockly.ASTNode.NAVIGATE_ALL_FIELDS = prevNavigateFields;
|
||||
|
||||
// Run through our text array and simplify expression to remove parentheses
|
||||
// around single field blocks.
|
||||
for (var i = 2, l = text.length; i < l; i++) {
|
||||
if (text[i - 2] == '(' && text[i] == ')') {
|
||||
text[i - 2] = text[i - 1];
|
||||
text.splice(i - 1, 2);
|
||||
l -= 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Join the text array, removing spaces around added paranthesis.
|
||||
text = text.join(' ').replace(/(\() | (\))/gmi, '$1$2').trim() || '???';
|
||||
if (opt_maxLength) {
|
||||
// TODO: Improve truncation so that text from this block is given priority.
|
||||
// E.g. "1+2+3+4+5+6+7+8+9=0" should be "...6+7+8+9=0", not "1+2+3+4+5...".
|
||||
|
||||
@@ -1680,4 +1680,116 @@ suite('Blocks', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
suite('toString', function() {
|
||||
var toStringTests = [
|
||||
{
|
||||
name: 'statement block',
|
||||
xml: '<block type="controls_repeat_ext">' +
|
||||
'<value name="TIMES">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">10</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>',
|
||||
toString: 'repeat 10 times do ?',
|
||||
},
|
||||
{
|
||||
name: 'nested statement blocks',
|
||||
xml: '<block type="controls_repeat_ext">' +
|
||||
'<value name="TIMES">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">10</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<statement name="DO">' +
|
||||
'<block type="controls_if"></block>' +
|
||||
'</statement>' +
|
||||
'</block>',
|
||||
toString: 'repeat 10 times do if ? do ?',
|
||||
},
|
||||
{
|
||||
name: 'nested Boolean output blocks',
|
||||
xml: '<block type="controls_if">' +
|
||||
'<value name="IF0">' +
|
||||
'<block type="logic_compare">' +
|
||||
'<field name="OP">EQ</field>' +
|
||||
'<value name="A">' +
|
||||
'<block type="logic_operation">' +
|
||||
'<field name="OP">AND</field>' +
|
||||
'</block>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'</value>' +
|
||||
'</block>',
|
||||
toString: 'if ((? and ?) = ?) do ?',
|
||||
},
|
||||
{
|
||||
name: 'output block',
|
||||
xml: '<block type="math_single">' +
|
||||
'<field name="OP">ROOT</field>' +
|
||||
'<value name="NUM">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">9</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>',
|
||||
toString: 'square root 9',
|
||||
},
|
||||
{
|
||||
name: 'nested Number output blocks',
|
||||
xml: '<block type="math_arithmetic">' +
|
||||
'<field name="OP">ADD</field>' +
|
||||
'<value name="A">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">1</field>' +
|
||||
'</shadow>' +
|
||||
'<block type="math_arithmetic">' +
|
||||
'<field name="OP">MULTIPLY</field>' +
|
||||
'<value name="A">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">10</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="B">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">5</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'</value>' +
|
||||
'<value name="B">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">3</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>',
|
||||
toString: '(10 × 5) + 3',
|
||||
},
|
||||
{
|
||||
name: 'nested String output blocks',
|
||||
xml: '<block type="text_join">' +
|
||||
'<mutation items="2"></mutation>' +
|
||||
'<value name="ADD0">' +
|
||||
'<block type="text">' +
|
||||
'<field name="TEXT">Hello</field>' +
|
||||
'</block>' +
|
||||
'</value>' +
|
||||
'<value name="ADD1">' +
|
||||
'<block type="text">' +
|
||||
'<field name="TEXT">World</field>' +
|
||||
'</block>' +
|
||||
'</value>' +
|
||||
'</block>',
|
||||
toString: 'create text with “ Hello ” “ World ”',
|
||||
},
|
||||
];
|
||||
// Create mocha test cases for each toString test.
|
||||
toStringTests.forEach(function(t) {
|
||||
test(t.name, function() {
|
||||
var block = Blockly.Xml.domToBlock(Blockly.Xml.textToDom(t.xml),
|
||||
this.workspace);
|
||||
chai.assert.equal(block.toString(), t.toString);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<script src="../../generators/php.js"></script>
|
||||
<script src="../../generators/python.js"></script>
|
||||
<script src="../../msg/messages.js"></script>
|
||||
<script src="../../blocks/loops.js"></script>
|
||||
<script src="../../blocks/lists.js"></script>
|
||||
<script src="../../blocks/logic.js"></script>
|
||||
<script src="../../blocks/math.js"></script>
|
||||
|
||||
@@ -669,15 +669,15 @@ function testAWorkspace() {
|
||||
|
||||
test('After dispose', function() {
|
||||
this.blockA.dispose();
|
||||
chai.assert.isNull(this.workspace.getBlockById(this.blockA));
|
||||
chai.assert.isNull(this.workspace.getBlockById(this.blockA.id));
|
||||
chai.assert.equal(
|
||||
this.workspace.getBlockById(this.blockB.id), this.blockB);
|
||||
});
|
||||
|
||||
test('After clear', function() {
|
||||
this.workspace.clear();
|
||||
chai.assert.isNull(this.workspace.getBlockById(this.blockA));
|
||||
chai.assert.isNull(this.workspace.getBlockById(this.blockB));
|
||||
chai.assert.isNull(this.workspace.getBlockById(this.blockA.id));
|
||||
chai.assert.isNull(this.workspace.getBlockById(this.blockB.id));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user