Unverified Commit 52cddaf3 authored by Evan W. Patton's avatar Evan W. Patton Committed by GitHub

Allow and/or blocks to have 0 or 1 inputs (#2220)

* Allow and/or blocks to have 0 or 1 inputs

Change-Id: Ic04587ca376f7ed1bb582d8c273b6f16833ad13d

* Make and/or ops more sane when < 2 args

Change-Id: I87f1fdf91bccb9ead5e786e0006f531c04585cba

* Add identities to math add/multiply with < 2 args

Change-Id: If637a09b447168e578c143b4f57bd990cb4aad8c

* Refactor identity information for logic_operation

Change-Id: I446b9dbc7f82bf165d5751a90297f409a841091b

* Fix missing identities when mutating and/or

Change-Id: I8bb25ca917f28ad054254f7b087dce7fc505698c

* Fix logic bug in logic_or block

Change-Id: Ib070bebc8d1ffb09a002d3e4557648f79cd26103

* Use ±inf.0 for min/max identities

Change-Id: I374ae68f0559483285246e1035fd6f5ddf1674c8

* Render ±infinity as strings rather than min/max long

Change-Id: I82721ab57128c381189c5afe5f1fff0749b3a06c
parent df02bf77
......@@ -169,14 +169,6 @@ Blockly.Blocks['logic_operation'] = {
* @type {Blockly.WorkspaceSvg}
*/
this.lastMutator = null;
// NOTE(ewp): Blockly doesn't trigger the validation function when the field is set during
// load, so we override setValue here to make sure that the additional and/or labels (if
// present) match the dropdown's value.
var oldSetValue = this.opField.setValue;
this.opField.setValue = function(newValue) {
oldSetValue.call(this, newValue);
thisBlock.updateFields(newValue);
};
this.setColour(Blockly.LOGIC_CATEGORY_HUE);
this.setOutput(true, Blockly.Blocks.Utilities.YailTypeToBlocklyType("boolean", Blockly.Blocks.Utilities.OUTPUT));
this.appendValueInput('A')
......@@ -203,13 +195,35 @@ Blockly.Blocks['logic_operation'] = {
}
}
if (this.itemCount_ === 0) {
this.removeInput(this.emptyInputName, true);
}
if (this.itemCount_ > 0) {
this.removeInput('A', true);
}
if (this.itemCount_ > 1) {
this.removeInput('B', true);
}
for (var x = 2; x < this.itemCount_; x++) {
this.removeInput(this.repeatingInputName + x);
this.removeInput(this.repeatingInputName + x, true);
}
this.itemCount_ = window.parseInt(container.getAttribute('items'), 10);
for (var x = 2; x < this.itemCount_; x++) {
for (var x = 0; x < this.itemCount_; x++) {
this.addInput(x);
}
if (this.itemCount_ === 0) {
this.addEmptyInput();
}
// NOTE(ewp): Blockly doesn't trigger the validation function when the field is set during
// load, so we override setValue here to make sure that the additional and/or labels (if
// present) match the dropdown's value.
// TODO: BeksOmega says that this can be removed once we update Blockly.
var oldSetValue = this.opField.setValue;
var thisBlock = this;
this.opField.setValue = function(newValue) {
oldSetValue.call(this, newValue);
thisBlock.updateFields(newValue);
};
},
decompose: function(workspace) {
var containerBlockName = 'mutator_container';
......@@ -227,31 +241,47 @@ Blockly.Blocks['logic_operation'] = {
this.lastMutator = workspace;
return containerBlock;
},
countNumberOfInputs: function(containerBlock) {
var start = containerBlock.getInputTargetBlock('STACK');
var i = 0;
while (start) {
i++;
start = start.getNextBlock();
}
return i;
},
compose: function(containerBlock) {
if (this.valuesToSave != null) {
for (var name in this.valuesToSave) {
this.valuesToSave[name] = this.getFieldValue(name);
}
}
if (this.itemCount_ === 0) {
this.removeInput(this.emptyInputName, true);
}
// Disconnect all input blocks and destroy all inputs.
for (var x = this.itemCount_ - 1; x >= 0; x--) {
this.removeInput(x > 1 ? this.repeatingInputName + x : ['A', 'B'][x]);
this.removeInput(x > 1 ? this.repeatingInputName + x : ['A', 'B'][x], true);
}
this.itemCount_ = 0;
// Rebuild the block's inputs.
var itemBlock = containerBlock.getInputTargetBlock('STACK')
var itemBlock = containerBlock.getInputTargetBlock('STACK');
this.itemCount_ = this.countNumberOfInputs(containerBlock);
var i = 0;
while (itemBlock) {
var input = this.addInput(this.itemCount_)
var input = this.addInput(i);
// Reconnect any child blocks.
if (itemBlock.valueConnection_) {
input.connection.connect(itemBlock.valueConnection_);
}
this.itemCount_++;
i++;
itemBlock = itemBlock.nextConnection &&
itemBlock.nextConnection.targetBlock();
}
if (this.itemCount_ === 0) {
this.addEmptyInput();
}
},
saveConnections: function(containerBlock) {
// Store a pointer to any connected child blocks.
......@@ -269,30 +299,35 @@ Blockly.Blocks['logic_operation'] = {
var input = this.appendValueInput(name)
.setCheck(Blockly.Blocks.Utilities.YailTypeToBlocklyType("boolean", Blockly.Blocks.Utilities.INPUT));
if (this.getInputsInline()) {
if (inputNum == 1) {
var op = this.opField.getValue();
this.opField = new Blockly.FieldDropdown(
Blockly.Blocks.logic_operation.OPERATORS(),
this.updateFields.bind(this));
this.opField.setValue(op);
input.appendField(this.opField, 'OP');
this.opField.init();
if (inputNum === 1) {
this.makeDropdown(input);
} else if (inputNum > 1) {
var field = new Blockly.FieldLabel(this.opField.getText());
input.appendField(field);
field.init();
} else if (this.itemCount_ === 1) {
input.appendField(Blockly.Blocks.logic_operation.IDENTITY(this.opField.getValue()),
'IDENTITY');
this.makeDropdown(input);
}
} else if (inputNum == 0) {
var op = this.opField.getValue();
this.opField = new Blockly.FieldDropdown(
Blockly.Blocks.logic_operation.OPERATORS.OPERATORS,
this.updateFields.bind(this));
this.opField.setValue(op);
input.appendField(this.opField, 'OP');
this.opField.init();
} else if (inputNum === 0) {
this.makeDropdown(input);
}
return input;
},
addEmptyInput: function() {
this.makeDropdown(this.appendDummyInput(this.emptyInputName));
},
makeDropdown: function(input) {
var op = this.opField.getValue();
this.opField = new Blockly.FieldDropdown(
Blockly.Blocks.logic_operation.OPERATORS(),
this.updateFields.bind(this));
this.opField.setValue(op);
input.appendField(this.opField, 'OP');
this.opField.init();
return input;
},
helpUrl: function () {
var op = this.getFieldValue('OP');
return Blockly.Blocks.logic_operation.HELPURLS()[op];
......@@ -300,10 +335,10 @@ Blockly.Blocks['logic_operation'] = {
setInputsInline: function(inline) {
if (inline) {
var ainput = this.getInput('A');
if (ainput.fieldRow.length > 0) {
if (ainput && ainput.fieldRow.length > 0) {
ainput.fieldRow.splice(0, 1);
var binput = this.getInput('B');
binput.fieldRow.splice(0, 0, this.opField);
this.makeDropdown(binput);
}
for (var input, i = 2; (input = this.inputList[i]); i++) {
var field = new Blockly.FieldLabel(this.opField.getText());
......@@ -312,10 +347,10 @@ Blockly.Blocks['logic_operation'] = {
}
} else {
var binput = this.getInput('B');
if (binput.fieldRow.length > 0) {
if (binput && binput.fieldRow.length > 0) {
binput.fieldRow.splice(0, 1);
var ainput = this.getInput('A');
ainput.fieldRow.splice(0, 0, this.opField);
this.makeDropdown(ainput);
}
for (var input, i = 2; (input = this.inputList[i]); i++) {
input.fieldRow[0].dispose();
......@@ -326,18 +361,25 @@ Blockly.Blocks['logic_operation'] = {
},
updateFields: function(op) {
if (this.getInputsInline()) {
var text = op == 'AND' ? Blockly.Msg.LANG_LOGIC_OPERATION_AND :
Blockly.Msg.LANG_LOGIC_OPERATION_OR;
var text = Blockly.Blocks.logic_operation.IDENTITY(op);
for (var input, i = 2; (input = this.inputList[i]); i++) {
input.fieldRow[0].setText(text);
}
}
if (this.itemCount_ === 1) {
var identity = this.getField('IDENTITY');
if (identity) {
identity.setText(Blockly.Blocks.logic_operation.IDENTITY(op));
}
}
// Update the mutator container block if the mutator is open
if (this.lastMutator) {
var mutatorBlock = this.lastMutator.getTopBlocks()[0];
var title = op === 'AND' ? Blockly.Msg.LANG_LOGIC_OPERATION_AND :
Blockly.Msg.LANG_LOGIC_OPERATION_OR;
mutatorBlock.setFieldValue(title, 'CONTAINER_TEXT');
var mutatorBlock = this.lastMutator.getTopBlocks(false)[0];
if (mutatorBlock) {
var title = op === 'AND' ? Blockly.Msg.LANG_LOGIC_OPERATION_AND :
Blockly.Msg.LANG_LOGIC_OPERATION_OR;
mutatorBlock.setFieldValue(title, 'CONTAINER_TEXT');
}
}
return op;
},
......@@ -363,6 +405,11 @@ Blockly.Blocks.logic_operation.OPERATORS = function () {
]
};
Blockly.Blocks.logic_operation.IDENTITY = function(op) {
return {'AND': Blockly.Msg.LANG_LOGIC_BOOLEAN_TRUE,
'OR': Blockly.Msg.LANG_LOGIC_BOOLEAN_FALSE}[op];
}
Blockly.Blocks.logic_operation.HELPURLS = function () {
return {
AND: Blockly.Msg.LANG_LOGIC_OPERATION_HELPURL_AND,
......@@ -385,9 +432,12 @@ Blockly.Blocks['logic_or'] = {
mutationToDom: Blockly.Blocks['logic_operation'].mutationToDom,
domToMutation: Blockly.Blocks['logic_operation'].domToMutation,
decompose: Blockly.Blocks['logic_operation'].decompose,
countNumberOfInputs: Blockly.Blocks['logic_operation'].countNumberOfInputs,
compose: Blockly.Blocks['logic_operation'].compose,
saveConnections: Blockly.Blocks['logic_operation'].saveConnections,
addInput: Blockly.Blocks['logic_operation'].addInput,
addEmptyInput: Blockly.Blocks['logic_operation'].addEmptyInput,
makeDropdown: Blockly.Blocks['logic_operation'].makeDropdown,
helpUrl: Blockly.Blocks['logic_operation'].helpUrl,
setInputsInline: Blockly.Blocks['logic_operation'].setInputsInline,
updateFields: Blockly.Blocks['logic_operation'].updateFields
......@@ -401,17 +451,5 @@ Blockly.Blocks['logic_mutator_item'] = {
this.setPreviousStatement(true);
this.setNextStatement(true);
this.contextMenu = false;
},
isMovable: function() {
if (this.previousConnection.targetBlock()) {
var parent = this.previousConnection.targetBlock();
if (parent.type == 'mutator_container') {
return false;
} else if(parent.previousConnection.targetBlock() &&
parent.previousConnection.targetBlock().type == 'mutator_container') {
return false;
}
}
return true;
}
};
......@@ -243,14 +243,29 @@ Blockly.Blocks['math_add'] = {
this.itemCount_ = 2;
},
mutationToDom: Blockly.mutationToDom,
domToMutation: Blockly.domToMutation,
domToMutation: function(container) {
Blockly.domToMutation.call(this, container);
// If we only have one input, put the + operator before it
if (this.itemCount_ === 1) {
this.inputList[0].appendField('0 ' + Blockly.Msg.LANG_MATH_ARITHMETIC_ADD);
}
},
decompose: function (workspace) {
return Blockly.decompose(workspace, 'math_mutator_item', this);
},
compose: Blockly.compose,
compose: function(containerBlock) {
Blockly.compose.call(this, containerBlock);
// If we only have one input, put the + operator before it
if (this.itemCount_ === 1) {
this.inputList[0].appendField('0 ' + Blockly.Msg.LANG_MATH_ARITHMETIC_ADD);
}
},
saveConnections: Blockly.saveConnections,
addEmptyInput: function () {
var input = this.appendDummyInput(this.emptyInputName);
this.appendDummyInput(this.emptyInputName)
.appendField(Blockly.Msg.LANG_MATH_ARITHMETIC_ADD);
},
addInput: function (inputNum) {
var input = this.appendValueInput(this.repeatingInputName + inputNum)
......@@ -323,14 +338,25 @@ Blockly.Blocks['math_multiply'] = {
this.itemCount_ = 2;
},
mutationToDom: Blockly.mutationToDom,
domToMutation: Blockly.domToMutation,
domToMutation: function(container) {
Blockly.domToMutation.call(this, container);
if (this.itemCount_ === 1) {
this.inputList[0].appendField('1 ' + Blockly.Blocks.Utilities.times_symbol);
}
},
decompose: function (workspace) {
return Blockly.decompose(workspace, 'math_mutator_item', this);
},
compose: Blockly.compose,
compose: function(containerBlock) {
Blockly.compose.call(this, containerBlock);
if (this.itemCount_ === 1) {
this.inputList[0].appendField('1 ' + Blockly.Blocks.Utilities.times_symbol);
}
},
saveConnections: Blockly.saveConnections,
addEmptyInput: function () {
var input = this.appendDummyInput(this.emptyInputName);
this.appendDummyInput(this.emptyInputName)
.appendField(Blockly.Blocks.Utilities.times_symbol);
},
addInput: function (inputNum) {
var input = this.appendValueInput(this.repeatingInputName + inputNum)
......
......@@ -42,13 +42,13 @@ Blockly.Yail['logic_negate'] = function() {
Blockly.Yail['logic_operation'] = function() {
// The and, or logic operations
// TODO: (Andrew) Make these take multiple arguments.
var mode = this.getFieldValue('OP');
var mode = this.opField.getValue();
var tuple = Blockly.Yail.logic_operation.OPERATORS[mode];
var operator = tuple[0];
var order = tuple[1];
var argument0 = Blockly.Yail.valueToCode(this, 'A', order) || Blockly.Yail.YAIL_FALSE;
var argument1 = Blockly.Yail.valueToCode(this, 'B', order) || Blockly.Yail.YAIL_FALSE;
var defaultValue = tuple[2];
var argument0 = Blockly.Yail.valueToCode(this, 'A', order) || defaultValue;
var argument1 = Blockly.Yail.valueToCode(this, 'B', order) || defaultValue;
var code = Blockly.Yail.YAIL_OPEN_COMBINATION + operator
+ Blockly.Yail.YAIL_SPACER + argument0 + Blockly.Yail.YAIL_SPACER
+ argument1;
......@@ -61,8 +61,8 @@ Blockly.Yail['logic_operation'] = function() {
};
Blockly.Yail.logic_operation.OPERATORS = {
AND : [ 'and-delayed', Blockly.Yail.ORDER_NONE ],
OR : [ 'or-delayed', Blockly.Yail.ORDER_NONE ]
AND : [ 'and-delayed', Blockly.Yail.ORDER_NONE, Blockly.Yail.YAIL_TRUE ],
OR : [ 'or-delayed', Blockly.Yail.ORDER_NONE, Blockly.Yail.YAIL_FALSE ]
};
Blockly.Yail['logic_or'] = function() {
......
......@@ -273,12 +273,17 @@ Blockly.Yail['math_on_list'] = function() {
var tuple = Blockly.Yail.math_on_list.OPERATORS[mode];
var operator = tuple[0];
var order = tuple[1];
var identity = tuple[2];
var args = "";
var typeString = "";
for(var i=0;i<this.itemCount_;i++) {
args += (Blockly.Yail.valueToCode(this, 'NUM' + i, order) || 0) + Blockly.Yail.YAIL_SPACER;
typeString += "number" + Blockly.Yail.YAIL_SPACER;
}
if (this.itemCount_ === 0) {
args += identity + Blockly.Yail.YAIL_SPACER;
typeString += "number" + Blockly.Yail.YAIL_SPACER;
}
var code = Blockly.Yail.YAIL_CALL_YAIL_PRIMITIVE + operator
+ Blockly.Yail.YAIL_SPACER;
code = code + Blockly.Yail.YAIL_OPEN_COMBINATION
......@@ -294,8 +299,8 @@ Blockly.Yail['math_on_list'] = function() {
};
Blockly.Yail.math_on_list.OPERATORS = {
MIN: ['min', Blockly.Yail.ORDER_NONE],
MAX: ['max', Blockly.Yail.ORDER_NONE]
MIN: ['min', Blockly.Yail.ORDER_NONE, '+inf.0'],
MAX: ['max', Blockly.Yail.ORDER_NONE, '-inf.0']
};
Blockly.Yail['math_divide'] = function() {
......
......@@ -56,6 +56,14 @@ public final class YailNumberToString {
// This implementation assumes that Kawa inexact numbers are passed to this routine
// as doubles.
public static String format(double number) {
// Handle positive and negative infinities (which are "round" and therefore coerce to long)
if (Double.isInfinite(number)) {
if (number < 0.0) {
return "-infinity";
} else {
return "+infinity";
}
}
// We will print integer values without a decimal point.
if (number == Math.rint(number)) {
return String.valueOf((long) number);
......
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