Merge remote-tracking branch 'Google/develop' into feature/upstream-merge-march-11-2

# Conflicts:
#	blockly_compressed_vertical.js
#	blocks_compressed.js
#	build.py
#	core/connection.js
#	core/workspace.js
#	tests/playground.html
This commit is contained in:
Tim Mickel 2016-03-11 13:18:14 -05:00
commit 516cd05d82
53 changed files with 3446 additions and 608 deletions

View file

@ -30,6 +30,7 @@ blockly/
|- blocks_compressed.js
|- dart_compressed.js
|- javascript_compressed.js
|- lua_compressed.js
|- php_compressed.js
`- python_compressed.js

View file

@ -46,7 +46,8 @@ goog.addDependency("../../../" + dir + "/core/blocks.js", ['Blockly.Blocks'], []
goog.addDependency("../../../" + dir + "/core/bubble.js", ['Blockly.Bubble'], ['Blockly.Workspace', 'goog.dom', 'goog.math', 'goog.userAgent']);
goog.addDependency("../../../" + dir + "/core/colours.js", ['Blockly.Colours'], []);
goog.addDependency("../../../" + dir + "/core/comment.js", ['Blockly.Comment'], ['Blockly.Bubble', 'Blockly.Icon', 'goog.userAgent']);
goog.addDependency("../../../" + dir + "/core/connection.js", ['Blockly.Connection', 'Blockly.ConnectionDB'], ['goog.dom']);
goog.addDependency("../../../" + dir + "/core/connection.js", ['Blockly.Connection'], ['goog.asserts', 'goog.dom']);
goog.addDependency("../../../" + dir + "/core/connection_db.js", ['Blockly.ConnectionDB'], ['Blockly.Connection']);
goog.addDependency("../../../" + dir + "/core/contextmenu.js", ['Blockly.ContextMenu'], ['goog.dom', 'goog.events', 'goog.style', 'goog.ui.Menu', 'goog.ui.MenuItem']);
goog.addDependency("../../../" + dir + "/core/css.js", ['Blockly.Css'], ['Blockly.Colours']);
goog.addDependency("../../../" + dir + "/core/events.js", ['Blockly.Events'], []);

View file

@ -123,8 +123,17 @@ Blockly.Blocks['lists_create_with'] = {
this.updateShape_();
// Reconnect any child blocks.
for (var i = 0; i < this.itemCount_; i++) {
if (connections[i]) {
this.getInput('ADD' + i).connection.connect(connections[i]);
var connectionChild = connections[i];
if (connectionChild) {
var parent = connectionChild.targetBlock();
var connectionParent = this.getInput('ADD' + i).connection;
if (connectionParent.targetConnection != connectionChild &&
(!parent || parent == this)) {
if (connectionParent.targetConnection) {
connectionParent.disconnect();
}
connectionParent.connect(connectionChild);
}
}
}
},
@ -150,28 +159,26 @@ Blockly.Blocks['lists_create_with'] = {
* @this Blockly.Block
*/
updateShape_: function() {
// Delete everything.
if (this.getInput('EMPTY')) {
if (this.itemCount_ && this.getInput('EMPTY')) {
this.removeInput('EMPTY');
} else {
var i = 0;
while (this.getInput('ADD' + i)) {
this.removeInput('ADD' + i);
i++;
}
}
// Rebuild block.
if (this.itemCount_ == 0) {
} else if (!this.itemCount_ && !this.getInput('EMPTY')) {
this.appendDummyInput('EMPTY')
.appendField(Blockly.Msg.LISTS_CREATE_EMPTY_TITLE);
} else {
for (var i = 0; i < this.itemCount_; i++) {
}
// Add new inputs.
for (var i = 0; i < this.itemCount_; i++) {
if (!this.getInput('ADD' + i)) {
var input = this.appendValueInput('ADD' + i);
if (i == 0) {
input.appendField(Blockly.Msg.LISTS_CREATE_WITH_INPUT_WITH);
}
}
}
// Remove deleted inputs.
while (this.getInput('ADD' + i)) {
this.removeInput('ADD' + i);
i++;
}
}
};

View file

@ -330,7 +330,7 @@ Blockly.Blocks['logic_compare'] = {
for (var i = 0; i < this.prevBlocks_.length; i++) {
var block = this.prevBlocks_[i];
if (block === blockA || block === blockB) {
block.setParent(null);
block.unplug();
block.bumpNeighbours_();
}
}
@ -470,10 +470,10 @@ Blockly.Blocks['logic_ternary'] = {
var block = (i == 1) ? blockA : blockB;
if (block && !block.outputConnection.checkType_(parentConnection)) {
if (parentConnection === this.prevParentConnection_) {
this.setParent(null);
this.unplug();
parentConnection.sourceBlock_.bumpNeighbours_();
} else {
block.setParent(null);
block.unplug();
block.bumpNeighbours_();
}
}

View file

@ -234,7 +234,7 @@ Blockly.Blocks['procedures_defnoreturn'] = {
this.statementConnection_ = stackConnection.targetConnection;
if (this.statementConnection_) {
var stackBlock = stackConnection.targetBlock();
stackBlock.setParent(null);
stackBlock.unplug();
stackBlock.bumpNeighbours_();
}
this.setStatements_(false);

View file

@ -605,11 +605,7 @@ Blockly.Blocks['text_prompt_ext'] = {
* @this Blockly.Block
*/
updateType_: function(newOp) {
if (newOp == 'NUMBER') {
this.outputConnection.setCheck('Number');
} else {
this.outputConnection.setCheck('String');
}
this.outputConnection.setCheck(newOp == 'NUMBER' ? 'Number' : 'String');
},
/**
* Create XML to represent the output type.

View file

@ -33,6 +33,7 @@
# javascript_compressed.js: The compressed Javascript generator.
# python_compressed.js: The compressed Python generator.
# dart_compressed.js: The compressed Dart generator.
# lua_compressed.js: The compressed Lua generator.
# msg/js/<LANG>.js for every language <LANG> defined in msg/js/<LANG>.json.
import sys

View file

@ -241,27 +241,28 @@ Blockly.Block.prototype.dispose = function(healStack) {
/**
* Unplug this block from its superior block. If this block is a statement,
* optionally reconnect the block underneath with the block on top.
* @param {boolean} healStack Disconnect child statement and reconnect stack.
* @param {boolean} opt_healStack Disconnect child statement and reconnect
* stack. Defaults to false.
*/
Blockly.Block.prototype.unplug = function(healStack) {
Blockly.Block.prototype.unplug = function(opt_healStack) {
if (this.outputConnection) {
if (this.outputConnection.targetConnection) {
// Disconnect from any superior block.
this.setParent(null);
this.outputConnection.disconnect();
}
} else {
} else if (this.previousConnection) {
var previousTarget = null;
if (this.previousConnection && this.previousConnection.targetConnection) {
if (this.previousConnection.targetConnection) {
// Remember the connection that any next statements need to connect to.
previousTarget = this.previousConnection.targetConnection;
// Detach this block from the parent's tree.
this.setParent(null);
this.previousConnection.disconnect();
}
var nextBlock = this.getNextBlock();
if (healStack && nextBlock) {
if (opt_healStack && nextBlock) {
// Disconnect the next statement.
var nextTarget = this.nextConnection.targetConnection;
nextBlock.setParent(null);
nextTarget.disconnect();
if (previousTarget && previousTarget.checkType_(nextTarget)) {
// Attach the next statement to the previous statement.
previousTarget.connect(nextTarget);
@ -424,9 +425,8 @@ Blockly.Block.prototype.getChildren = function() {
* @param {Blockly.Block} newParent New parent block.
*/
Blockly.Block.prototype.setParent = function(newParent) {
var event;
if (Blockly.Events.isEnabled()) {
event = new Blockly.Events.Move(this);
if (newParent == this.parentBlock_) {
return;
}
if (this.parentBlock_) {
// Remove this block from the old parent's child list.
@ -439,13 +439,13 @@ Blockly.Block.prototype.setParent = function(newParent) {
}
// Disconnect from superior blocks.
this.parentBlock_ = null;
if (this.previousConnection && this.previousConnection.targetConnection) {
this.previousConnection.disconnect();
throw 'Still connected to previous block.';
}
if (this.outputConnection && this.outputConnection.targetConnection) {
this.outputConnection.disconnect();
throw 'Still connected to parent block.';
}
this.parentBlock_ = null;
// This block hasn't actually moved on-screen, so there's no need to update
// its connection locations.
} else {
@ -460,10 +460,6 @@ Blockly.Block.prototype.setParent = function(newParent) {
} else {
this.workspace.addTopBlock(this);
}
if (event) {
event.recordNew();
Blockly.Events.fire(event);
}
};
/**
@ -1303,7 +1299,7 @@ Blockly.Block.prototype.removeInput = function(name, opt_quiet) {
if (input.name == name) {
if (input.connection && input.connection.targetConnection) {
// Disconnect any attached block.
input.connection.targetBlock().setParent(null);
input.connection.targetBlock().unplug();
}
input.dispose();
this.inputList.splice(i, 1);
@ -1361,7 +1357,7 @@ Blockly.Block.prototype.getCommentText = function() {
Blockly.Block.prototype.setCommentText = function(text) {
if (this.comment != text) {
Blockly.Events.fire(new Blockly.Events.Change(
this, 'comment', null, this.comment, text || ''));
this, 'comment', null, this.comment, text || ''));
this.comment = text;
}
};

View file

@ -218,10 +218,18 @@ Blockly.BlockSvg.terminateDrag_ = function() {
delete selected.draggedBubbles_;
selected.setDragging_(false);
selected.render();
goog.Timer.callOnce(
selected.snapToGrid, Blockly.BUMP_DELAY / 2, selected);
goog.Timer.callOnce(
selected.bumpNeighbours_, Blockly.BUMP_DELAY, selected);
// Ensure that any stap and bump are part of this move's event group.
var group = Blockly.Events.getGroup();
setTimeout(function() {
Blockly.Events.setGroup(group);
selected.snapToGrid();
Blockly.Events.setGroup(false);
}, Blockly.BUMP_DELAY / 2);
setTimeout(function() {
Blockly.Events.setGroup(group);
selected.bumpNeighbours_();
Blockly.Events.setGroup(false);
}, Blockly.BUMP_DELAY);
// Fire an event to allow scrollbars to resize.
Blockly.fireUiEvent(window, 'resize');
}
@ -235,6 +243,9 @@ Blockly.BlockSvg.terminateDrag_ = function() {
* @param {Blockly.BlockSvg} newParent New parent block.
*/
Blockly.BlockSvg.prototype.setParent = function(newParent) {
if (newParent == this.parentBlock_) {
return;
}
var svgRoot = this.getSvgRoot();
if (this.parentBlock_ && svgRoot) {
// Move this block up the DOM. Keep track of x/y translations.
@ -393,7 +404,9 @@ Blockly.BlockSvg.prototype.onMouseDown_ = function(e) {
// dragged instead.
return;
} else {
Blockly.Events.group = Blockly.genUid();
if (!Blockly.Events.getGroup()) {
Blockly.Events.setGroup(true);
}
// Left-click (or middle click)
Blockly.removeAllRanges();
Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
@ -473,7 +486,9 @@ Blockly.BlockSvg.prototype.onMouseUp_ = function(e) {
Blockly.highlightedConnection_ = null;
}
Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
Blockly.Events.group = '';
if (!Blockly.WidgetDiv.isVisible()) {
Blockly.Events.setGroup(false);
}
};
/**
@ -647,7 +662,7 @@ Blockly.BlockSvg.prototype.onMouseMove_ = function(e) {
group.skew_ = '';
if (this.parentBlock_) {
// Push this block to the very top of the stack.
this.setParent(null);
this.unplug();
this.disconnectUiEffect();
}
this.setDragging_(true);

View file

@ -392,6 +392,9 @@ Blockly.onKeyDown_ = function(e) {
if (Blockly.clipboardXml_) {
Blockly.clipboardSource_.paste(Blockly.clipboardXml_);
}
} else if (e.keyCode == 90) {
// 'z' for undo 'Z' is for redo.
Blockly.mainWorkspace.undo(e.shiftKey);
}
}
if (deleteBlock) {

View file

@ -25,8 +25,8 @@
'use strict';
goog.provide('Blockly.Connection');
goog.provide('Blockly.ConnectionDB');
goog.require('goog.asserts');
goog.require('goog.dom');
@ -74,10 +74,129 @@ Blockly.Connection.NUMBER = 3;
Blockly.Connection.CAN_CONNECT = 0;
Blockly.Connection.REASON_SELF_CONNECTION = 1;
Blockly.Connection.REASON_WRONG_TYPE = 2;
Blockly.Connection.REASON_MUST_DISCONNECT = 3;
Blockly.Connection.REASON_TARGET_NULL = 4;
Blockly.Connection.REASON_CHECKS_FAILED = 5;
Blockly.Connection.REASON_DIFFERENT_WORKSPACES = 6;
Blockly.Connection.REASON_TARGET_NULL = 3;
Blockly.Connection.REASON_CHECKS_FAILED = 4;
Blockly.Connection.REASON_DIFFERENT_WORKSPACES = 5;
/**
* Connect two connections together.
* @param {!Blockly.Connection} parentConnection Connection on superior block.
* @param {!Blockly.Connection} childConnection Connection on inferior block.
*/
Blockly.Connection.connect_ = function(parentConnection, childConnection) {
var parentBlock = parentConnection.sourceBlock_;
var childBlock = childConnection.sourceBlock_;
// Disconnect any existing parent on the child connection.
if (childConnection.targetConnection) {
childConnection.disconnect();
}
if (parentConnection.targetConnection) {
// Other connection is already connected to something.
// Disconnect it and reattach it or bump it as needed.
var orphanBlock = parentConnection.targetBlock();
var shadowDom = parentConnection.getShadowDom();
// Temporarily set the shadow DOM to null so it does not respawn.
parentConnection.setShadowDom(null);
// Displaced shadow blocks dissolve rather than reattaching or bumping.
if (orphanBlock.isShadow()) {
// Save the shadow block so that field values are preserved.
shadowDom = Blockly.Xml.blockToDom(orphanBlock);
orphanBlock.dispose();
orphanBlock = null;
} else if (parentConnection.type == Blockly.INPUT_VALUE) {
// Value connections.
// If female block is already connected, disconnect and bump the male.
if (!orphanBlock.outputConnection) {
throw 'Orphan block does not have an output connection.';
}
// Attempt to reattach the orphan at the end of the newly inserted
// block. Since this block may be a row, walk down to the end
// or to the first (and only) shadow block.
var connection = Blockly.Connection.lastConnectionInRow_(
childBlock, orphanBlock);
if (connection) {
orphanBlock.outputConnection.connect(connection);
orphanBlock = null;
}
} else if (parentConnection.type == Blockly.NEXT_STATEMENT) {
// Statement connections.
// Statement blocks may be inserted into the middle of a stack.
// Split the stack.
if (!orphanBlock.previousConnection) {
throw 'Orphan block does not have a previous connection.';
}
// Attempt to reattach the orphan at the bottom of the newly inserted
// block. Since this block may be a stack, walk down to the end.
var newBlock = childBlock;
while (newBlock.nextConnection) {
if (newBlock.nextConnection.targetConnection) {
newBlock = newBlock.getNextBlock();
} else {
if (orphanBlock.previousConnection.checkType_(
newBlock.nextConnection)) {
newBlock.nextConnection.connect(orphanBlock.previousConnection);
orphanBlock = null;
}
break;
}
}
}
if (orphanBlock) {
// Unable to reattach orphan.
parentConnection.disconnect();
if (Blockly.Events.recordUndo) {
// Bump it off to the side after a moment.
var group = Blockly.Events.getGroup();
setTimeout(function() {
// Verify orphan hasn't been deleted or reconnected (user on meth).
if (orphanBlock.workspace && !orphanBlock.getParent()) {
Blockly.Events.setGroup(group);
if (orphanBlock.outputConnection) {
orphanBlock.outputConnection.bumpAwayFrom_(parentConnection);
} else if (orphanBlock.previousConnection) {
orphanBlock.previousConnection.bumpAwayFrom_(parentConnection);
}
Blockly.Events.setGroup(false);
}
}, Blockly.BUMP_DELAY);
}
}
// Restore the shadow DOM.
parentConnection.setShadowDom(shadowDom);
}
var event;
if (Blockly.Events.isEnabled()) {
event = new Blockly.Events.Move(childBlock);
}
// Establish the connections.
Blockly.Connection.connectReciprocally_(parentConnection, childConnection);
// Demote the inferior block so that one is a child of the superior one.
childBlock.setParent(parentBlock);
if (event) {
event.recordNew();
Blockly.Events.fire(event);
}
if (parentBlock.rendered) {
parentBlock.updateDisabled();
}
if (childBlock.rendered) {
childBlock.updateDisabled();
}
if (parentBlock.rendered && childBlock.rendered) {
if (parentConnection.type == Blockly.NEXT_STATEMENT ||
parentConnection.type == Blockly.PREVIOUS_STATEMENT) {
// Child block may need to square off its corners if it is in a stack.
// Rendering a child will render its parent.
childBlock.render();
} else {
// Child block does not change shape. Rendering the parent node will
// move its connected children into position.
parentBlock.render();
}
}
};
/**
* Connection this connection connects to. Null if not connected.
@ -171,6 +290,18 @@ Blockly.Connection.prototype.isSuperior = function() {
this.type == Blockly.NEXT_STATEMENT;
};
/**
* Returns the distance between this connection and another connection.
* @param {!Blockly.Connection} otherConnection The other connection to measure
* the distance to.
* @return {number} The distance between connections.
*/
Blockly.Connection.prototype.distanceFrom = function(otherConnection) {
var xDiff = this.x_ - otherConnection.x_;
var yDiff = this.y_ - otherConnection.y_;
return Math.sqrt(xDiff * xDiff + yDiff * yDiff);
};
/**
* Checks whether the current connection can connect with the target
* connection.
@ -186,8 +317,6 @@ Blockly.Connection.prototype.canConnectWithReason_ = function(target) {
return Blockly.Connection.REASON_SELF_CONNECTION;
} else if (target.type != Blockly.OPPOSITE_TYPE[this.type]) {
return Blockly.Connection.REASON_WRONG_TYPE;
} else if (this.targetConnection) {
return Blockly.Connection.REASON_MUST_DISCONNECT;
} else if (this.sourceBlock_ && target.sourceBlock_ &&
this.sourceBlock_.workspace !== target.sourceBlock_.workspace) {
return Blockly.Connection.REASON_DIFFERENT_WORKSPACES;
@ -214,8 +343,6 @@ Blockly.Connection.prototype.checkConnection_ = function(target) {
throw 'Blocks are on different workspaces.';
case Blockly.Connection.REASON_WRONG_TYPE:
throw 'Attempt to connect incompatible types.';
case Blockly.Connection.REASON_MUST_DISCONNECT:
throw 'Source connection already connected.';
case Blockly.Connection.REASON_TARGET_NULL:
throw 'Target connection is null.';
case Blockly.Connection.REASON_CHECKS_FAILED:
@ -225,117 +352,78 @@ Blockly.Connection.prototype.checkConnection_ = function(target) {
}
};
/**
* Check if the two connections can be dragged to connect to each other.
* @param {!Blockly.Connection} candidate A nearby connection to check.
* @param {number} maxRadius The maximum radius allowed for connections.
* @return {boolean} True if the connection is allowed, false otherwise.
*/
Blockly.Connection.prototype.isConnectionAllowed = function(candidate,
maxRadius) {
if (this.distanceFrom(candidate) > maxRadius) {
return false;
}
// Type checking.
var canConnect = this.canConnectWithReason_(candidate);
if (canConnect != Blockly.Connection.CAN_CONNECT &&
canConnect != Blockly.Connection.REASON_MUST_DISCONNECT) {
return false;
}
// Don't offer to connect an already connected left (male) value plug to
// an available right (female) value plug. Don't offer to connect the
// bottom of a statement block to one that's already connected.
if (candidate.type == Blockly.OUTPUT_VALUE ||
candidate.type == Blockly.PREVIOUS_STATEMENT) {
if (candidate.targetConnection || this.targetConnection) {
return false;
}
}
// Offering to connect the left (male) of a value block to an already
// connected value pair is ok, we'll splice it in.
// However, don't offer to splice into an unmovable block.
if (candidate.type == Blockly.INPUT_VALUE &&
candidate.targetConnection &&
!candidate.targetBlock().isMovable() &&
!candidate.targetBlock().isShadow()) {
return false;
}
// Don't let blocks try to connect to themselves or ones they nest.
var targetSourceBlock = candidate.sourceBlock_;
var sourceBlock = this.sourceBlock_;
if (targetSourceBlock && sourceBlock) {
do {
if (sourceBlock == targetSourceBlock) {
return false;
}
targetSourceBlock = targetSourceBlock.getParent();
} while (targetSourceBlock);
}
return true;
};
/**
* Connect this connection to another connection.
* @param {!Blockly.Connection} otherConnection Connection to connect to.
*/
Blockly.Connection.prototype.connect = function(otherConnection) {
this.checkConnection_(otherConnection);
// If the previous statement failed it would have thrown an exception.
if (otherConnection.targetConnection) {
// Other connection is already connected to something.
// Disconnect it and reattach it or bump it as needed.
var orphanBlock = otherConnection.targetBlock();
orphanBlock.setParent(null);
// Displaced shadow blocks dissolve rather than reattaching or bumping.
if (orphanBlock.isShadow()) {
// Save the shadow block so that field values are preserved.
otherConnection.setShadowDom(Blockly.Xml.blockToDom(orphanBlock));
orphanBlock.dispose();
orphanBlock = null;
} else if (this.type == Blockly.INPUT_VALUE ||
this.type == Blockly.OUTPUT_VALUE) {
// Value connections.
// If female block is already connected, disconnect and bump the male.
if (!orphanBlock.outputConnection) {
throw 'Orphan block does not have an output connection.';
}
// Attempt to reattach the orphan at the end of the newly inserted
// block. Since this block may be a row, walk down to the end
// or to the first (and only) shadow block.
var connection = Blockly.Connection.lastConnectionInRow_(
this.sourceBlock_, orphanBlock);
if (connection != null) {
orphanBlock.outputConnection.connect(connection);
orphanBlock = null;
}
} else if (this.type == Blockly.PREVIOUS_STATEMENT) {
// Statement connections.
// Statement blocks may be inserted into the middle of a stack.
// Split the stack.
if (!orphanBlock.previousConnection) {
throw 'Orphan block does not have a previous connection.';
}
// Attempt to reattach the orphan at the bottom of the newly inserted
// block. Since this block may be a stack, walk down to the end.
var newBlock = this.sourceBlock_;
while (newBlock.nextConnection) {
if (newBlock.nextConnection.targetConnection) {
newBlock = newBlock.getNextBlock();
} else {
if (orphanBlock.previousConnection.checkType_(
newBlock.nextConnection)) {
newBlock.nextConnection.connect(orphanBlock.previousConnection);
orphanBlock = null;
}
break;
}
}
} else {
// Type is Blockly.NEXT_STATEMENT.
throw 'Can only do a mid-stack connection with the top of a block.';
}
if (orphanBlock) {
// Unable to reattach orphan. Bump it off to the side after a moment.
setTimeout(function() {
// Verify orphan hasn't been deleted or reconnected (user on meth).
if (orphanBlock.workspace && !orphanBlock.getParent()) {
if (orphanBlock.outputConnection) {
orphanBlock.outputConnection.bumpAwayFrom_(otherConnection);
} else if (orphanBlock.previousConnection) {
orphanBlock.previousConnection.bumpAwayFrom_(otherConnection);
}
}
}, Blockly.BUMP_DELAY);
}
if (this.targetConnection == otherConnection) {
// Already connected together. NOP.
return;
}
this.checkConnection_(otherConnection);
// Determine which block is superior (higher in the source stack).
var parentBlock, childBlock;
if (this.isSuperior()) {
// Superior block.
parentBlock = this.sourceBlock_;
childBlock = otherConnection.sourceBlock_;
Blockly.Connection.connect_(this, otherConnection);
} else {
// Inferior block.
parentBlock = otherConnection.sourceBlock_;
childBlock = this.sourceBlock_;
}
// Establish the connections.
Blockly.Connection.connectReciprocally(this, otherConnection);
// Demote the inferior block so that one is a child of the superior one.
childBlock.setParent(parentBlock);
if (parentBlock.rendered) {
parentBlock.updateDisabled();
}
if (childBlock.rendered) {
childBlock.updateDisabled();
}
if (parentBlock.rendered && childBlock.rendered) {
if (this.type == Blockly.NEXT_STATEMENT ||
this.type == Blockly.PREVIOUS_STATEMENT) {
// Child block may need to square off its corners if it is in a stack.
// Rendering a child will render its parent.
childBlock.render();
} else {
// Child block does not change shape. Rendering the parent node will
// move its connected children into position.
parentBlock.render();
}
Blockly.Connection.connect_(otherConnection, this);
}
};
@ -343,11 +431,10 @@ Blockly.Connection.prototype.connect = function(otherConnection) {
* Update two connections to target each other.
* @param {Blockly.Connection} first The first connection to update.
* @param {Blockly.Connection} second The second conneciton to update.
* @private
*/
Blockly.Connection.connectReciprocally = function(first, second) {
if (!first || !second) {
throw 'Cannot connect null connections.';
}
Blockly.Connection.connectReciprocally_ = function(first, second) {
goog.asserts.assert(first && second, 'Cannot connect null connections.');
first.targetConnection = second;
second.targetConnection = first;
};
@ -407,15 +494,10 @@ Blockly.Connection.lastConnectionInRow_ = function(startBlock, orphanBlock) {
*/
Blockly.Connection.prototype.disconnect = function() {
var otherConnection = this.targetConnection;
if (!otherConnection) {
throw 'Source connection not connected.';
} else if (otherConnection.targetConnection != this) {
throw 'Target connection not connected to source connection.';
}
otherConnection.targetConnection = null;
this.targetConnection = null;
goog.asserts.assert(otherConnection, 'Source connection not connected.');
goog.asserts.assert(otherConnection.targetConnection == this,
'Target connection not connected to source connection.');
// Rerender the parent so that it may reflow.
var parentBlock, childBlock, parentConnection;
if (this.isSuperior()) {
// Superior block.
@ -428,9 +510,22 @@ Blockly.Connection.prototype.disconnect = function() {
childBlock = this.sourceBlock_;
parentConnection = otherConnection;
}
var event;
if (Blockly.Events.isEnabled()) {
event = new Blockly.Events.Move(childBlock);
}
otherConnection.targetConnection = null;
this.targetConnection = null;
childBlock.setParent(null);
if (event) {
event.recordNew();
Blockly.Events.fire(event);
}
// Respawn the shadow block if there is one.
var shadow = parentConnection.getShadowDom();
if (parentBlock.workspace && !childBlock.isShadow() && shadow) {
// Respawn the shadow block.
if (parentBlock.workspace && shadow && Blockly.Events.recordUndo) {
var blockShadow =
Blockly.Xml.domToBlock(parentBlock.workspace, shadow);
if (blockShadow.outputConnection) {
@ -443,6 +538,8 @@ Blockly.Connection.prototype.disconnect = function() {
blockShadow.initSvg();
blockShadow.render(false);
}
// Rerender the parent so that it may reflow.
if (parentBlock.rendered) {
parentBlock.render();
}
@ -521,7 +618,7 @@ Blockly.Connection.prototype.moveTo = function(x, y) {
this.y_ = y;
// Insert it into its new location in the database.
if (!this.hidden_) {
this.db_.addConnection_(this);
this.db_.addConnection(this);
}
};
@ -593,108 +690,18 @@ Blockly.Connection.prototype.tighten_ = function() {
* in the database and the current location (as a result of dragging).
* @param {number} dy Vertical offset between this connection's location
* in the database and the current location (as a result of dragging).
* @return {!{connection: ?Blockly.Connection, radius: number}} Contains two properties: 'connection' which is either
* another connection or null, and 'radius' which is the distance.
* @return {!{connection: ?Blockly.Connection, radius: number}} Contains two
* properties:' connection' which is either another connection or null,
* and 'radius' which is the distance.
*/
Blockly.Connection.prototype.closest = function(maxLimit, dx, dy) {
if (this.targetConnection) {
// Don't offer to connect to a connection that's already connected.
return {connection: null, radius: maxLimit};
var closestConnection = this.dbOpposite_.searchForClosest(this, maxLimit, dx,
dy);
if (closestConnection) {
return {connection: closestConnection,
radius: this.distanceFrom(closestConnection)};
}
// Determine the opposite type of connection.
var db = this.dbOpposite_;
// Since this connection is probably being dragged, add the delta.
var currentX = this.x_ + dx;
var currentY = this.y_ + dy;
// Binary search to find the closest y location.
var pointerMin = 0;
var pointerMax = db.length - 2;
var pointerMid = pointerMax;
while (pointerMin < pointerMid) {
if (db[pointerMid].y_ < currentY) {
pointerMin = pointerMid;
} else {
pointerMax = pointerMid;
}
pointerMid = Math.floor((pointerMin + pointerMax) / 2);
}
// Walk forward and back on the y axis looking for the closest x,y point.
pointerMin = pointerMid;
pointerMax = pointerMid;
var closestConnection = null;
var sourceBlock = this.sourceBlock_;
var thisConnection = this;
if (db.length) {
while (pointerMin >= 0 && checkConnection_(pointerMin)) {
pointerMin--;
}
do {
pointerMax++;
} while (pointerMax < db.length && checkConnection_(pointerMax));
}
/**
* Computes if the current connection is within the allowed radius of another
* connection.
* This function is a closure and has access to outside variables.
* @param {number} yIndex The other connection's index in the database.
* @return {boolean} True if the search needs to continue: either the current
* connection's vertical distance from the other connection is less than
* the allowed radius, or if the connection is not compatible.
* @private
*/
function checkConnection_(yIndex) {
var connection = db[yIndex];
if (connection.type == Blockly.OUTPUT_VALUE ||
connection.type == Blockly.PREVIOUS_STATEMENT) {
// Don't offer to connect an already connected left (male) value plug to
// an available right (female) value plug. Don't offer to connect the
// bottom of a statement block to one that's already connected.
if (connection.targetConnection) {
return true;
}
}
// Offering to connect the top of a statement block to an already connected
// connection is ok, we'll just insert it into the stack.
// Offering to connect the left (male) of a value block to an already
// connected value pair is ok, we'll splice it in.
// However, don't offer to splice into an unmovable block.
if (connection.type == Blockly.INPUT_VALUE &&
connection.targetConnection &&
!connection.targetBlock().isMovable() &&
!connection.targetBlock().isShadow()) {
return true;
}
// Do type checking.
if (!thisConnection.checkType_(connection)) {
return true;
}
// Don't let blocks try to connect to themselves or ones they nest.
var targetSourceBlock = connection.sourceBlock_;
do {
if (sourceBlock == targetSourceBlock) {
return true;
}
targetSourceBlock = targetSourceBlock.getParent();
} while (targetSourceBlock);
// Only connections within the maxLimit radius.
var dx = currentX - connection.x_;
var dy = currentY - connection.y_;
var r = Math.sqrt(dx * dx + dy * dy);
if (r <= maxLimit) {
closestConnection = connection;
maxLimit = r;
}
return Math.abs(dy) < maxLimit;
}
return {connection: closestConnection, radius: maxLimit};
return {connection: null, radius: maxLimit};
};
/**
@ -747,7 +754,7 @@ Blockly.Connection.prototype.setCheck = function(check) {
// The new value type may not be compatible with the existing connection.
if (this.targetConnection && !this.checkType_(this.targetConnection)) {
var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_;
child.setParent(null);
child.unplug();
// Bump away.
this.sourceBlock_.bumpNeighbours_();
}
@ -797,59 +804,11 @@ Blockly.Connection.prototype.getShadowDom = function() {
* @private
*/
Blockly.Connection.prototype.neighbours_ = function(maxLimit) {
// Determine the opposite type of connection.
var db = this.dbOpposite_;
var currentX = this.x_;
var currentY = this.y_;
// Binary search to find the closest y location.
var pointerMin = 0;
var pointerMax = db.length - 2;
var pointerMid = pointerMax;
while (pointerMin < pointerMid) {
if (db[pointerMid].y_ < currentY) {
pointerMin = pointerMid;
} else {
pointerMax = pointerMid;
}
pointerMid = Math.floor((pointerMin + pointerMax) / 2);
}
// Walk forward and back on the y axis looking for the closest x,y point.
pointerMin = pointerMid;
pointerMax = pointerMid;
var neighbours = [];
var sourceBlock = this.sourceBlock_;
if (db.length) {
while (pointerMin >= 0 && checkConnection_(pointerMin)) {
pointerMin--;
}
do {
pointerMax++;
} while (pointerMax < db.length && checkConnection_(pointerMax));
}
/**
* Computes if the current connection is within the allowed radius of another
* connection.
* This function is a closure and has access to outside variables.
* @param {number} yIndex The other connection's index in the database.
* @return {boolean} True if the current connection's vertical distance from
* the other connection is less than the allowed radius.
*/
function checkConnection_(yIndex) {
var dx = currentX - db[yIndex].x_;
var dy = currentY - db[yIndex].y_;
var r = Math.sqrt(dx * dx + dy * dy);
if (r <= maxLimit) {
neighbours.push(db[yIndex]);
}
return dy < maxLimit;
}
return neighbours;
return this.dbOpposite_.getNeighbours(this, maxLimit);
};
// Appearance or lack thereof.
/**
* Set whether this connections is hidden (not tracked in a database) or not.
* @param {boolean} hidden True if connection is hidden.
@ -859,7 +818,7 @@ Blockly.Connection.prototype.setHidden = function(hidden) {
if (hidden && this.inDB_) {
this.db_.removeConnection_(this);
} else if (!hidden && !this.inDB_) {
this.db_.addConnection_(this);
this.db_.addConnection(this);
}
};
@ -929,110 +888,34 @@ Blockly.Connection.prototype.unhideAll = function() {
return renderList;
};
/**
* Database of connections.
* Connections are stored in order of their vertical component. This way
* connections in an area may be looked up quickly using a binary search.
* @constructor
* Add highlighting around this connection.
*/
Blockly.ConnectionDB = function() {
};
Blockly.Connection.prototype.highlight = function() {
var steps;
if (this.type == Blockly.INPUT_VALUE || this.type == Blockly.OUTPUT_VALUE) {
var tabWidth = this.sourceBlock_.RTL ? -Blockly.BlockSvg.TAB_WIDTH :
Blockly.BlockSvg.TAB_WIDTH;
steps = 'm 0,0 ' + Blockly.BlockSvg.TAB_PATH_DOWN + ' v 5';
Blockly.ConnectionDB.prototype = new Array();
/**
* Don't inherit the constructor from Array.
* @type {!Function}
*/
Blockly.ConnectionDB.constructor = Blockly.ConnectionDB;
/**
* Add a connection to the database. Must not already exist in DB.
* @param {!Blockly.Connection} connection The connection to be added.
* @private
*/
Blockly.ConnectionDB.prototype.addConnection_ = function(connection) {
if (connection.inDB_) {
throw 'Connection already in database.';
} else {
steps = 'm -20,0 h 5 ' + Blockly.BlockSvg.NOTCH_PATH_LEFT + ' h 5';
}
if (connection.sourceBlock_.isInFlyout) {
// Don't bother maintaining a database of connections in a flyout.
return;
}
// Insert connection using binary search.
var pointerMin = 0;
var pointerMax = this.length;
while (pointerMin < pointerMax) {
var pointerMid = Math.floor((pointerMin + pointerMax) / 2);
if (this[pointerMid].y_ < connection.y_) {
pointerMin = pointerMid + 1;
} else if (this[pointerMid].y_ > connection.y_) {
pointerMax = pointerMid;
} else {
pointerMin = pointerMid;
break;
}
}
this.splice(pointerMin, 0, connection);
connection.inDB_ = true;
var xy = this.sourceBlock_.getRelativeToSurfaceXY();
var x = this.x_ - xy.x;
var y = this.y_ - xy.y;
Blockly.Connection.highlightedPath_ = Blockly.createSvgElement('path',
{'class': 'blocklyHighlightedConnectionPath',
'd': steps,
transform: 'translate(' + x + ',' + y + ')' +
(this.sourceBlock_.RTL ? ' scale(-1 1)' : '')},
this.sourceBlock_.getSvgRoot());
};
/**
* Remove a connection from the database. Must already exist in DB.
* @param {!Blockly.Connection} connection The connection to be removed.
* @private
* Remove the highlighting around this connection.
*/
Blockly.ConnectionDB.prototype.removeConnection_ = function(connection) {
if (!connection.inDB_) {
throw 'Connection not in database.';
}
connection.inDB_ = false;
// Find the connection using a binary search.
// About 10% faster than a linear search using indexOf.
var pointerMin = 0;
var pointerMax = this.length - 2;
var pointerMid = pointerMax;
while (pointerMin < pointerMid) {
if (this[pointerMid].y_ < connection.y_) {
pointerMin = pointerMid;
} else {
pointerMax = pointerMid;
}
pointerMid = Math.floor((pointerMin + pointerMax) / 2);
}
// Walk forward and back on the y axis looking for the connection.
// When found, splice it out of the array.
pointerMin = pointerMid;
pointerMax = pointerMid;
while (pointerMin >= 0 && this[pointerMin].y_ == connection.y_) {
if (this[pointerMin] == connection) {
this.splice(pointerMin, 1);
return;
}
pointerMin--;
}
do {
if (this[pointerMax] == connection) {
this.splice(pointerMax, 1);
return;
}
pointerMax++;
} while (pointerMax < this.length &&
this[pointerMax].y_ == connection.y_);
throw 'Unable to find connection in connectionDB.';
};
/**
* Initialize a set of connection DBs for a specified workspace.
* @param {!Blockly.Workspace} workspace The workspace this DB is for.
*/
Blockly.ConnectionDB.init = function(workspace) {
// Create four databases, one for each connection type.
var dbList = [];
dbList[Blockly.INPUT_VALUE] = new Blockly.ConnectionDB();
dbList[Blockly.OUTPUT_VALUE] = new Blockly.ConnectionDB();
dbList[Blockly.NEXT_STATEMENT] = new Blockly.ConnectionDB();
dbList[Blockly.PREVIOUS_STATEMENT] = new Blockly.ConnectionDB();
workspace.connectionDBList = dbList;
Blockly.Connection.prototype.unhighlight = function() {
goog.dom.removeNode(Blockly.Connection.highlightedPath_);
delete Blockly.Connection.highlightedPath_;
};

291
core/connection_db.js Normal file
View file

@ -0,0 +1,291 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2011 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Components for managing connections between blocks.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.ConnectionDB');
goog.require('Blockly.Connection');
/**
* Database of connections.
* Connections are stored in order of their vertical component. This way
* connections in an area may be looked up quickly using a binary search.
* @constructor
*/
Blockly.ConnectionDB = function() {
};
Blockly.ConnectionDB.prototype = new Array();
/**
* Don't inherit the constructor from Array.
* @type {!Function}
*/
Blockly.ConnectionDB.constructor = Blockly.ConnectionDB;
/**
* Add a connection to the database. Must not already exist in DB.
* @param {!Blockly.Connection} connection The connection to be added.
* @private
*/
Blockly.ConnectionDB.prototype.addConnection = function(connection) {
if (connection.inDB_) {
throw 'Connection already in database.';
}
if (connection.sourceBlock_.isInFlyout) {
// Don't bother maintaining a database of connections in a flyout.
return;
}
var position = this.findPositionForConnection_(connection);
this.splice(position, 0, connection);
connection.inDB_ = true;
};
/**
* Find the given connection.
* Starts by doing a binary search to find the approximate location, then
* linearly searches nearby for the exact connection.
* @param {Blockly.Connection} conn The connection to find.
* @return The index of the connection, or -1 if the connection was not found.
*/
Blockly.ConnectionDB.prototype.findConnection = function(conn) {
if (this.length == 0) {
return -1;
}
var bestGuess = this.findPositionForConnection_(conn);
if (bestGuess >= this.length) {
// Not in list
return -1;
}
var yPos = conn.y_;
// Walk forward and back on the y axis looking for the connection.
var pointerMin = bestGuess;
var pointerMax = bestGuess;
while(pointerMin >= 0 && this[pointerMin].y_ == yPos) {
if (this[pointerMin] == conn) {
return pointerMin;
}
pointerMin--;
}
while (pointerMax < this.length && this[pointerMax].y_ == yPos) {
if (this[pointerMax] == conn) {
return pointerMax;
}
pointerMax++;
}
return -1;
};
/**
* Finds a candidate position for inserting this connection into the list.
* This will be in the correct y order but makes no guarantees about ordering in
* the x axis.
* @param {Blockly.Connection} connection The connection to insert.
* @return {number} The candidate index.
* @private
*/
Blockly.ConnectionDB.prototype.findPositionForConnection_ = function(connection) {
if (this.length == 0) {
return 0;
}
var pointerMin = 0;
var pointerMax = this.length;
while (pointerMin < pointerMax) {
var pointerMid = Math.floor((pointerMin + pointerMax) / 2);
if (this[pointerMid].y_ < connection.y_) {
pointerMin = pointerMid + 1;
} else if (this[pointerMid].y_ > connection.y_) {
pointerMax = pointerMid;
} else {
pointerMin = pointerMid;
break;
}
}
return pointerMin;
};
/**
* Remove a connection from the database. Must already exist in DB.
* @param {!Blockly.Connection} connection The connection to be removed.
* @private
*/
Blockly.ConnectionDB.prototype.removeConnection_ = function(connection) {
if (!connection.inDB_) {
throw 'Connection not in database.';
}
var removalIndex = this.findConnection(connection);
if (removalIndex == -1) {
throw 'Unable to find connection in connectionDB.';
}
connection.inDB_ = false;
this.splice(removalIndex, 1);
};
/**
* Find all nearby connections to the given connection.
* Type checking does not apply, since this function is used for bumping.
* @param {!Blockly.Connection} connection The connection whose neighbours should
* be returned.
* @param {number} maxRadius The maximum radius to another connection.
* @return {!Array.<Blockly.Connection>} List of connections.
* @private
*/
Blockly.ConnectionDB.prototype.getNeighbours = function(connection, maxRadius) {
var db = this;
var currentX = connection.x_;
var currentY = connection.y_;
// Binary search to find the closest y location.
var pointerMin = 0;
var pointerMax = db.length - 2;
var pointerMid = pointerMax;
while (pointerMin < pointerMid) {
if (db[pointerMid].y_ < currentY) {
pointerMin = pointerMid;
} else {
pointerMax = pointerMid;
}
pointerMid = Math.floor((pointerMin + pointerMax) / 2);
}
// Walk forward and back on the y axis looking for the closest x,y point.
pointerMin = pointerMid;
pointerMax = pointerMid;
var neighbours = [];
var sourceBlock = connection.sourceBlock_;
if (db.length) {
while (pointerMin >= 0 && checkConnection_(pointerMin)) {
pointerMin--;
}
do {
pointerMax++;
} while (pointerMax < db.length && checkConnection_(pointerMax));
}
/**
* Computes if the current connection is within the allowed radius of another
* connection.
* This function is a closure and has access to outside variables.
* @param {number} yIndex The other connection's index in the database.
* @return {boolean} True if the current connection's vertical distance from
* the other connection is less than the allowed radius.
*/
function checkConnection_(yIndex) {
var dx = currentX - db[yIndex].x_;
var dy = currentY - db[yIndex].y_;
var r = Math.sqrt(dx * dx + dy * dy);
if (r <= maxRadius) {
neighbours.push(db[yIndex]);
}
return dy < maxRadius;
}
return neighbours;
};
Blockly.ConnectionDB.prototype.isInYRange_ = function(index, baseY, maxRadius) {
return (Math.abs(this[index].y_ - baseY) <= maxRadius);
}
/**
* Find the closest compatible connection to this connection.
* @param {Blockly.Connection} conn The connection searching for a compatible
* mate.
* @param {number} maxRadius The maximum radius to another connection.
* @param {number} dx Horizontal offset between this connection's location
* in the database and the current location (as a result of dragging).
* @param {number} dy Vertical offset between this connection's location
* in the database and the current location (as a result of dragging).
* @return ?Blockly.Connection the closest valid connection.
* another connection or null, and 'radius' which is the distance.
*/
Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius, dx,
dy) {
// Don't bother.
if (!this.length) {
return null;
}
// Stash the values of x and y from before the drag.
var baseY = conn.y_;
var baseX = conn.x_;
conn.x_ = baseX + dx;
conn.y_ = baseY + dy;
// findPositionForConnection finds an index for insertion, which is always
// after any block with the same y index. We want to search both forward
// and back, so search on both sides of the index.
var closestIndex = this.findPositionForConnection_(conn);
var bestConnection = null;
var bestRadius = maxRadius;
var temp;
// Walk forward and back on the y axis looking for the closest x,y point.
var pointerMin = closestIndex - 1;
while (pointerMin >= 0 &&
this.isInYRange_(pointerMin, conn.y_, maxRadius)) {
temp = this[pointerMin];
if (conn.isConnectionAllowed(temp, bestRadius)) {
bestConnection = temp;
bestRadius = temp.distanceFrom(conn);
}
pointerMin--;
}
var pointerMax = closestIndex;
while (pointerMax < this.length && this.isInYRange_(pointerMax, conn.y_,
maxRadius)) {
temp = this[pointerMax];
if (conn.isConnectionAllowed(temp, bestRadius)) {
bestConnection = temp;
bestRadius = temp.distanceFrom(conn);
}
pointerMax++;
}
// Reset the values of x and y.
conn.x_ = baseX;
conn.y_ = baseY;
return bestConnection;
};
/**
* Initialize a set of connection DBs for a specified workspace.
* @param {!Blockly.Workspace} workspace The workspace this DB is for.
*/
Blockly.ConnectionDB.init = function(workspace) {
// Create four databases, one for each connection type.
var dbList = [];
dbList[Blockly.INPUT_VALUE] = new Blockly.ConnectionDB();
dbList[Blockly.OUTPUT_VALUE] = new Blockly.ConnectionDB();
dbList[Blockly.NEXT_STATEMENT] = new Blockly.ConnectionDB();
dbList[Blockly.PREVIOUS_STATEMENT] = new Blockly.ConnectionDB();
workspace.connectionDBList = dbList;
};

View file

@ -30,8 +30,15 @@ goog.provide('Blockly.Events');
/**
* Group ID for new events. Grouped events are indivisible.
* @type {string}
* @private
*/
Blockly.Events.group = '';
Blockly.Events.group_ = '';
/**
* Sets whether events should be added to the undo stack.
* @type {boolean}
*/
Blockly.Events.recordUndo = true;
/**
* Allow change events to be created and fired.
@ -90,7 +97,7 @@ Blockly.Events.fire = function(event) {
* @private
*/
Blockly.Events.fireNow_ = function() {
var queue = Blockly.Events.filter_(Blockly.Events.FIRE_QUEUE_);
var queue = Blockly.Events.filter(Blockly.Events.FIRE_QUEUE_);
Blockly.Events.FIRE_QUEUE_.length = 0;
for (var i = 0, event; event = queue[i]; i++) {
var workspace = Blockly.Workspace.getById(event.workspaceId);
@ -104,9 +111,8 @@ Blockly.Events.fireNow_ = function() {
* Filter the queued events and merge duplicates.
* @param {!Array.<!Blockly.Events.Abstract>} queueIn Array of events.
* @return {!Array.<!Blockly.Events.Abstract>} Array of filtered events.
* @private
*/
Blockly.Events.filter_ = function(queueIn) {
Blockly.Events.filter = function(queueIn) {
var queue = goog.array.clone(queueIn);
// Merge duplicates. O(n^2), but n should be very small.
for (var i = 0, event1; event1 = queue[i]; i++) {
@ -138,6 +144,14 @@ Blockly.Events.filter_ = function(queueIn) {
queue.splice(i, 1);
}
}
// Move mutation events to the top of the queue.
// Intentionally skip first event.
for (var i = 1, event; event = queue[i]; i++) {
if (event.type == Blockly.Events.CHANGE &&
event.element == 'mutation') {
queue.unshift(queue.splice(i, 1)[0]);
}
}
return queue;
};
@ -164,6 +178,27 @@ Blockly.Events.isEnabled = function() {
return Blockly.Events.disabled_ == 0;
};
/**
* Current group.
* @return {string} ID string.
*/
Blockly.Events.getGroup = function() {
return Blockly.Events.group_;
};
/**
* Start or stop a group.
* @param {boolean|string} state True to start new group, false to end group.
* String to set group explicitly.
*/
Blockly.Events.setGroup = function(state) {
if (typeof state == 'boolean') {
Blockly.Events.group_ = state ? Blockly.genUid() : '';
} else {
Blockly.Events.group_ = state;
}
};
/**
* Abstract class for an event.
* @param {!Blockly.Block} block The block.
@ -172,7 +207,8 @@ Blockly.Events.isEnabled = function() {
Blockly.Events.Abstract = function(block) {
this.blockId = block.id;
this.workspaceId = block.workspace.id;
this.group = Blockly.Events.group;
this.group = Blockly.Events.group_;
this.recordUndo = Blockly.Events.recordUndo;
};
/**
@ -201,6 +237,24 @@ goog.inherits(Blockly.Events.Create, Blockly.Events.Abstract);
*/
Blockly.Events.Create.prototype.type = Blockly.Events.CREATE;
/**
* Run a creation event.
* @param {boolean} forward True if run forward, false if run backward (undo).
*/
Blockly.Events.Create.prototype.run = function(forward) {
if (forward) {
var workspace = Blockly.Workspace.getById(this.workspaceId);
var xml = goog.dom.createDom('xml');
xml.appendChild(this.xml);
Blockly.Xml.domToWorkspace(workspace, xml);
} else {
var block = Blockly.Block.getById(this.blockId);
if (block) {
block.dispose(false, true);
}
}
};
/**
* Class for a block deletion event.
* @param {!Blockly.Block} block The deleted block.
@ -222,6 +276,24 @@ goog.inherits(Blockly.Events.Delete, Blockly.Events.Abstract);
*/
Blockly.Events.Delete.prototype.type = Blockly.Events.DELETE;
/**
* Run a deletion event.
* @param {boolean} forward True if run forward, false if run backward (undo).
*/
Blockly.Events.Delete.prototype.run = function(forward) {
if (forward) {
var block = Blockly.Block.getById(this.blockId);
if (block) {
block.dispose(false, true);
}
} else {
var workspace = Blockly.Workspace.getById(this.workspaceId);
var xml = goog.dom.createDom('xml');
xml.appendChild(this.oldXml);
Blockly.Xml.domToWorkspace(workspace, xml);
}
};
/**
* Class for a block change event.
* @param {!Blockly.Block} block The changed block.
@ -255,6 +327,57 @@ Blockly.Events.Change.prototype.isNull = function() {
return this.oldValue == this.newValue;
};
/**
* Run a change event.
* @param {boolean} forward True if run forward, false if run backward (undo).
*/
Blockly.Events.Change.prototype.run = function(forward) {
var block = Blockly.Block.getById(this.blockId);
if (!block) {
return;
}
var value = forward ? this.newValue : this.oldValue;
switch (this.element) {
case 'field':
var field = block.getField(this.name);
if (field) {
field.setValue(value);
} else {
console.warn("Can't set non-existant field: " + this.name);
}
break;
case 'comment':
block.setCommentText(value || null);
break;
case 'collapsed':
block.setCollapsed(value);
break;
case 'disabled':
block.setDisabled(value);
break;
case 'inline':
block.setInputsInline(value);
break;
case 'mutation':
if (block.mutator) {
// Close the mutator (if open) since we don't want to update it.
block.mutator.setVisible(false);
}
var oldMutation = '';
if (block.mutationToDom) {
var oldMutationDom = block.mutationToDom();
oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom);
}
if (block.domToMutation) {
var dom = Blockly.Xml.textToDom('<xml>' + value + '</xml>');
block.domToMutation(dom.firstChild);
}
Blockly.Events.fire(new Blockly.Events.Change(
block, 'mutation', null, oldMutation, value));
break;
}
};
/**
* Class for a block move event. Created before the move.
* @param {!Blockly.Block} block The moved block.
@ -317,3 +440,47 @@ Blockly.Events.Move.prototype.isNull = function() {
this.oldInputName == this.newInputName &&
goog.math.Coordinate.equals(this.oldCoordinate, this.newCoordinate);
};
/**
* Run a move event.
* @param {boolean} forward True if run forward, false if run backward (undo).
*/
Blockly.Events.Move.prototype.run = function(forward) {
var block = Blockly.Block.getById(this.blockId);
if (!block) {
return;
}
var parentId = forward ? this.newParentId : this.oldParentId;
var inputName = forward ? this.newInputName : this.oldInputName;
var coordinate = forward ? this.newCoordinate : this.oldCoordinate;
var parentBlock = null;
if (parentId) {
parentBlock = Blockly.Block.getById(parentId);
if (!parentBlock) {
return;
}
}
if (block.getParent()) {
block.unplug();
}
if (coordinate) {
var xy = block.getRelativeToSurfaceXY();
block.moveBy(coordinate.x - xy.x, coordinate.y - xy.y);
} else {
var blockConnection = block.outputConnection || block.previousConnection;
var parentConnection;
if (inputName) {
var input = parentBlock.getInput(inputName);
if (input) {
parentConnection = input.connection;
} else {
console.warn("Can't connect to non-existant input: " + inputName);
}
} else if (blockConnection.type == Blockly.PREVIOUS_STATEMENT) {
parentConnection = parentBlock.nextConnection;
}
if (parentConnection) {
blockConnection.connect(parentConnection);
}
}
};

View file

@ -812,7 +812,8 @@ Blockly.Flyout.prototype.createBlockFunc_ = function(originBlock) {
}
block.moveBy(xyOld.x - xyNew.x, xyOld.y - xyNew.y);
Blockly.Events.enable();
if (Blockly.Events.isEnabled() && !block.isShadow()) {
if (Blockly.Events.isEnabled()) {
Blockly.Events.setGroup(true);
Blockly.Events.fire(new Blockly.Events.Create(block));
}
if (flyout.autoClose) {

View file

@ -280,29 +280,36 @@ Blockly.Mutator.prototype.workspaceChanged_ = function() {
// When the mutator's workspace changes, update the source block.
if (this.rootBlock_.workspace == this.workspace_) {
var oldMutationDom = this.block_.mutationToDom();
Blockly.Events.setGroup(true);
var block = this.block_;
var oldMutationDom = block.mutationToDom();
var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom);
// Switch off rendering while the source block is rebuilt.
var savedRendered = this.block_.rendered;
this.block_.rendered = false;
var savedRendered = block.rendered;
block.rendered = false;
// Allow the source block to rebuild itself.
this.block_.compose(this.rootBlock_);
block.compose(this.rootBlock_);
// Restore rendering and show the changes.
this.block_.rendered = savedRendered;
block.rendered = savedRendered;
// Mutation may have added some elements that need initalizing.
this.block_.initSvg();
var newMutationDom = this.block_.mutationToDom();
block.initSvg();
var newMutationDom = block.mutationToDom();
var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom);
if (oldMutation != newMutation) {
Blockly.Events.fire(new Blockly.Events.Change(
this.block_, 'mutation', null, oldMutation, newMutation));
goog.Timer.callOnce(
this.block_.bumpNeighbours_, Blockly.BUMP_DELAY, this.block_);
block, 'mutation', null, oldMutation, newMutation));
// Ensure that any bump is part of this mutation's event group.
var group = Blockly.Events.getGroup();
setTimeout(function() {
Blockly.Events.setGroup(group);
block.bumpNeighbours_();
}, Blockly.BUMP_DELAY);
}
if (this.block_.rendered) {
this.block_.render();
if (block.rendered) {
block.render();
}
this.resizeBubble_();
Blockly.Events.setGroup(false);
}
};

View file

@ -158,7 +158,7 @@ Blockly.ScrollbarPair.prototype.set = function(x, y) {
this.workspace_.setMetrics(xyRatio);
this.hScroll.svgKnob_.setAttribute('x', hKnobValue);
this.vScroll.svgKnob_.setAttribute('y', vKnobValue);
}
};
/**
* Helper to calculate the ratio of knob value to bar length.
@ -172,7 +172,7 @@ Blockly.ScrollbarPair.prototype.getRatio_ = function(knobValue, barLength) {
return 0;
}
return ratio;
}
};
// --------------------------------------------------------------------

View file

@ -80,11 +80,13 @@ Blockly.Variables.allVariables = function(root) {
* @param {!Blockly.Workspace} workspace Workspace rename variables in.
*/
Blockly.Variables.renameVariable = function(oldName, newName, workspace) {
Blockly.Events.setGroup(true);
var blocks = workspace.getAllBlocks();
// Iterate through every block.
for (var i = 0; i < blocks.length; i++) {
blocks[i].renameVar(oldName, newName);
}
Blockly.Events.setGroup(false);
};
/**

View file

@ -82,6 +82,7 @@ Blockly.WidgetDiv.show = function(newOwner, rtl, dispose) {
Blockly.WidgetDiv.DIV.style.top = xy.y + 'px';
Blockly.WidgetDiv.DIV.style.direction = rtl ? 'rtl' : 'ltr';
Blockly.WidgetDiv.DIV.style.display = 'block';
Blockly.Events.setGroup(true);
};
/**
@ -97,6 +98,7 @@ Blockly.WidgetDiv.hide = function() {
Blockly.WidgetDiv.dispose_ && Blockly.WidgetDiv.dispose_();
Blockly.WidgetDiv.dispose_ = null;
goog.dom.removeChildren(Blockly.WidgetDiv.DIV);
Blockly.Events.setGroup(false);
}
};

View file

@ -51,6 +51,10 @@ Blockly.Workspace = function(opt_options) {
this.listeners_ = [];
/** @type {!Array.<!Function>} */
this.tapListeners_ = [];
/** @type {!Array.<!Blockly.Events.Abstract>} */
this.undoStack_ = [];
/** @type {!Array.<!Blockly.Events.Abstract>} */
this.redoStack_ = [];
};
/**
@ -59,6 +63,12 @@ Blockly.Workspace = function(opt_options) {
*/
Blockly.Workspace.prototype.rendered = false;
/**
* Maximum number of undo events in stack.
* @type {number} 0 to turn off undo, Infinity for unlimited.
*/
Blockly.Workspace.prototype.MAX_UNDO = 1024;
/**
* Dispose of this workspace.
* Unlink from all DOM elements to prevent memory leaks.
@ -198,6 +208,31 @@ Blockly.Workspace.prototype.remainingCapacity = function() {
return this.options.maxBlocks - this.getAllBlocks().length;
};
/**
* Undo or redo the previous action.
* @param {boolean} redo False if undo, true if redo.
*/
Blockly.Workspace.prototype.undo = function(redo) {
var sourceStack = redo ? this.redoStack_ : this.undoStack_;
var event = sourceStack.pop();
if (!event) {
return;
}
var events = [event];
// Do another undo/redo if the next one is of the same group.
while (sourceStack.length && event.group &&
event.group == sourceStack[sourceStack.length - 1].group) {
events.push(sourceStack.pop());
}
events = Blockly.Events.filter(events);
Blockly.Events.recordUndo = false;
for (var i = 0, event; event = events[i]; i++) {
event.run(redo);
(redo ? this.undoStack_ : this.redoStack_).push(event);
}
Blockly.Events.recordUndo = true;
};
/**
* When something in this workspace changes, call a function.
* @param {!Function} func Function to call.
@ -225,6 +260,13 @@ Blockly.Workspace.prototype.removeChangeListener = function(func) {
* @param {!Blockly.Events.Abstract} event Event to fire.
*/
Blockly.Workspace.prototype.fireChangeListener = function(event) {
if (event.recordUndo) {
this.undoStack_.push(event);
this.redoStack_.length = 0;
if (this.undoStack_.length > this.MAX_UNDO) {
this.undoStack_.unshift();
}
}
for (var i = 0, func; func = this.listeners_[i]; i++) {
func(event);
}

View file

@ -663,6 +663,7 @@ Blockly.WorkspaceSvg.prototype.onMouseWheel_ = function(e) {
* @private
*/
Blockly.WorkspaceSvg.prototype.cleanUp_ = function() {
Blockly.Events.setGroup(true);
var topBlocks = this.getTopBlocks(true);
var cursorY = 0;
for (var i = 0, block; block = topBlocks[i]; i++) {
@ -672,6 +673,7 @@ Blockly.WorkspaceSvg.prototype.cleanUp_ = function() {
cursorY = block.getRelativeToSurfaceXY().y +
block.getHeightWidth().height + Blockly.BlockSvg.MIN_BLOCK_Y;
}
Blockly.Events.setGroup(false);
// Fire an event to allow scrollbars to resize.
Blockly.fireUiEvent(window, 'resize');
};

View file

@ -142,7 +142,7 @@
<select id="format">
<option value="JavaScript">JavaScript</option>
<option value="JSON">JSON</option>
<option value="Manual">Manual edit...</option>
<option value="Manual">Manual edit&hellip;</option>
</select>
</h3>
</td>
@ -161,6 +161,7 @@
<option value="Python">Python</option>
<option value="PHP">PHP</option>
<option value="Dart">Dart</option>
<option value="Lua">Lua</option>
</select>
</h3>
</td>

View file

@ -244,7 +244,7 @@ Code.LANG = Code.getLang();
* List of tab names.
* @private
*/
Code.TABS_ = ['blocks', 'javascript', 'php', 'python', 'dart', 'xml'];
Code.TABS_ = ['blocks', 'javascript', 'php', 'python', 'dart', 'lua', 'xml'];
Code.selected = 'blocks';
@ -341,6 +341,14 @@ Code.renderContent = function() {
code = prettyPrintOne(code, 'dart');
content.innerHTML = code;
}
} else if (content.id == 'content_lua') {
code = Blockly.Lua.workspaceToCode(Code.workspace);
content.textContent = code;
if (typeof prettyPrintOne == 'function') {
code = content.innerHTML;
code = prettyPrintOne(code, 'lua');
content.innerHTML = code;
}
}
};

View file

@ -12,6 +12,7 @@
<script src="../../python_compressed.js"></script>
<script src="../../php_compressed.js"></script>
<script src="../../dart_compressed.js"></script>
<script src="../../lua_compressed.js"></script>
<script src="code.js"></script>
</head>
<body>
@ -41,6 +42,8 @@
<td class="tabmin">&nbsp;</td>
<td id="tab_dart" class="taboff">Dart</td>
<td class="tabmin">&nbsp;</td>
<td id="tab_lua" class="taboff">Lua</td>
<td class="tabmin">&nbsp;</td>
<td id="tab_xml" class="taboff">XML</td>
<td class="tabmax">
<button id="trashButton" class="notext" title="...">
@ -67,6 +70,7 @@
<pre id="content_php" class="content"></pre>
<pre id="content_python" class="content"></pre>
<pre id="content_dart" class="content"></pre>
<pre id="content_lua" class="content"></pre>
<textarea id="content_xml" class="content" wrap="off"></textarea>
<xml id="toolbox" style="display: none">

View file

@ -23,7 +23,7 @@
<p>This is a simple demo of injecting Blockly into a fixed-sized 'div' element.</p>
<p>&rarr; More info on <a href="https://developers.google.com/blockly/installation/injecting-fixed-size">injecting fixed-sized Blockly</a>...</p>
<p>&rarr; More info on <a href="https://developers.google.com/blockly/installation/injecting-fixed-size">injecting fixed-sized Blockly</a>&hellip;</p>
<div id="blocklyDiv" style="height: 480px; width: 600px;"></div>

View file

@ -24,7 +24,7 @@
<p>This is a simple demo of generating code from blocks.</p>
<p>&rarr; More info on <a href="https://developers.google.com/blockly/installation/code-generators">Code Generators</a>...</p>
<p>&rarr; More info on <a href="https://developers.google.com/blockly/installation/code-generators">Code Generators</a>&hellip;</p>
<p>
<button onclick="showCode()">Show JavaScript</button>

View file

@ -39,7 +39,7 @@
<p>This is a demo of giving instant feedback as blocks are changed.</p>
<p>&rarr; More info on <a href="https://developers.google.com/blockly/installation/code-generators#realtime_generation">Realtime generation</a>...</p>
<p>&rarr; More info on <a href="https://developers.google.com/blockly/installation/code-generators#realtime_generation">Realtime generation</a>&hellip;</p>
<table>
<tr>

View file

@ -168,7 +168,7 @@
</td>
<td>
<div><a href="code/index.html">Code Editor</a></div>
<div>Export a Blockly program into JavaScript, Python, PHP, Dart or XML.</div>
<div>Export a Blockly program into JavaScript, Python, PHP, Dart, Lua or XML.</div>
</td>
</tr>

View file

@ -25,7 +25,7 @@
<p>This is a simple demo of executing code with a sandboxed JavaScript interpreter.</p>
<p>&rarr; More info on <a href="https://developers.google.com/blockly/installation/js-interpreter">JS Interpreter</a>...</p>
<p>&rarr; More info on <a href="https://developers.google.com/blockly/installation/js-interpreter">JS Interpreter</a>&hellip;</p>
<p>
<button onclick="parseCode()">Parse JavaScript</button>

View file

@ -39,7 +39,7 @@
CSS or tables to create an area for it.
Next, <a href="overlay.html">inject Blockly</a> over that area.</p>
<p>&rarr; More info on <a href="https://developers.google.com/blockly/installation/injecting-resizable">injecting resizable Blockly</a>...</p>
<p>&rarr; More info on <a href="https://developers.google.com/blockly/installation/injecting-resizable">injecting resizable Blockly</a>&hellip;</p>
</td>
</tr>
<tr>

View file

@ -42,7 +42,7 @@
A resize handler keeps it in position as the page changes.
</p>
<p>&rarr; More info on <a href="https://developers.google.com/blockly/installation/injecting-resizable">injecting resizable Blockly</a>...</p>
<p>&rarr; More info on <a href="https://developers.google.com/blockly/installation/injecting-resizable">injecting resizable Blockly</a>&hellip;</p>
</td>
</tr>
<tr>

View file

@ -41,7 +41,7 @@
}
</script>
<p>&rarr; More info on <a href="https://developers.google.com/blockly/installation/cloud-storage">Cloud Storage</a>...</p>
<p>&rarr; More info on <a href="https://developers.google.com/blockly/installation/cloud-storage">Cloud Storage</a>&hellip;</p>
<p>
<button onclick="BlocklyStorage.link()">Save Blocks</button>

View file

@ -23,7 +23,7 @@
<p>This is a demo of a complex category structure for the toolbox.</p>
<p>&rarr; More info on the <a href="https://developers.google.com/blockly/installation/toolbox">Toolbox</a>...</p>
<p>&rarr; More info on the <a href="https://developers.google.com/blockly/installation/toolbox">Toolbox</a>&hellip;</p>
<div id="blocklyDiv" style="height: 600px; width: 800px;"></div>

188
generators/lua.js Normal file
View file

@ -0,0 +1,188 @@
/**
* @license
* Visual Blocks Language
*
* Copyright 2016 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Helper functions for generating Lua for blocks.
* @author rodrigoq@google.com (Rodrigo Queiro)
* Based on Ellen Spertus's blocky-lua project.
*/
'use strict';
goog.provide('Blockly.Lua');
goog.require('Blockly.Generator');
/**
* Lua code generator.
* @type {!Blockly.Generator}
*/
Blockly.Lua = new Blockly.Generator('Lua');
/**
* List of illegal variable names.
* This is not intended to be a security feature. Blockly is 100% client-side,
* so bypassing this list is trivial. This is intended to prevent users from
* accidentally clobbering a built-in object or function.
* @private
*/
Blockly.Lua.addReservedWords(
// Special character
'_' +
// From theoriginalbit's script:
// https://github.com/espertus/blockly-lua/issues/6
'__inext,assert,bit,colors,colours,coroutine,disk,dofile,error,fs,' +
'fetfenv,getmetatable,gps,help,io,ipairs,keys,loadfile,loadstring,math,' +
'native,next,os,paintutils,pairs,parallel,pcall,peripheral,print,' +
'printError,rawequal,rawget,rawset,read,rednet,redstone,rs,select,' +
'setfenv,setmetatable,sleep,string,table,term,textutils,tonumber,' +
'tostring,turtle,type,unpack,vector,write,xpcall,_VERSION,__indext,' +
// Not included in the script, probably because it wasn't enabled:
'HTTP,' +
// Keywords (http://www.lua.org/pil/1.3.html).
'and,break,do,else,elseif,end,false,for,function,if,in,local,nil,not,or,' +
'repeat,return,then,true,until,while,' +
// Metamethods (http://www.lua.org/manual/5.2/manual.html).
'add,sub,mul,div,mod,pow,unm,concat,len,eq,lt,le,index,newindex,call,' +
// Basic functions (http://www.lua.org/manual/5.2/manual.html, section 6.1).
'assert,collectgarbage,dofile,error,_G,getmetatable,inpairs,load,' +
'loadfile,next,pairs,pcall,print,rawequal,rawget,rawlen,rawset,select,' +
'setmetatable,tonumber,tostring,type,_VERSION,xpcall,' +
// Modules (http://www.lua.org/manual/5.2/manual.html, section 6.3).
'require,package,string,table,math,bit32,io,file,os,debug'
);
/**
* Order of operation ENUMs.
* http://www.lua.org/manual/5.3/manual.html#3.4.8
*/
Blockly.Lua.ORDER_ATOMIC = 0; // literals
// The next level was not explicit in documentation and inferred by Ellen.
Blockly.Lua.ORDER_HIGH = 1; // Function calls, tables[]
Blockly.Lua.ORDER_EXPONENTIATION = 2; // ^
Blockly.Lua.ORDER_UNARY = 3; // not # - ()
Blockly.Lua.ORDER_MULTIPLICATIVE = 4; // * / %
Blockly.Lua.ORDER_ADDITIVE = 5; // + -
Blockly.Lua.ORDER_CONCATENATION = 6; // ..
Blockly.Lua.ORDER_RELATIONAL = 7; // < > <= >= ~= ==
Blockly.Lua.ORDER_AND = 8; // and
Blockly.Lua.ORDER_OR = 9; // or
Blockly.Lua.ORDER_NONE = 99;
/**
* Initialise the database of variable names.
* @param {!Blockly.Workspace} workspace Workspace to generate code from.
*/
Blockly.Lua.init = function(workspace) {
// Create a dictionary of definitions to be printed before the code.
Blockly.Lua.definitions_ = Object.create(null);
// Create a dictionary mapping desired function names in definitions_
// to actual function names (to avoid collisions with user functions).
Blockly.Lua.functionNames_ = Object.create(null);
if (!Blockly.Lua.variableDB_) {
Blockly.Lua.variableDB_ =
new Blockly.Names(Blockly.Lua.RESERVED_WORDS_);
} else {
Blockly.Lua.variableDB_.reset();
}
};
/**
* Prepend the generated code with the variable definitions.
* @param {string} code Generated code.
* @return {string} Completed code.
*/
Blockly.Lua.finish = function(code) {
// Convert the definitions dictionary into a list.
var definitions = [];
for (var name in Blockly.Lua.definitions_) {
definitions.push(Blockly.Lua.definitions_[name]);
}
// Clean up temporary data.
delete Blockly.Lua.definitions_;
delete Blockly.Lua.functionNames_;
Blockly.Lua.variableDB_.reset();
return definitions.join('\n\n') + '\n\n\n' + code;
};
/**
* Naked values are top-level blocks with outputs that aren't plugged into
* anything. In Lua, an expression is not a legal statement, so we must assign
* the value to the (conventionally ignored) _.
* http://lua-users.org/wiki/ExpressionsAsStatements
* @param {string} line Line of generated code.
* @return {string} Legal line of code.
*/
Blockly.Lua.scrubNakedValue = function(line) {
return 'local _ = ' + line + '\n';
};
/**
* Encode a string as a properly escaped Lua string, complete with
* quotes.
* @param {string} string Text to encode.
* @return {string} Lua string.
* @private
*/
Blockly.Lua.quote_ = function(string) {
// TODO: This is a quick hack. Replace with goog.string.quote
string = string.replace(/\\/g, '\\\\')
.replace(/\n/g, '\\\n')
.replace(/'/g, '\\\'');
return '\'' + string + '\'';
};
/**
* Common tasks for generating Lua from blocks.
* Handles comments for the specified block and any connected value blocks.
* Calls any statements following this block.
* @param {!Blockly.Block} block The current block.
* @param {string} code The Lua code created for this block.
* @return {string} Lua code with comments and subsequent blocks added.
* @private
*/
Blockly.Lua.scrub_ = function(block, code) {
var commentCode = '';
// Only collect comments for blocks that aren't inline.
if (!block.outputConnection || !block.outputConnection.targetConnection) {
// Collect comment for this block.
var comment = block.getCommentText();
if (comment) {
commentCode += Blockly.Lua.prefixLines(comment, '-- ') + '\n';
}
// Collect comments for all value arguments.
// Don't collect comments for nested statements.
for (var x = 0; x < block.inputList.length; x++) {
if (block.inputList[x].type == Blockly.INPUT_VALUE) {
var childBlock = block.inputList[x].connection.targetBlock();
if (childBlock) {
comment = Blockly.Lua.allNestedComments(childBlock);
if (comment) {
commentCode += Blockly.Lua.prefixLines(comment, '-- ');
}
}
}
}
}
var nextBlock = block.nextConnection && block.nextConnection.targetBlock();
var nextCode = Blockly.Lua.blockToCode(nextBlock);
return commentCode + code + nextCode;
};

90
generators/lua/colour.js Normal file
View file

@ -0,0 +1,90 @@
/**
* @license
* Visual Blocks Language
*
* Copyright 2016 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Generating Lua for colour blocks.
* @author rodrigoq@google.com (Rodrigo Queiro)
*/
'use strict';
goog.provide('Blockly.Lua.colour');
goog.require('Blockly.Lua');
Blockly.Lua['colour_picker'] = function(block) {
// Colour picker.
var code = '\'' + block.getFieldValue('COLOUR') + '\'';
return [code, Blockly.Lua.ORDER_ATOMIC];
};
Blockly.Lua['colour_random'] = function(block) {
// Generate a random colour.
var code = 'string.format("#%06x", math.random(0, 2^24 - 1))';
return [code, Blockly.Lua.ORDER_HIGH];
};
Blockly.Lua['colour_rgb'] = function(block) {
// Compose a colour from RGB components expressed as percentages.
var functionName = Blockly.Lua.provideFunction_(
'colour_rgb',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(r, g, b)',
' r = math.floor(math.min(100, math.max(0, r)) * 2.55 + .5)',
' g = math.floor(math.min(100, math.max(0, g)) * 2.55 + .5)',
' b = math.floor(math.min(100, math.max(0, b)) * 2.55 + .5)',
' return string.format("#%02x%02x%02x", r, g, b)',
'end']);
var r = Blockly.Lua.valueToCode(block, 'RED',
Blockly.Lua.ORDER_NONE) || 0;
var g = Blockly.Lua.valueToCode(block, 'GREEN',
Blockly.Lua.ORDER_NONE) || 0;
var b = Blockly.Lua.valueToCode(block, 'BLUE',
Blockly.Lua.ORDER_NONE) || 0;
var code = functionName + '(' + r + ', ' + g + ', ' + b + ')';
return [code, Blockly.Lua.ORDER_HIGH];
};
Blockly.Lua['colour_blend'] = function(block) {
// Blend two colours together.
var functionName = Blockly.Lua.provideFunction_(
'colour_blend',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ +
'(colour1, colour2, ratio)',
' local r1 = tonumber(string.sub(colour1, 2, 3), 16)',
' local r2 = tonumber(string.sub(colour2, 2, 3), 16)',
' local g1 = tonumber(string.sub(colour1, 4, 5), 16)',
' local g2 = tonumber(string.sub(colour2, 4, 5), 16)',
' local b1 = tonumber(string.sub(colour1, 6, 7), 16)',
' local b2 = tonumber(string.sub(colour2, 6, 7), 16)',
' local ratio = math.min(1, math.max(0, ratio))',
' local r = math.floor(r1 * (1 - ratio) + r2 * ratio + .5)',
' local g = math.floor(g1 * (1 - ratio) + g2 * ratio + .5)',
' local b = math.floor(b1 * (1 - ratio) + b2 * ratio + .5)',
' return string.format("#%02x%02x%02x", r, g, b)',
'end']);
var colour1 = Blockly.Lua.valueToCode(block, 'COLOUR1',
Blockly.Lua.ORDER_NONE) || '\'#000000\'';
var colour2 = Blockly.Lua.valueToCode(block, 'COLOUR2',
Blockly.Lua.ORDER_NONE) || '\'#000000\'';
var ratio = Blockly.Lua.valueToCode(block, 'RATIO',
Blockly.Lua.ORDER_NONE) || 0;
var code = functionName + '(' + colour1 + ', ' + colour2 + ', ' + ratio + ')';
return [code, Blockly.Lua.ORDER_HIGH];
};

363
generators/lua/lists.js Normal file
View file

@ -0,0 +1,363 @@
/**
* @license
* Visual Blocks Language
*
* Copyright 2016 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Generating Lua for list blocks.
* @author rodrigoq@google.com (Rodrigo Queiro)
*/
'use strict';
goog.provide('Blockly.Lua.lists');
goog.require('Blockly.Lua');
Blockly.Lua['lists_create_empty'] = function(block) {
// Create an empty list.
// List literals must be parenthesized before indexing into.
return ['({})', Blockly.Lua.ORDER_ATOMIC];
};
Blockly.Lua['lists_create_with'] = function(block) {
// Create a list with any number of elements of any type.
var code = new Array(block.itemCount_);
for (var n = 0; n < block.itemCount_; n++) {
code[n] = Blockly.Lua.valueToCode(block, 'ADD' + n,
Blockly.Lua.ORDER_NONE) || 'None';
}
code = '({' + code.join(', ') + '})';
return [code, Blockly.Lua.ORDER_ATOMIC];
};
Blockly.Lua['lists_repeat'] = function(block) {
// Create a list with one element repeated.
var functionName = Blockly.Lua.provideFunction_(
'create_list_repeated',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(item, count)',
' local t = {}',
' for i = 1, count do',
' table.insert(t, item)',
' end',
' return t',
'end']);
var argument0 = Blockly.Lua.valueToCode(block, 'ITEM',
Blockly.Lua.ORDER_NONE) || 'None';
var argument1 = Blockly.Lua.valueToCode(block, 'NUM',
Blockly.Lua.ORDER_NONE) || '0';
var code = functionName + '(' + argument0 + ', ' + argument1 + ')';
return [code, Blockly.Lua.ORDER_HIGH];
};
Blockly.Lua['lists_length'] = function(block) {
// String or array length.
var argument0 = Blockly.Lua.valueToCode(block, 'VALUE',
Blockly.Lua.ORDER_HIGH) || '({})';
return ['#' + argument0, Blockly.Lua.ORDER_HIGH];
};
Blockly.Lua['lists_isEmpty'] = function(block) {
// Is the string null or array empty?
var argument0 = Blockly.Lua.valueToCode(block, 'VALUE',
Blockly.Lua.ORDER_HIGH) || '({})';
var code = '#' + argument0 + ' == 0';
return [code, Blockly.Lua.ORDER_RELATIONAL];
};
Blockly.Lua['lists_indexOf'] = function(block) {
// Find an item in the list.
var argument0 = Blockly.Lua.valueToCode(block, 'FIND',
Blockly.Lua.ORDER_NONE) || '\'\'';
var argument1 = Blockly.Lua.valueToCode(block, 'VALUE',
Blockly.Lua.ORDER_NONE) || '({})';
var functionName;
if (block.getTitleValue('END') == 'FIRST') {
functionName = Blockly.Lua.provideFunction_(
'first_index',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t, elem)',
' for k, v in ipairs(t) do',
' if v == elem then',
' return k',
' end',
' end',
' return 0',
'end']);
} else {
functionName = Blockly.Lua.provideFunction_(
'last_index',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t, elem)',
' for i = #t, 1, -1 do',
' if t[i] == elem then',
' return i',
' end',
' end',
' return 0',
'end']);
}
var code = functionName + '(' + argument1 + ', ' + argument0 + ')';
return [code, Blockly.Lua.ORDER_HIGH];
};
/**
* Returns an expression calculating the index into a list.
* @private
* @param {string} listname Name of the list, used to calculate length.
* @param {string} where The method of indexing, selected by dropdown in Blockly
* @param {=string} opt_at The optional offset when indexing from start/end.
* @return {string} Index expression.
*/
Blockly.Lua.lists.getIndex_ = function(listname, where, opt_at) {
if (where == 'FIRST') {
return 1;
} else if (where == 'FROM_END') {
return '#' + listname + ' + 1 - ' + opt_at;
} else if (where == 'LAST') {
return '#' + listname;
} else if (where == 'RANDOM') {
return 'math.random(#' + listname + ')';
} else {
return opt_at;
}
};
/**
* Counter for generating unique symbols.
* @private
* @type {number}
*/
Blockly.Lua.lists.gensym_counter_ = 0;
/**
* Generate a unique symbol.
* @private
* @return {string} unique symbol, eg 'G123'
*/
Blockly.Lua.lists.gensym_ = function() {
return 'G' + Blockly.Lua.lists.gensym_counter_++;
};
Blockly.Lua['lists_getIndex'] = function(block) {
// Get element at index.
// Note: Until January 2013 this block did not have MODE or WHERE inputs.
var mode = block.getTitleValue('MODE') || 'GET';
var where = block.getTitleValue('WHERE') || 'FROM_START';
var at = Blockly.Lua.valueToCode(block, 'AT',
Blockly.Lua.ORDER_ADDITIVE) || '1';
var list = Blockly.Lua.valueToCode(block, 'VALUE',
Blockly.Lua.ORDER_HIGH) || '({})';
var getIndex_ = Blockly.Lua.lists.getIndex_;
var gensym_ = Blockly.Lua.lists.gensym_;
// If `list` would be evaluated more than once (which is the case for LAST,
// FROM_END, and RANDOM) and is non-trivial, make sure to access it only once.
if ((where == 'LAST' || where == 'FROM_END' || where == 'RANDOM') &&
!list.match(/^\w+$/)) {
// `list` is an expression, so we may not evaluate it more than once.
if (mode == 'REMOVE') {
// We can use multiple statements.
var listVar = Blockly.Lua.variableDB_.getDistinctName(
'tmp_list', Blockly.Variables.NAME_TYPE);
var code = listVar + ' = ' + list + '\n' +
'table.remove(' + listVar + ', ' + getIndex_(listVar, where, at) +
')\n';
return code;
} else {
// We need to create a procedure to avoid reevaluating values.
if (mode == 'GET') {
// Note that getIndex_() ignores `at` when `where` == 'LAST' or
// 'RANDOM', so we only need one procedure for each of those 'where'
// values. The value for 'FROM_END' depends on `at`, so we will
// generate a unique procedure (name) each time.
var functionName = Blockly.Lua.provideFunction_(
'list_get_' + where.toLowerCase() +
(where == 'FROM_END' ? '_' + gensym_() : ''),
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)',
' return t[' + getIndex_('t', where, at) + ']',
'end']);
} else { // mode == 'GET_REMOVE'
// We need to create a procedure.
var functionName = Blockly.Lua.provideFunction_(
'list_remove_' + where.toLowerCase() +
(where == 'FROM_END' ? '_' + gensym_() : ''),
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)',
' return table.remove(t, ' + getIndex_('t', where, at) + ')',
'end']);
}
var code = functionName + '(' + list + ')';
return [code, Blockly.Lua.ORDER_HIGH];
}
} else {
// Either `list` is a simple variable, or we only need to refer to `list`
// once.
if (mode == 'GET') {
var code = list + '[' + getIndex_(list, where, at) + ']';
return [code, Blockly.Lua.ORDER_HIGH];
} else {
var code = 'table.remove(' + list + ', ' + getIndex_(list, where, at) +
')';
if (mode == 'GET_REMOVE') {
return [code, Blockly.Lua.ORDER_HIGH];
} else { // `mode` == 'REMOVE'
return code + '\n';
}
}
}
};
Blockly.Lua['lists_setIndex'] = function(block) {
// Set element at index.
// Note: Until February 2013 this block did not have MODE or WHERE inputs.
var list = Blockly.Lua.valueToCode(block, 'LIST',
Blockly.Lua.ORDER_HIGH) || '({})';
var mode = block.getFieldValue('MODE') || 'SET';
var where = block.getFieldValue('WHERE') || 'FROM_START';
var at = Blockly.Lua.valueToCode(block, 'AT',
Blockly.Lua.ORDER_ADDITIVE) || '1';
var value = Blockly.Lua.valueToCode(block, 'TO',
Blockly.Lua.ORDER_NONE) || 'None';
var getIndex_ = Blockly.Lua.lists.getIndex_;
// If `list` would be evaluated more than once (which is the case for LAST,
// FROM_END, and RANDOM) and is non-trivial, make sure to access it only once.
if ((where == 'LAST' || where == 'FROM_END' || where == 'RANDOM') &&
!list.match(/^\w+$/)) {
// `list` is an expression, so we may not evaluate it more than once.
if (where == 'RANDOM' || where == 'LAST') {
// In these cases, `at` is implicit. getIndex_() ignores its value.
if (mode == 'SET') {
var functionName = Blockly.Lua.provideFunction_(
'list_set_' + where.toLowerCase(),
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t, val)',
' t[' + getIndex_('t', where, at) + '] = val',
'end']);
} else { // `mode` == 'INSERT'
var functionName = Blockly.Lua.provideFunction_(
'list_insert_' + where.toLowerCase(),
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t, val)',
' table.insert(t, ' +
// LAST is a special case, because we want to insert
// *after* not *before*, the existing last element.
getIndex_('t', where, at) + (where == 'LAST' ? ' + 1' : '') +
', val)',
'end']);
}
var code = functionName + '(' + list + ', ' + value + ')\n';
return code;
} else { // `where` = 'FROM_END'
if (mode == 'SET') {
var functionName = Blockly.Lua.provideFunction_(
'list_set_from_end',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ +
'(t, index, val)',
' t[#t + 1 - index] = val',
'end']);
} else { // `mode` == 'INSERT'
var functionName = Blockly.Lua.provideFunction_(
'list_insert_from_end',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ +
'(t, index, val)',
' table.insert(t, #t + 1 - index, val)',
'end']);
}
var code = functionName + '(' + list + ', ' + at + ', ' + value + ')\n';
return code;
}
} else {
// It's okay to have multiple references to the list.
if (mode == 'SET') {
var code = list + '[' + getIndex_(list, where, at) + '] = ' + value;
} else { // `mode` == 'INSERT'
// LAST is a special case, because we want to insert
// *after* not *before*, the existing last element.
var code = 'table.insert(' + list + ', ' +
(getIndex_(list, where, at) + (where == 'LAST' ? ' + 1' : '')) +
', ' + value + ')';
}
return code + '\n';
}
};
Blockly.Lua['lists_getSublist'] = function(block) {
// Get sublist.
var list = Blockly.Lua.valueToCode(block, 'LIST',
Blockly.Lua.ORDER_HIGH) || '({})';
var where1 = block.getFieldValue('WHERE1');
var where2 = block.getFieldValue('WHERE2');
var at1 = Blockly.Lua.valueToCode(block, 'AT1',
Blockly.Lua.ORDER_ADDITIVE) || '1';
var at2 = Blockly.Lua.valueToCode(block, 'AT2',
Blockly.Lua.ORDER_ADDITIVE) || '1';
var getIndex_ = Blockly.Lua.lists.getIndex_;
var functionName = Blockly.Lua.provideFunction_(
'list_sublist_' + Blockly.Lua.lists.gensym_(),
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(source)',
' local t = {}',
' local start = ' + getIndex_('source', where1, at1),
' local finish = ' + getIndex_('source', where2, at2),
' for i = start, finish do',
' table.insert(t, source[i])',
' end',
' return t',
'end']);
var code = functionName + '(' + list + ')';
return [code, Blockly.Lua.ORDER_HIGH];
};
Blockly.Lua['lists_split'] = function(block) {
// Block for splitting text into a list, or joining a list into text.
var value_input = Blockly.Lua.valueToCode(block, 'INPUT',
Blockly.Lua.ORDER_NONE);
var value_delim = Blockly.Lua.valueToCode(block, 'DELIM',
Blockly.Lua.ORDER_NONE) || '\'\'';
var mode = block.getFieldValue('MODE');
var functionName;
if (mode == 'SPLIT') {
if (!value_input) {
value_input = '\'\'';
}
functionName = Blockly.Lua.provideFunction_(
'list_string_split',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ +
'(input, delim)',
' local t = {}',
' local pos = 1',
' while true do',
' next_delim = string.find(input, delim, pos)',
' if next_delim == nil then',
' table.insert(t, string.sub(input, pos))',
' break',
' else',
' table.insert(t, string.sub(input, pos, next_delim-1))',
' pos = next_delim + #delim',
' end',
' end',
' return t',
'end']);
} else if (mode == 'JOIN') {
if (!value_input) {
value_input = '({})';
}
functionName = 'table.concat';
} else {
throw 'Unknown mode: ' + mode;
}
var code = functionName + '(' + value_input + ', ' + value_delim + ')';
return [code, Blockly.Lua.ORDER_HIGH];
};

125
generators/lua/logic.js Normal file
View file

@ -0,0 +1,125 @@
/**
* @license
* Visual Blocks Language
*
* Copyright 2016 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Generating Lua for logic blocks.
* @author rodrigoq@google.com (Rodrigo Queiro)
*/
'use strict';
goog.provide('Blockly.Lua.logic');
goog.require('Blockly.Lua');
Blockly.Lua['controls_if'] = function(block) {
// If/elseif/else condition.
var n = 0;
var argument = Blockly.Lua.valueToCode(block, 'IF' + n,
Blockly.Lua.ORDER_NONE) || 'false';
var branch = Blockly.Lua.statementToCode(block, 'DO' + n);
var code = 'if ' + argument + ' then\n' + branch;
for (n = 1; n <= block.elseifCount_; n++) {
argument = Blockly.Lua.valueToCode(block, 'IF' + n,
Blockly.Lua.ORDER_NONE) || 'false';
branch = Blockly.Lua.statementToCode(block, 'DO' + n);
code += ' elseif ' + argument + ' then\n' + branch;
}
if (block.elseCount_) {
branch = Blockly.Lua.statementToCode(block, 'ELSE');
code += ' else\n' + branch;
}
return code + 'end\n';
};
Blockly.Lua['logic_compare'] = function(block) {
// Comparison operator.
var OPERATORS = {
'EQ': '==',
'NEQ': '~=',
'LT': '<',
'LTE': '<=',
'GT': '>',
'GTE': '>='
};
var operator = OPERATORS[block.getFieldValue('OP')];
var argument0 = Blockly.Lua.valueToCode(block, 'A',
Blockly.Lua.ORDER_RELATIONAL) || '0';
var argument1 = Blockly.Lua.valueToCode(block, 'B',
Blockly.Lua.ORDER_RELATIONAL) || '0';
var code = argument0 + ' ' + operator + ' ' + argument1;
return [code, Blockly.Lua.ORDER_RELATIONAL];
};
Blockly.Lua['logic_operation'] = function(block) {
// Operations 'and', 'or'.
var operator = (block.getFieldValue('OP') == 'AND') ? 'and' : 'or';
var order = (operator == 'and') ? Blockly.Lua.ORDER_AND :
Blockly.Lua.ORDER_OR;
var argument0 = Blockly.Lua.valueToCode(block, 'A', order);
var argument1 = Blockly.Lua.valueToCode(block, 'B', order);
if (!argument0 && !argument1) {
// If there are no arguments, then the return value is false.
argument0 = 'false';
argument1 = 'false';
} else {
// Single missing arguments have no effect on the return value.
var defaultArgument = (operator == 'and') ? 'true' : 'false';
if (!argument0) {
argument0 = defaultArgument;
}
if (!argument1) {
argument1 = defaultArgument;
}
}
var code = argument0 + ' ' + operator + ' ' + argument1;
return [code, order];
};
Blockly.Lua['logic_negate'] = function(block) {
// Negation.
var argument0 = Blockly.Lua.valueToCode(block, 'BOOL',
Blockly.Lua.ORDER_UNARY) || 'true';
var code = 'not ' + argument0;
return [code, Blockly.Lua.ORDER_UNARY];
};
Blockly.Lua['logic_boolean'] = function(block) {
// Boolean values true and false.
var code = (block.getFieldValue('BOOL') == 'TRUE') ? 'true' : 'false';
return [code, Blockly.Lua.ORDER_ATOMIC];
};
Blockly.Lua['logic_null'] = function(block) {
// Null data type.
return ['nil', Blockly.Lua.ORDER_ATOMIC];
};
Blockly.Lua['logic_ternary'] = function(block) {
// Ternary operator.
var value_if = Blockly.Lua.valueToCode(block, 'IF',
Blockly.Lua.ORDER_AND) || 'false';
var value_then = Blockly.Lua.valueToCode(block, 'THEN',
Blockly.Lua.ORDER_AND) || 'nil';
var value_else = Blockly.Lua.valueToCode(block, 'ELSE',
Blockly.Lua.ORDER_OR) || 'nil';
var code = value_if + ' and ' + value_then + ' or ' + value_else;
return [code, Blockly.Lua.ORDER_OR];
};

166
generators/lua/loops.js Normal file
View file

@ -0,0 +1,166 @@
/**
* @license
* Visual Blocks Language
*
* Copyright 2016 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Generating Lua for loop blocks.
* @author rodrigoq@google.com (Rodrigo Queiro)
*/
'use strict';
goog.provide('Blockly.Lua.loops');
goog.require('Blockly.Lua');
/**
* This is the text used to implement a <pre>continue</pre>.
* It is also used to recognise <pre>continue</pre>s in generated code so that
* the appropriate label can be put at the end of the loop body.
* @const {string}
*/
Blockly.Lua.CONTINUE_STATEMENT = 'goto continue\n';
/**
* If the loop body contains a "goto continue" statement, add a continue label
* to the loop body. Slightly inefficient, as continue labels will be generated
* in all outer loops, but this is safer than duplicating the logic of
* blockToCode.
*
* @param {string} branch Generated code of the loop body
* @return {string} Generated label or '' if unnecessary
*/
Blockly.Lua.addContinueLabel = function(branch) {
if (branch.indexOf(Blockly.Lua.CONTINUE_STATEMENT) > -1) {
return branch + Blockly.Lua.INDENT + '::continue::\n';
} else {
return branch;
}
};
Blockly.Lua['controls_repeat'] = function(block) {
// Repeat n times (internal number).
var repeats = parseInt(block.getFieldValue('TIMES'), 10);
var branch = Blockly.Lua.statementToCode(block, 'DO') || '';
branch = Blockly.Lua.addContinueLabel(branch);
var loopVar = Blockly.Lua.variableDB_.getDistinctName(
'count', Blockly.Variables.NAME_TYPE);
var code = 'for ' + loopVar + ' = 1, ' + repeats + ' do\n' + branch + 'end\n';
return code;
};
Blockly.Lua['controls_repeat_ext'] = function(block) {
// Repeat n times (external number).
var repeats = Blockly.Lua.valueToCode(block, 'TIMES',
Blockly.Lua.ORDER_NONE) || '0';
if (Blockly.isNumber(repeats)) {
repeats = parseInt(repeats, 10);
} else {
repeats = 'math.floor(' + repeats + ')';
}
var branch = Blockly.Lua.statementToCode(block, 'DO') || '\n';
branch = Blockly.Lua.addContinueLabel(branch);
var loopVar = Blockly.Lua.variableDB_.getDistinctName(
'count', Blockly.Variables.NAME_TYPE);
var code = 'for ' + loopVar + ' = 1, ' + repeats + ' do\n' +
branch + 'end\n';
return code;
};
Blockly.Lua['controls_whileUntil'] = function(block) {
// Do while/until loop.
var until = block.getFieldValue('MODE') == 'UNTIL';
var argument0 = Blockly.Lua.valueToCode(block, 'BOOL',
until ? Blockly.Lua.ORDER_UNARY :
Blockly.Lua.ORDER_NONE) || 'false';
var branch = Blockly.Lua.statementToCode(block, 'DO') || '\n';
branch = Blockly.Lua.addLoopTrap(branch, block.id);
branch = Blockly.Lua.addContinueLabel(branch);
if (until) {
argument0 = 'not ' + argument0;
}
return 'while ' + argument0 + ' do\n' + branch + 'end\n';
};
Blockly.Lua['controls_for'] = function(block) {
// For loop.
var variable0 = Blockly.Lua.variableDB_.getName(
block.getFieldValue('VAR'), Blockly.Variables.NAME_TYPE);
var startVar = Blockly.Lua.valueToCode(block, 'FROM',
Blockly.Lua.ORDER_NONE) || '0';
var endVar = Blockly.Lua.valueToCode(block, 'TO',
Blockly.Lua.ORDER_NONE) || '0';
var increment = Blockly.Lua.valueToCode(block, 'BY',
Blockly.Lua.ORDER_NONE) || '1';
var branch = Blockly.Lua.statementToCode(block, 'DO') || '\n';
branch = Blockly.Lua.addLoopTrap(branch, block.id);
branch = Blockly.Lua.addContinueLabel(branch);
var code = '';
var incValue;
if (Blockly.isNumber(startVar) && Blockly.isNumber(endVar) &&
Blockly.isNumber(increment)) {
// All arguments are simple numbers.
var up = parseFloat(startVar) <= parseFloat(endVar);
var step = Math.abs(parseFloat(increment));
incValue = (up ? '' : '-') + step;
} else {
code = '';
// Determine loop direction at start, in case one of the bounds
// changes during loop execution.
incValue = Blockly.Lua.variableDB_.getDistinctName(
variable0 + '_inc', Blockly.Variables.NAME_TYPE);
code += incValue + ' = ';
if (Blockly.isNumber(increment)) {
code += Math.abs(increment) + '\n';
} else {
code += 'math.abs(' + increment + ')\n';
}
code += 'if (' + startVar + ') > (' + endVar + ') then\n';
code += Blockly.Lua.INDENT + incValue + ' = -' + incValue + '\n';
code += 'end\n';
}
code += 'for ' + variable0 + ' = ' + startVar + ', ' + endVar +
', ' + incValue;
code += ' do\n' + branch + 'end\n';
return code;
};
Blockly.Lua['controls_forEach'] = function(block) {
// For each loop.
var variable0 = Blockly.Lua.variableDB_.getName(
block.getFieldValue('VAR'), Blockly.Variables.NAME_TYPE);
var argument0 = Blockly.Lua.valueToCode(block, 'LIST',
Blockly.Lua.ORDER_NONE) || '{}';
var branch = Blockly.Lua.statementToCode(block, 'DO') || '\n';
branch = Blockly.Lua.addContinueLabel(branch);
var code = 'for _, ' + variable0 + ' in ipairs(' + argument0 + ') do \n' +
branch + 'end\n';
return code;
};
Blockly.Lua['controls_flow_statements'] = function(block) {
// Flow statements: continue, break.
switch (block.getFieldValue('FLOW')) {
case 'BREAK':
return 'break\n';
case 'CONTINUE':
return Blockly.Lua.CONTINUE_STATEMENT;
}
throw 'Unknown flow statement.';
};

425
generators/lua/math.js Normal file
View file

@ -0,0 +1,425 @@
/**
* @license
* Visual Blocks Language
*
* Copyright 2016 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Generating Lua for math blocks.
* @author rodrigoq@google.com (Rodrigo Queiro)
*/
'use strict';
goog.provide('Blockly.Lua.math');
goog.require('Blockly.Lua');
Blockly.Lua['math_number'] = function(block) {
// Numeric value.
var code = parseFloat(block.getFieldValue('NUM'));
var order = code < 0 ? Blockly.Lua.ORDER_UNARY :
Blockly.Lua.ORDER_ATOMIC;
return [code, order];
};
Blockly.Lua['math_arithmetic'] = function(block) {
// Basic arithmetic operators, and power.
var OPERATORS = {
ADD: [' + ', Blockly.Lua.ORDER_ADDITIVE],
MINUS: [' - ', Blockly.Lua.ORDER_ADDITIVE],
MULTIPLY: [' * ', Blockly.Lua.ORDER_MULTIPLICATIVE],
DIVIDE: [' / ', Blockly.Lua.ORDER_MULTIPLICATIVE],
POWER: [' ^ ', Blockly.Lua.ORDER_EXPONENTIATION]
};
var tuple = OPERATORS[block.getFieldValue('OP')];
var operator = tuple[0];
var order = tuple[1];
var argument0 = Blockly.Lua.valueToCode(block, 'A', order) || '0';
var argument1 = Blockly.Lua.valueToCode(block, 'B', order) || '0';
var code = argument0 + operator + argument1;
return [code, order];
};
Blockly.Lua['math_single'] = function(block) {
// Math operators with single operand.
var operator = block.getFieldValue('OP');
var code;
var arg;
if (operator == 'NEG') {
// Negation is a special case given its different operator precedence.
arg = Blockly.Lua.valueToCode(block, 'NUM',
Blockly.Lua.ORDER_UNARY) || '0';
return ['-' + arg, Blockly.Lua.ORDER_UNARY];
}
if (operator == 'SIN' || operator == 'COS' || operator == 'TAN') {
arg = Blockly.Lua.valueToCode(block, 'NUM',
Blockly.Lua.ORDER_MULTIPLICATIVE) || '0';
} else {
arg = Blockly.Lua.valueToCode(block, 'NUM',
Blockly.Lua.ORDER_NONE) || '0';
}
switch (operator) {
case 'ABS':
code = 'math.abs(' + arg + ')';
break;
case 'ROOT':
code = 'math.sqrt(' + arg + ')';
break;
case 'LN':
code = 'math.log(' + arg + ')';
break;
case 'LOG10':
code = 'math.log10(' + arg + ')';
break;
case 'EXP':
code = 'math.exp(' + arg + ')';
break;
case 'POW10':
code = 'math.pow(10,' + arg + ')';
break;
case 'ROUND':
// This rounds up. Blockly does not specify rounding direction.
code = 'math.floor(' + arg + ' + .5)';
break;
case 'ROUNDUP':
code = 'math.ceil(' + arg + ')';
break;
case 'ROUNDDOWN':
code = 'math.floor(' + arg + ')';
break;
case 'SIN':
code = 'math.sin(math.rad(' + arg + '))';
break;
case 'COS':
code = 'math.cos(math.rad(' + arg + '))';
break;
case 'TAN':
code = 'math.tan(math.rad(' + arg + '))';
break;
case 'ASIN':
code = 'math.deg(math.asin(' + arg + '))';
break;
case 'ACOS':
code = 'math.deg(math.acos(' + arg + '))';
break;
case 'ATAN':
code = 'math.deg(math.atan(' + arg + '))';
break;
default:
throw 'Unknown math operator: ' + operator;
}
return [code, Blockly.Lua.ORDER_HIGH];
};
Blockly.Lua['math_constant'] = function(block) {
// Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY.
var CONSTANTS = {
PI: ['math.pi', Blockly.Lua.ORDER_HIGH],
E: ['math.exp(1)', Blockly.Lua.ORDER_HIGH],
GOLDEN_RATIO: ['(1 + math.sqrt(5)) / 2', Blockly.Lua.ORDER_MULTIPLICATIVE],
SQRT2: ['math.sqrt(2)', Blockly.Lua.ORDER_HIGH],
SQRT1_2: ['math.sqrt(1 / 2)', Blockly.Lua.ORDER_HIGH],
INFINITY: ['math.huge', Blockly.Lua.ORDER_HIGH]
};
return CONSTANTS[block.getFieldValue('CONSTANT')];
};
Blockly.Lua['math_number_property'] = function(block) {
// Check if a number is even, odd, prime, whole, positive, or negative
// or if it is divisible by certain number. Returns true or false.
var number_to_check = Blockly.Lua.valueToCode(block, 'NUMBER_TO_CHECK',
Blockly.Lua.ORDER_MULTIPLICATIVE) || '0';
var dropdown_property = block.getFieldValue('PROPERTY');
var code;
if (dropdown_property == 'PRIME') {
// Prime is a special case as it is not a one-liner test.
var functionName = Blockly.Lua.provideFunction_(
'math_isPrime',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(n)',
' -- https://en.wikipedia.org/wiki/Primality_test#Naive_methods',
' if n == 2 or n == 3 then',
' return true',
' end',
' -- False if n is NaN, negative, is 1, or not whole.',
' -- And false if n is divisible by 2 or 3.',
' if not(n > 1) or n % 1 ~= 0 or n % 2 == 0 or n % 3 == 0 then',
' return false',
' end',
' -- Check all the numbers of form 6k +/- 1, up to sqrt(n).',
' for x = 6, math.sqrt(n) + 1.5, 6 do',
' if n % (x - 1) == 0 or n % (x + 1) == 0 then',
' return false',
' end',
' end',
' return true',
'end']);
code = functionName + '(' + number_to_check + ')';
return [code, Blockly.Lua.ORDER_HIGH];
}
switch (dropdown_property) {
case 'EVEN':
code = number_to_check + ' % 2 == 0';
break;
case 'ODD':
code = number_to_check + ' % 2 == 1';
break;
case 'WHOLE':
code = number_to_check + ' % 1 == 0';
break;
case 'POSITIVE':
code = number_to_check + ' > 0';
break;
case 'NEGATIVE':
code = number_to_check + ' < 0';
break;
case 'DIVISIBLE_BY':
var divisor = Blockly.Lua.valueToCode(block, 'DIVISOR',
Blockly.Lua.ORDER_MULTIPLICATIVE);
// If 'divisor' is some code that evals to 0, Lua will produce a nan.
// Let's produce nil if we can determine this at compile-time.
if (!divisor || divisor == '0') {
return ['nil', Blockly.Lua.ORDER_ATOMIC];
}
// The normal trick to implement ?: with and/or doesn't work here:
// divisor == 0 and nil or number_to_check % divisor == 0
// because nil is false, so allow a runtime failure. :-(
code = number_to_check + ' % ' + divisor + ' == 0';
break;
}
return [code, Blockly.Lua.ORDER_RELATIONAL];
};
Blockly.Lua['math_change'] = function(block) {
// Add to a variable in place.
var argument0 = Blockly.Lua.valueToCode(block, 'DELTA',
Blockly.Lua.ORDER_ADDITIVE) || '0';
var varName = Blockly.Lua.variableDB_.getName(
block.getFieldValue('VAR'), Blockly.Variables.NAME_TYPE);
return varName + ' = ' + varName + ' + ' + argument0 + '\n';
};
// Rounding functions have a single operand.
Blockly.Lua['math_round'] = Blockly.Lua['math_single'];
// Trigonometry functions have a single operand.
Blockly.Lua['math_trig'] = Blockly.Lua['math_single'];
Blockly.Lua['math_on_list'] = function(block) {
// Math functions for lists.
var func = block.getFieldValue('OP');
var list = Blockly.Lua.valueToCode(block, 'LIST',
Blockly.Lua.ORDER_NONE) || '{}';
var functionName;
// Functions needed in more than one case.
function provideSum() {
return Blockly.Lua.provideFunction_(
'math_sum',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)',
' local result = 0',
' for _, v in ipairs(t) do',
' result = result + v',
' end',
' return result',
'end']);
}
switch (func) {
case 'SUM':
functionName = provideSum();
break;
case 'MIN':
// Returns 0 for the empty list.
functionName = Blockly.Lua.provideFunction_(
'math_min',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)',
' if #t == 0 then',
' return 0',
' end',
' local result = math.huge',
' for _, v in ipairs(t) do',
' if v < result then',
' result = v',
' end',
' end',
' return result',
'end']);
break;
case 'AVERAGE':
// Returns 0 for the empty list.
functionName = Blockly.Lua.provideFunction_(
'math_average',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)',
' if #t == 0 then',
' return 0',
' end',
' return ' + provideSum() + '(t) / #t',
'end']);
break;
case 'MAX':
// Returns 0 for the empty list.
functionName = Blockly.Lua.provideFunction_(
'math_max',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)',
' if #t == 0 then',
' return 0',
' end',
' local result = -math.huge',
' for _, v in ipairs(t) do',
' if v > result then',
' result = v',
' end',
' end',
' return result',
'end']);
break;
case 'MEDIAN':
functionName = Blockly.Lua.provideFunction_(
'math_median',
// This operation excludes non-numbers.
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)',
' -- Source: http://lua-users.org/wiki/SimpleStats',
' if #t == 0 then',
' return 0',
' end',
' local temp={}',
' for _, v in ipairs(t) do',
' if type(v) == "number" then',
' table.insert(temp, v)',
' end',
' end',
' table.sort(temp)',
' if #temp % 2 == 0 then',
' return (temp[#temp/2] + temp[(#temp/2)+1]) / 2',
' else',
' return temp[math.ceil(#temp/2)]',
' end',
'end']);
break;
case 'MODE':
functionName = Blockly.Lua.provideFunction_(
'math_modes',
// As a list of numbers can contain more than one mode,
// the returned result is provided as an array.
// The Lua version includes non-numbers.
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)',
' -- Source: http://lua-users.org/wiki/SimpleStats',
' local counts={}',
' for _, v in ipairs(t) do',
' if counts[v] == nil then',
' counts[v] = 1',
' else',
' counts[v] = counts[v] + 1',
' end',
' end',
' local biggestCount = 0',
' for _, v in pairs(counts) do',
' if v > biggestCount then',
' biggestCount = v',
' end',
' end',
' local temp={}',
' for k, v in pairs(counts) do',
' if v == biggestCount then',
' table.insert(temp, k)',
' end',
' end',
' return temp',
'end']);
break;
case 'STD_DEV':
functionName = Blockly.Lua.provideFunction_(
'math_standard_deviation',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)',
' local m',
' local vm',
' local total = 0',
' local count = 0',
' local result',
' m = #t == 0 and 0 or ' + provideSum() + '(t) / #t',
' for _, v in ipairs(t) do',
" if type(v) == 'number' then",
' vm = v - m',
' total = total + (vm * vm)',
' count = count + 1',
' end',
' end',
' result = math.sqrt(total / (count-1))',
' return result',
'end']);
break;
case 'RANDOM':
functionName = Blockly.Lua.provideFunction_(
'math_random_list',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)',
' if #t == 0 then',
' return nil',
' end',
' return t[math.random(#t)]',
'end']);
break;
default:
throw 'Unknown operator: ' + func;
}
return [functionName + '(' + list + ')', Blockly.Lua.ORDER_HIGH];
};
Blockly.Lua['math_modulo'] = function(block) {
// Remainder computation.
var argument0 = Blockly.Lua.valueToCode(block, 'DIVIDEND',
Blockly.Lua.ORDER_MULTIPLICATIVE) || '0';
var argument1 = Blockly.Lua.valueToCode(block, 'DIVISOR',
Blockly.Lua.ORDER_MULTIPLICATIVE) || '0';
var code = argument0 + ' % ' + argument1;
return [code, Blockly.Lua.ORDER_MULTIPLICATIVE];
};
Blockly.Lua['math_constrain'] = function(block) {
// Constrain a number between two limits.
var argument0 = Blockly.Lua.valueToCode(block, 'VALUE',
Blockly.Lua.ORDER_NONE) || '0';
var argument1 = Blockly.Lua.valueToCode(block, 'LOW',
Blockly.Lua.ORDER_NONE) || '-math.huge';
var argument2 = Blockly.Lua.valueToCode(block, 'HIGH',
Blockly.Lua.ORDER_NONE) || 'math.huge';
var code = 'math.min(math.max(' + argument0 + ', ' + argument1 + '), ' +
argument2 + ')';
return [code, Blockly.Lua.ORDER_HIGH];
};
Blockly.Lua['math_random_int'] = function(block) {
// Random integer between [X] and [Y].
var argument0 = Blockly.Lua.valueToCode(block, 'FROM',
Blockly.Lua.ORDER_NONE) || '0';
var argument1 = Blockly.Lua.valueToCode(block, 'TO',
Blockly.Lua.ORDER_NONE) || '0';
var code = 'math.random(' + argument0 + ', ' + argument1 + ')';
return [code, Blockly.Lua.ORDER_HIGH];
};
Blockly.Lua['math_random_float'] = function(block) {
// Random fraction between 0 and 1.
return ['math.random()', Blockly.Lua.ORDER_HIGH];
};

View file

@ -0,0 +1,110 @@
/**
* @license
* Visual Blocks Language
*
* Copyright 2016 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Generating Lua for procedure blocks.
* @author rodrigoq@google.com (Rodrigo Queiro)
*/
'use strict';
goog.provide('Blockly.Lua.procedures');
goog.require('Blockly.Lua');
Blockly.Lua['procedures_defreturn'] = function(block) {
// Define a procedure with a return value.
var funcName = Blockly.Lua.variableDB_.getName(
block.getFieldValue('NAME'), Blockly.Procedures.NAME_TYPE);
var branch = Blockly.Lua.statementToCode(block, 'STACK');
if (Blockly.Lua.STATEMENT_PREFIX) {
branch = Blockly.Lua.prefixLines(
Blockly.Lua.STATEMENT_PREFIX.replace(/%1/g,
'\'' + block.id + '\''), Blockly.Lua.INDENT) + branch;
}
if (Blockly.Lua.INFINITE_LOOP_TRAP) {
branch = Blockly.Lua.INFINITE_LOOP_TRAP.replace(/%1/g,
'\'' + block.id + '\'') + branch;
}
var returnValue = Blockly.Lua.valueToCode(block, 'RETURN',
Blockly.Lua.ORDER_NONE) || '';
if (returnValue) {
returnValue = ' return ' + returnValue + '\n';
} else if (!branch) {
branch = '';
}
var args = [];
for (var x = 0; x < block.arguments_.length; x++) {
args[x] = Blockly.Lua.variableDB_.getName(block.arguments_[x],
Blockly.Variables.NAME_TYPE);
}
var code = 'function ' + funcName + '(' + args.join(', ') + ')\n' +
branch + returnValue + 'end\n';
code = Blockly.Lua.scrub_(block, code);
Blockly.Lua.definitions_[funcName] = code;
return null;
};
// Defining a procedure without a return value uses the same generator as
// a procedure with a return value.
Blockly.Lua['procedures_defnoreturn'] =
Blockly.Lua['procedures_defreturn'];
Blockly.Lua['procedures_callreturn'] = function(block) {
// Call a procedure with a return value.
var funcName = Blockly.Lua.variableDB_.getName(
block.getFieldValue('NAME'), Blockly.Procedures.NAME_TYPE);
var args = [];
for (var x = 0; x < block.arguments_.length; x++) {
args[x] = Blockly.Lua.valueToCode(block, 'ARG' + x,
Blockly.Lua.ORDER_NONE) || 'nil';
}
var code = funcName + '(' + args.join(', ') + ')';
return [code, Blockly.Lua.ORDER_HIGH];
};
Blockly.Lua['procedures_callnoreturn'] = function(block) {
// Call a procedure with no return value.
var funcName = Blockly.Lua.variableDB_.getName(
block.getFieldValue('NAME'), Blockly.Procedures.NAME_TYPE);
var args = [];
for (var x = 0; x < block.arguments_.length; x++) {
args[x] = Blockly.Lua.valueToCode(block, 'ARG' + x,
Blockly.Lua.ORDER_NONE) || 'nil';
}
var code = funcName + '(' + args.join(', ') + ')\n';
return code;
};
Blockly.Lua['procedures_ifreturn'] = function(block) {
// Conditionally return value from a procedure.
var condition = Blockly.Lua.valueToCode(block, 'CONDITION',
Blockly.Lua.ORDER_NONE) || 'false';
var code = 'if ' + condition + ' then\n';
if (block.hasReturnValue_) {
var value = Blockly.Lua.valueToCode(block, 'VALUE',
Blockly.Lua.ORDER_NONE) || 'nil';
code += ' return ' + value + '\n';
} else {
code += ' return\n';
}
code += 'end\n';
return code;
};

293
generators/lua/text.js Normal file
View file

@ -0,0 +1,293 @@
/**
* @license
* Visual Blocks Language
*
* Copyright 2016 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Generating Lua for text blocks.
* @author rodrigoq@google.com (Rodrigo Queiro)
*/
'use strict';
goog.provide('Blockly.Lua.texts');
goog.require('Blockly.Lua');
Blockly.Lua['text'] = function(block) {
// Text value.
var code = Blockly.Lua.quote_(block.getFieldValue('TEXT'));
return [code, Blockly.Lua.ORDER_ATOMIC];
};
Blockly.Lua['text_join'] = function(block) {
// Create a string made up of any number of elements of any type.
if (block.itemCount_ == 0) {
return ['\'\'', Blockly.Lua.ORDER_ATOMIC];
} else if (block.itemCount_ == 1) {
var argument0 = Blockly.Lua.valueToCode(block, 'ADD0',
Blockly.Lua.ORDER_NONE) || '\'\'';
var code = argument0;
return [code, Blockly.Lua.ORDER_HIGH];
} else if (block.itemCount_ == 2) {
var argument0 = Blockly.Lua.valueToCode(block, 'ADD0',
Blockly.Lua.ORDER_NONE) || '\'\'';
var argument1 = Blockly.Lua.valueToCode(block, 'ADD1',
Blockly.Lua.ORDER_NONE) || '\'\'';
var code = argument0 + ' .. ' + argument1;
return [code, Blockly.Lua.ORDER_UNARY];
} else {
var code = [];
for (var n = 0; n < block.itemCount_; n++) {
code[n] = Blockly.Lua.valueToCode(block, 'ADD' + n,
Blockly.Lua.ORDER_NONE) || '\'\'';
}
code = 'table.concat({' + code.join(', ') + '})';
return [code, Blockly.Lua.ORDER_HIGH];
}
};
Blockly.Lua['text_append'] = function(block) {
// Append to a variable in place.
var varName = Blockly.Lua.variableDB_.getName(
block.getFieldValue('VAR'), Blockly.Variables.NAME_TYPE);
var argument0 = Blockly.Lua.valueToCode(block, 'TEXT',
Blockly.Lua.ORDER_NONE) || '\'\'';
return varName + ' = ' + varName + ' .. ' + argument0 + '\n';
};
Blockly.Lua['text_length'] = function(block) {
// String or array length.
var argument0 = Blockly.Lua.valueToCode(block, 'VALUE',
Blockly.Lua.ORDER_HIGH) || '\'\'';
return ['#' + argument0, Blockly.Lua.ORDER_HIGH];
};
Blockly.Lua['text_isEmpty'] = function(block) {
// Is the string null or array empty?
var argument0 = Blockly.Lua.valueToCode(block, 'VALUE',
Blockly.Lua.ORDER_HIGH) || '\'\'';
return ['#' + argument0 + ' == 0', Blockly.Lua.ORDER_RELATIONAL];
};
Blockly.Lua['text_indexOf'] = function(block) {
// Search the text for a substring.
var operator = block.getFieldValue('END') == 'FIRST' ?
'indexOf' : 'lastIndexOf';
var substr = Blockly.Lua.valueToCode(block, 'FIND',
Blockly.Lua.ORDER_NONE) || '\'\'';
var str = Blockly.Lua.valueToCode(block, 'VALUE',
Blockly.Lua.ORDER_NONE) || '\'\'';
if (block.getTitleValue('END') == 'FIRST') {
var functionName = Blockly.Lua.provideFunction_(
'firstIndexOf',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ +
'(str, substr) ',
' local i = string.find(str, substr, 1, true)',
' if i == nil then',
' return 0',
' else',
' return i',
' end',
'end']);
} else {
var functionName = Blockly.Lua.provideFunction_(
'lastIndexOf',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ +
'(str, substr)',
' local i = string.find(string.reverse(str), ' +
'string.reverse(substr), 1, true)',
' if i then',
' return #str + 2 - i - #substr',
' end',
' return 0',
'end']);
}
var code = functionName + '(' + str + ', ' + substr + ')';
return [code, Blockly.Lua.ORDER_HIGH];
};
Blockly.Lua['text_charAt'] = function(block) {
// Get letter at index.
// Note: Until January 2013 this block did not have the WHERE input.
var where = block.getFieldValue('WHERE') || 'FROM_START';
var at = Blockly.Lua.valueToCode(block, 'AT',
Blockly.Lua.ORDER_UNARY) || '1';
var text = Blockly.Lua.valueToCode(block, 'VALUE',
Blockly.Lua.ORDER_NONE) || '\'\'';
var code;
if (where == 'RANDOM') {
var functionName = Blockly.Lua.provideFunction_(
'text_random_letter',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(str)',
' local index = math.random(string.len(str))',
' return string.sub(str, index, index)',
'end']);
code = functionName + '(' + text + ')';
} else {
if (where == 'FIRST') {
var start = '1';
} else if (where == 'LAST') {
var start = '-1';
} else {
if (where == 'FROM_START') {
var start = at;
} else if (where == 'FROM_END') {
var start = '-' + at;
} else {
throw 'Unhandled option (text_charAt).';
}
}
if (start.match(/^-?\w*$/)) {
code = 'string.sub(' + text + ', ' + start + ', ' + start + ')';
} else {
// use function to avoid reevaluation
var functionName = Blockly.Lua.provideFunction_(
'text_char_at',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ +
'(str, index)',
' return string.sub(str, index, index)',
'end']);
code = functionName + '(' + text + ', ' + start + ')';
}
}
return [code, Blockly.Lua.ORDER_HIGH];
};
Blockly.Lua['text_getSubstring'] = function(block) {
// Get substring.
var text = Blockly.Lua.valueToCode(block, 'STRING',
Blockly.Lua.ORDER_NONE) || '\'\'';
// Get start index.
var where1 = block.getFieldValue('WHERE1');
var at1 = Blockly.Lua.valueToCode(block, 'AT1',
Blockly.Lua.ORDER_UNARY) || '1';
if (where1 == 'FIRST') {
var start = 1;
} else if (where1 == 'FROM_START') {
var start = at1;
} else if (where1 == 'FROM_END') {
var start = '-' + at1;
} else {
throw 'Unhandled option (text_getSubstring)';
}
// Get end index.
var where2 = block.getFieldValue('WHERE2');
var at2 = Blockly.Lua.valueToCode(block, 'AT2',
Blockly.Lua.ORDER_UNARY) || '1';
if (where2 == 'LAST') {
var end = -1;
} else if (where2 == 'FROM_START') {
var end = at2;
} else if (where2 == 'FROM_END') {
var end = '-' + at2;
} else {
throw 'Unhandled option (text_getSubstring)';
}
var code = 'string.sub(' + text + ', ' + start + ', ' + end + ')';
return [code, Blockly.Lua.ORDER_HIGH];
};
Blockly.Lua['text_changeCase'] = function(block) {
// Change capitalization.
var operator = block.getFieldValue('CASE');
var argument0 = Blockly.Lua.valueToCode(block, 'TEXT',
Blockly.Lua.ORDER_NONE) || '\'\'';
if (operator == 'UPPERCASE') {
var functionName = 'string.upper';
} else if (operator == 'LOWERCASE') {
var functionName = 'string.lower';
} else if (operator == 'TITLECASE') {
var functionName = Blockly.Lua.provideFunction_(
'text_titlecase',
// There are shorter versions at
// http://lua-users.org/wiki/SciteTitleCase
// that do not preserve whitespace.
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(str)',
' local buf = {}',
' local inWord = false',
' for i = 1, #str do',
' local c = string.sub(str, i, i)',
' if inWord then',
' table.insert(buf, string.lower(c))',
' if string.find(c, "%s") then',
' inWord = false',
' end',
' else',
' table.insert(buf, string.upper(c))',
' inWord = true',
' end',
' end',
' return table.concat(buf)',
'end']);
}
var code = functionName + '(' + argument0 + ')';
return [code, Blockly.Lua.ORDER_HIGH];
};
Blockly.Lua['text_trim'] = function(block) {
// Trim spaces.
var OPERATORS = {
LEFT: '^%s*(,-)',
RIGHT: '(.-)%s*$',
BOTH: '^%s*(.-)%s*$'
};
var operator = OPERATORS[block.getFieldValue('MODE')];
var text = Blockly.Lua.valueToCode(block, 'TEXT',
Blockly.Lua.ORDER_NONE) || '\'\'';
var code = 'string.gsub(' + text + ', "' + operator + '", "%1")';
return [code, Blockly.Lua.ORDER_HIGH];
};
Blockly.Lua['text_print'] = function(block) {
// Print statement.
var argument0 = Blockly.Lua.valueToCode(block, 'TEXT',
Blockly.Lua.ORDER_NONE) || '\'\'';
return 'print(' + argument0 + ')\n';
};
Blockly.Lua['text_prompt_ext'] = function(block) {
// Prompt function.
if (block.getField('TEXT')) {
// Internal message.
var msg = Blockly.Lua.quote_(block.getFieldValue('TEXT'));
} else {
// External message.
var msg = Blockly.Lua.valueToCode(block, 'TEXT',
Blockly.Lua.ORDER_NONE) || '\'\'';
}
var functionName = Blockly.Lua.provideFunction_(
'text_prompt',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(msg)',
' io.write(msg)',
' io.flush()',
' return io.read()',
'end']);
var code = functionName + '(' + msg + ')';
var toNumber = block.getFieldValue('TYPE') == 'NUMBER';
if (toNumber) {
code = 'tonumber(' + code + ', 10)';
}
return [code, Blockly.Lua.ORDER_HIGH];
};
Blockly.Lua['text_prompt'] = Blockly.Lua['text_prompt_ext'];

View file

@ -0,0 +1,46 @@
/**
* @license
* Visual Blocks Language
*
* Copyright 2016 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Generating Lua for variable blocks.
* @author rodrigoq@google.com (Rodrigo Queiro)
*/
'use strict';
goog.provide('Blockly.Lua.variables');
goog.require('Blockly.Lua');
Blockly.Lua['variables_get'] = function(block) {
// Variable getter.
var code = Blockly.Lua.variableDB_.getName(block.getFieldValue('VAR'),
Blockly.Variables.NAME_TYPE);
return [code, Blockly.Lua.ORDER_ATOMIC];
};
Blockly.Lua['variables_set'] = function(block) {
// Variable setter.
var argument0 = Blockly.Lua.valueToCode(block, 'VALUE',
Blockly.Lua.ORDER_NONE) || '0';
var varName = Blockly.Lua.variableDB_.getName(
block.getFieldValue('VAR'), Blockly.Variables.NAME_TYPE);
return varName + ' = ' + argument0 + '\n';
};

74
lua_compressed.js Normal file

File diff suppressed because one or more lines are too long

View file

@ -62,7 +62,7 @@ Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_UNTIL = "kartok, kol pasieksi";
Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_WHILE = "kartok kol";
Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL = "Kartoja veiksmus, kol bus pasiekta nurodyta sąlyga.";
Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_WHILE = "Kartoja veiksmus, kol sąlyga tenkinama.";
Blockly.Msg.DELETE_ALL_BLOCKS = "Delete all %1 blocks?"; // untranslated
Blockly.Msg.DELETE_ALL_BLOCKS = "Ištrinti visus %1 blokus?";
Blockly.Msg.DELETE_BLOCK = "Ištrinti bloką";
Blockly.Msg.DELETE_X_BLOCKS = "Ištrinti %1 blokus";
Blockly.Msg.DISABLE_BLOCK = "Išjungti bloką";

View file

@ -96,6 +96,7 @@
"LOGIC_BOOLEAN_TOOLTIP": "Vrací pravda nebo nepravda.",
"LOGIC_NULL": "prázdný",
"LOGIC_NULL_TOOLTIP": "Vrátí prázdnou hodnotu",
"LOGIC_TERNARY_HELPURL": "https://cs.wikipedia.org/wiki/Ternární operátor (programování)",
"LOGIC_TERNARY_CONDITION": "test",
"LOGIC_TERNARY_IF_TRUE": "pokud pravda",
"LOGIC_TERNARY_IF_FALSE": "pokud nepravda",
@ -297,12 +298,14 @@
"VARIABLES_SET": "nastavit %1 na %2",
"VARIABLES_SET_TOOLTIP": "Nastaví tuto proměnnou, aby se rovnala vstupu.",
"VARIABLES_SET_CREATE_GET": "Vytvořit \"získat %1\"",
"PROCEDURES_DEFNORETURN_HELPURL": "https://cs.wikipedia.org/w/index.php?title=Funkce_(programování)",
"PROCEDURES_DEFNORETURN_TITLE": "k provedení",
"PROCEDURES_DEFNORETURN_PROCEDURE": "proveď něco",
"PROCEDURES_BEFORE_PARAMS": "s:",
"PROCEDURES_CALL_BEFORE_PARAMS": "s:",
"PROCEDURES_DEFNORETURN_TOOLTIP": "Vytvořit funkci bez výstupu.",
"PROCEDURES_DEFNORETURN_COMMENT": "Popište tuto funkci...",
"PROCEDURES_DEFRETURN_HELPURL": "https://cs.wikipedia.org/w/index.php?title=Funkce_(programování)",
"PROCEDURES_DEFRETURN_RETURN": "navrátit",
"PROCEDURES_DEFRETURN_TOOLTIP": "Vytvořit funkci s výstupem.",
"PROCEDURES_ALLOW_STATEMENTS": "povolit příkazy",

View file

@ -9,14 +9,15 @@
"Noamrotem",
"Dvb",
"LaG roiL",
"아라"
"아라",
"Elyashiv"
]
},
"VARIABLES_DEFAULT_NAME": "פריט",
"TODAY": "היום",
"DUPLICATE_BLOCK": "שכפל",
"ADD_COMMENT": "הוסף תגובה",
"REMOVE_COMMENT": "הסר הערה",
"REMOVE_COMMENT": "הסר תגובה",
"EXTERNAL_INPUTS": "קלטים חיצוניים",
"INLINE_INPUTS": "קלטים פנימיים",
"DELETE_BLOCK": "מחק קטע קוד",
@ -87,6 +88,7 @@
"LOGIC_OPERATION_TOOLTIP_OR": "תחזיר נכון אם מתקיים לפחות אחד מהקלטים נכונים.",
"LOGIC_OPERATION_OR": "או",
"LOGIC_NEGATE_TITLE": "לא %1",
"LOGIC_NEGATE_TOOLTIP": "החזר אמת אם הקלט הוא שקר. החזר שקר אם הקלט אמת.",
"LOGIC_BOOLEAN_TRUE": "נכון",
"LOGIC_BOOLEAN_FALSE": "שגוי",
"LOGIC_BOOLEAN_TOOLTIP": "תחזיר אם נכון או אם שגוי.",
@ -96,6 +98,7 @@
"LOGIC_TERNARY_IF_TRUE": "אם נכון",
"LOGIC_TERNARY_IF_FALSE": "אם שגוי",
"LOGIC_TERNARY_TOOLTIP": "בדוק את התנאי ב'מבחן'. אם התנאי נכון, תחזיר את הערך 'אם נכון'; אחרת תחזיר את הערך 'אם שגוי'.",
"MATH_NUMBER_HELPURL": "https://he.wikipedia.org/wiki/מספר_ממשי",
"MATH_NUMBER_TOOLTIP": "מספר.",
"MATH_ADDITION_SYMBOL": "+",
"MATH_SUBTRACTION_SYMBOL": "-",
@ -112,11 +115,22 @@
"MATH_ARITHMETIC_TOOLTIP_ADD": "תחזיר את סכום שני המספרים.",
"MATH_ARITHMETIC_TOOLTIP_MINUS": "החזרת ההפרש בין שני מספרים.",
"MATH_ARITHMETIC_TOOLTIP_MULTIPLY": "החזרת תוצאת הכפל בין שני מספרים.",
"MATH_ARITHMETIC_TOOLTIP_DIVIDE": "החזרת המנה של שני המספרים.",
"MATH_ARITHMETIC_TOOLTIP_POWER": "החזרת המספר הראשון בחזקת המספר השני.",
"MATH_SINGLE_HELPURL": "https://he.wikipedia.org/wiki/שורש_ריבועי",
"MATH_SINGLE_OP_ROOT": "שורש ריבועי",
"MATH_SINGLE_TOOLTIP_ROOT": "החזרת השורש הריבועי של מספר.",
"MATH_SINGLE_OP_ABSOLUTE": "ערך מוחלט",
"MATH_SINGLE_TOOLTIP_ABS": "החזרת הערך המוחלט של מספר.",
"MATH_SINGLE_TOOLTIP_NEG": "החזרת הערך הנגדי של מספר.",
"MATH_SINGLE_TOOLTIP_LN": "החזרת הלוגריתם הטבעי של מספר.",
"MATH_SINGLE_TOOLTIP_LOG10": "החזרת הלוגריתם לפי בסיס עשר של מספר.",
"MATH_SINGLE_TOOLTIP_EXP": "החזרת e בחזקת מספר.",
"MATH_SINGLE_TOOLTIP_POW10": "החזרת 10 בחזקת מספר.",
"MATH_TRIG_HELPURL": "https://he.wikipedia.org/wiki/פונקציות_טריגונומטריות",
"MATH_TRIG_TOOLTIP_SIN": "החזרת הסינוס של מעלה (לא רדיאן).",
"MATH_TRIG_TOOLTIP_COS": "החזרת הקוסינוס של מעלה (לא רדיאן).",
"MATH_TRIG_TOOLTIP_TAN": "החזרת הטנגס של מעלה (לא רדיאן).",
"MATH_IS_EVEN": "זוגי",
"MATH_IS_ODD": "אי-זוגי",
"MATH_IS_PRIME": "ראשוני",
@ -147,6 +161,8 @@
"TEXT_CREATE_JOIN_TITLE_JOIN": "צירוף",
"TEXT_APPEND_TO": "אל",
"TEXT_APPEND_APPENDTEXT": "הוספת טקסט",
"TEXT_GET_SUBSTRING_END_FROM_START": "לאות #",
"TEXT_GET_SUBSTRING_END_FROM_END": "לאות # מהסוף",
"TEXT_CHANGECASE_OPERATOR_UPPERCASE": "לאותיות גדולות (עבור טקסט באנגלית)",
"TEXT_CHANGECASE_OPERATOR_LOWERCASE": "לאותיות קטנות (עבור טקסט באנגלית)",
"TEXT_CHANGECASE_OPERATOR_TITLECASE": "לאותיות גדולות בתחילת כל מילה (עבור טקסט באנגלית)",
@ -176,7 +192,7 @@
"LISTS_INLIST": "ברשימה",
"LISTS_INDEX_OF_FIRST": "מחזירה את המיקום הראשון של פריט ברשימה",
"LISTS_INDEX_OF_LAST": "מחזירה את המיקום האחרון של פריט ברשימה",
"LISTS_INDEX_OF_TOOLTIP": "מחזירה את האינדקס של המופע ראשון/אחרון של הפריט ברשימה. מחזירה 0 אם טקסט אינו נמצא.",
"LISTS_INDEX_OF_TOOLTIP": "מחזירה את האינדקס של המופע ראשון/אחרון של הפריט ברשימה. מחזירה 0 אם הפריט אינו נמצא.",
"LISTS_GET_INDEX_GET": "לקבל",
"LISTS_GET_INDEX_GET_REMOVE": "קבל ומחק",
"LISTS_GET_INDEX_REMOVE": "הסרה",

View file

@ -4,22 +4,25 @@
"Kenrick95",
"아라",
"Mirws",
"Marwan Mohamad"
"Marwan Mohamad",
"Kasimtan"
]
},
"VARIABLES_DEFAULT_NAME": "item",
"TODAY": "Hari ini",
"DUPLICATE_BLOCK": "Duplikat",
"ADD_COMMENT": "Tambahkan sebuah comment",
"REMOVE_COMMENT": "Hapus komentar",
"EXTERNAL_INPUTS": "Input-input eksternal",
"INLINE_INPUTS": "Input inline",
"ADD_COMMENT": "Tambahkan Komentar",
"REMOVE_COMMENT": "Hapus Komentar",
"EXTERNAL_INPUTS": "Input Eksternal",
"INLINE_INPUTS": "Input Inline",
"DELETE_BLOCK": "Hapus Blok",
"DELETE_X_BLOCKS": "Hapus %1 Blok",
"DELETE_ALL_BLOCKS": "Hapus semua %1 blok?",
"CLEAN_UP": "Bersihkan Blok",
"COLLAPSE_BLOCK": "Ciutkan Blok",
"COLLAPSE_ALL": "Ciutkan Blok",
"EXPAND_BLOCK": "Kembangkan Blok",
"EXPAND_ALL": "Kembangkan blok-blok",
"EXPAND_ALL": "Kembangkan Blok",
"DISABLE_BLOCK": "Nonaktifkan Blok",
"ENABLE_BLOCK": "Aktifkan Blok",
"HELP": "Bantuan",
@ -27,9 +30,9 @@
"AUTH": "Silakan mengotorisasi aplikasi ini untuk memungkinkan pekerjaan Anda dapat disimpan dan digunakan bersama.",
"ME": "Saya",
"CHANGE_VALUE_TITLE": "Ubah nilai:",
"NEW_VARIABLE": "Pembolehubah baru...",
"NEW_VARIABLE_TITLE": "Nama pembolehubah baru:",
"RENAME_VARIABLE": "namai ulang variabel...",
"NEW_VARIABLE": "Variabel baru...",
"NEW_VARIABLE_TITLE": "Nama variabel baru:",
"RENAME_VARIABLE": "Ubah nama variabel...",
"RENAME_VARIABLE_TITLE": "Ubah nama semua variabel '%1' menjadi:",
"COLOUR_PICKER_HELPURL": "https://en.wikipedia.org/wiki/Color",
"COLOUR_PICKER_TOOLTIP": "Pilih warna dari daftar warna.",
@ -42,62 +45,62 @@
"COLOUR_RGB_BLUE": "biru",
"COLOUR_RGB_TOOLTIP": "Buatlah warna dengan jumlah yang ditentukan dari merah, hijau dan biru. Semua nilai harus antarai 0 sampai 100.",
"COLOUR_BLEND_HELPURL": "http://meyerweb.com/eric/tools/color-blend/",
"COLOUR_BLEND_TITLE": "Tertutup",
"COLOUR_BLEND_COLOUR1": "Warna 1",
"COLOUR_BLEND_COLOUR2": "Warna 2",
"COLOUR_BLEND_TITLE": "campur",
"COLOUR_BLEND_COLOUR1": "warna 1",
"COLOUR_BLEND_COLOUR2": "warna 2",
"COLOUR_BLEND_RATIO": "rasio",
"COLOUR_BLEND_TOOLTIP": "mencampur dua warna secara bersamaan dengan perbandingan (0.0-1.0).",
"COLOUR_BLEND_TOOLTIP": "Campur dua warna secara bersamaan dengan perbandingan (0.0 - 1.0).",
"CONTROLS_REPEAT_HELPURL": "https://en.wikipedia.org/wiki/For_loop",
"CONTROLS_REPEAT_TITLE": "ulangi %1 kali",
"CONTROLS_REPEAT_INPUT_DO": "kerjakan",
"CONTROLS_REPEAT_TOOLTIP": "Lakukan beberapa perintah beberapa kali.",
"CONTROLS_WHILEUNTIL_OPERATOR_WHILE": "Ulangi jika",
"CONTROLS_WHILEUNTIL_OPERATOR_UNTIL": "Ulangi sampai",
"CONTROLS_WHILEUNTIL_TOOLTIP_WHILE": "Jika sementara nilai benar (true), maka lakukan beberapa perintah.",
"CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL": "Jika sementara nilai tidak benar (false), maka lakukan beberapa perintah.",
"CONTROLS_WHILEUNTIL_OPERATOR_WHILE": "ulangi jika",
"CONTROLS_WHILEUNTIL_OPERATOR_UNTIL": "ulangi sampai",
"CONTROLS_WHILEUNTIL_TOOLTIP_WHILE": "Selagi nilainya benar, maka lakukan beberapa perintah.",
"CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL": "Selagi nilainya salah, maka lakukan beberapa perintah.",
"CONTROLS_FOR_TOOLTIP": "Menggunakan variabel \"%1\" dengan mengambil nilai dari batas awal hingga ke batas akhir, dengan interval tertentu, dan mengerjakan block tertentu.",
"CONTROLS_FOR_TITLE": "Cacah dengan %1 dari %2 ke %3 dengan step / penambahan %4",
"CONTROLS_FOREACH_TITLE": "untuk setiap item %1 di dalam list %2",
"CONTROLS_FOREACH_TOOLTIP": "Untuk tiap-tiap item di dalam list, tetapkan variabel '%1' ke dalam item, selanjutnya kerjakan beberapa statement.",
"CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK": "Keluar dari perulangan",
"CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE": "Lanjutkan dengan langkah penggulangan berikutnya",
"CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK": "Keluar sementara dari perulanggan.",
"CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE": "Abaikan sisa dari loop ini, dan lanjutkan dengan iterasi berikutnya.",
"CONTROLS_FLOW_STATEMENTS_WARNING": "Peringatan: Blok ini hanya dapat digunakan dalam loop.",
"CONTROLS_IF_TOOLTIP_1": "jika nilainya benar maka kerjakan perintah berikutnya.",
"CONTROLS_IF_TOOLTIP_2": "jika nilainya benar, maka kerjakan blok perintah yang pertama. Jika tidak, kerjakan blok perintah yang kedua.",
"CONTROLS_IF_TOOLTIP_3": "Jika nilai pertama adalah benar (true), maka lakukan perintah-perintah yang berada didalam blok pertama. Jika nilai kedua adalah benar (true), maka lakukan perintah-perintah yang berada didalam blok kedua.",
"CONTROLS_IF_TOOLTIP_4": "Jika blok pertama adalah benar (true), maka lakukan perintah-perintah yang berada didalam blok pertama. Atau jika blok kedua adalah benar (true), maka lakukan perintah-perintah yang berada didalam blok kedua.",
"CONTROLS_IF_MSG_IF": "Jika",
"CONTROLS_IF_MSG_ELSEIF": "else if",
"CONTROLS_IF_MSG_ELSE": "else",
"CONTROLS_IF_IF_TOOLTIP": "Menambahkan, menghapus, atau menyusun kembali bagian untuk mengkonfigurasi blok IF ini.",
"CONTROLS_IF_ELSEIF_TOOLTIP": "tambahkan prasyarat ke dalam blok IF.",
"CONTROLS_IF_ELSE_TOOLTIP": "Terakhir, tambahkan tangkap-semua kondisi kedalam blok jika (if).",
"CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK": "keluar dari perulangan",
"CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE": "lanjutkan dengan langkah perulangan berikutnya",
"CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK": "Keluar dari perulangan.",
"CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE": "Abaikan sisa dari perulangan ini, dan lanjutkan dengan langkah berikutnya.",
"CONTROLS_FLOW_STATEMENTS_WARNING": "Peringatan: Blok ini hanya dapat digunakan dalam perulangan.",
"CONTROLS_IF_TOOLTIP_1": "Jika nilainya benar, maka lakukan beberapa perintah.",
"CONTROLS_IF_TOOLTIP_2": "Jika nilainya benar, maka kerjakan perintah blok pertama. Jika tidak, kerjakan perintah blok kedua.",
"CONTROLS_IF_TOOLTIP_3": "Jika nilai pertama benar, maka kerjakan perintah blok pertama. Sebaliknya, jika nilai kedua benar, kerjakan perintah blok kedua.",
"CONTROLS_IF_TOOLTIP_4": "Jika nilai pertama benar, maka kerjakan perintah blok pertama. Sebaliknya, jika nilai kedua benar, kerjakan perintah blok kedua. Jika dua-duanya tidak benar, kerjakan perintah blok terakhir.",
"CONTROLS_IF_MSG_IF": "jika",
"CONTROLS_IF_MSG_ELSEIF": "atau jika",
"CONTROLS_IF_MSG_ELSE": "lainnya",
"CONTROLS_IF_IF_TOOLTIP": "Tambahkan, hapus, atau susun kembali bagian untuk mengkonfigurasi blok IF ini.",
"CONTROLS_IF_ELSEIF_TOOLTIP": "Tambahkan prasyarat ke dalam blok IF.",
"CONTROLS_IF_ELSE_TOOLTIP": "Terakhir, tambahkan kondisi tangkap-semua kedalam blok IF.",
"LOGIC_COMPARE_HELPURL": "https://en.wikipedia.org/wiki/Inequality_(mathematics)",
"LOGIC_COMPARE_TOOLTIP_EQ": "Mengembalikan betul jika input kedua-duanya sama dengan satu sama lain.",
"LOGIC_COMPARE_TOOLTIP_NEQ": "Mengembalikan nilai benar (true) jika kedua input tidak sama satu dengan yang lain.",
"LOGIC_COMPARE_TOOLTIP_LT": "Mengembalikan nilai benar (true) jika input yang pertama lebih kecil dari input yang kedua.",
"LOGIC_COMPARE_TOOLTIP_LTE": "Mengembalikan nilai benar (true) jika input yang pertama lebih kecil atau sama dengan input yang kedua .",
"LOGIC_COMPARE_TOOLTIP_GT": "Mengembalikan nilai benar (true) jika input yang pertama lebih besar dari input yang kedua.",
"LOGIC_COMPARE_TOOLTIP_GTE": "Mengembalikan nilai benar (true) jika input yang pertama lebih besar dari atau sama dengan input yang kedua.",
"LOGIC_OPERATION_TOOLTIP_AND": "Kembalikan betul jika kedua-dua input adalah betul.",
"LOGIC_COMPARE_TOOLTIP_EQ": "Kembalikan benar jika kedua input sama satu dengan lainnya.",
"LOGIC_COMPARE_TOOLTIP_NEQ": "Kembalikan benar jika kedua input tidak sama satu dengan lainnya.",
"LOGIC_COMPARE_TOOLTIP_LT": "Kembalikan benar jika input pertama lebih kecil dari input kedua.",
"LOGIC_COMPARE_TOOLTIP_LTE": "Kembalikan benar jika input pertama lebih kecil atau sama dengan input kedua .",
"LOGIC_COMPARE_TOOLTIP_GT": "Kembalikan benar jika input pertama lebih besar dari input kedua.",
"LOGIC_COMPARE_TOOLTIP_GTE": "Kembalikan benar jika input pertama lebih besar dari atau sama dengan input kedua.",
"LOGIC_OPERATION_TOOLTIP_AND": "Kembalikan benar jika kedua input adalah benar.",
"LOGIC_OPERATION_AND": "dan",
"LOGIC_OPERATION_TOOLTIP_OR": "Mengembalikan nilai benar (true) jika setidaknya salah satu masukan nilainya benar (true).",
"LOGIC_OPERATION_TOOLTIP_OR": "Kembalikan benar jika minimal satu input nilainya benar.",
"LOGIC_OPERATION_OR": "atau",
"LOGIC_NEGATE_TITLE": "bukan (not) %1",
"LOGIC_NEGATE_TOOLTIP": "Mengembalikan nilai benar (true) jika input false. Mengembalikan nilai salah (false) jika input true.",
"LOGIC_BOOLEAN_TRUE": "Benar",
"LOGIC_BOOLEAN_FALSE": "Salah",
"LOGIC_BOOLEAN_TOOLTIP": "Mengembalikan betul (true) atau salah (false).",
"LOGIC_NEGATE_TOOLTIP": "Kembalikan benar jika input salah. Kembalikan salah jika input benar.",
"LOGIC_BOOLEAN_TRUE": "benar",
"LOGIC_BOOLEAN_FALSE": "salah",
"LOGIC_BOOLEAN_TOOLTIP": "Kembalikan benar atau salah.",
"LOGIC_NULL_HELPURL": "https://en.wikipedia.org/wiki/Nullable_type",
"LOGIC_NULL": "null",
"LOGIC_NULL_TOOLTIP": "mengembalikan kosong.",
"LOGIC_NULL_TOOLTIP": "Kembalikan null.",
"LOGIC_TERNARY_HELPURL": "https://en.wikipedia.org/wiki/%3F:",
"LOGIC_TERNARY_CONDITION": "test",
"LOGIC_TERNARY_IF_TRUE": "jika benar (true)",
"LOGIC_TERNARY_IF_FALSE": "jika tidak benar (false)",
"LOGIC_TERNARY_TOOLTIP": "Periksa kondisi di \"test\". Jika kondisi benar (true), mengembalikan nilai \"jika benar\" ; Jik sebaliknya akan mengembalikan nilai \"jika salah\".",
"LOGIC_TERNARY_IF_TRUE": "jika benar",
"LOGIC_TERNARY_IF_FALSE": "jika salah",
"LOGIC_TERNARY_TOOLTIP": "Periksa kondisi di 'test'. Jika kondisi benar, kembalikan nilai 'if true'; jika sebaliknya kembalikan nilai 'if false'.",
"MATH_NUMBER_HELPURL": "https://en.wikipedia.org/wiki/Number",
"MATH_NUMBER_TOOLTIP": "Suatu angka.",
"MATH_ADDITION_SYMBOL": "+",
@ -129,8 +132,8 @@
"MATH_SINGLE_TOOLTIP_POW10": "Kembalikan 10 pangkat angka.",
"MATH_TRIG_HELPURL": "https://en.wikipedia.org/wiki/Trigonometric_functions",
"MATH_TRIG_TOOLTIP_SIN": "Kembalikan sinus dari derajat (bukan radian).",
"MATH_TRIG_TOOLTIP_COS": "Kembalikan cos dari derajat (bukan radian).",
"MATH_TRIG_TOOLTIP_TAN": "Kembalikan tangen dari derajat (tidak radian).",
"MATH_TRIG_TOOLTIP_COS": "Kembalikan cosinus dari derajat (bukan radian).",
"MATH_TRIG_TOOLTIP_TAN": "Kembalikan tangen dari derajat (bukan radian).",
"MATH_TRIG_TOOLTIP_ASIN": "Kembalikan asin dari angka.",
"MATH_TRIG_TOOLTIP_ACOS": "Kembalikan acosine dari angka.",
"MATH_TRIG_TOOLTIP_ATAN": "Kembalikan atan dari angka.",
@ -140,48 +143,48 @@
"MATH_IS_ODD": "adalah bilangan ganjil",
"MATH_IS_PRIME": "adalah bilangan pokok",
"MATH_IS_WHOLE": "adalah bilangan bulat",
"MATH_IS_POSITIVE": "adalah bilangan positif",
"MATH_IS_NEGATIVE": "adalah bilangan negatif",
"MATH_IS_DIVISIBLE_BY": "dibagi oleh",
"MATH_IS_TOOLTIP": "Periksa apakah angka adalah bilangan genap, bilangan pokok, bilangan bulat, bilangan positif, bilangan negatif, atau apakan bisa dibagi oleh angka tertentu. Mengembalikan benar (true) atau salah (false).",
"MATH_IS_POSITIVE": "adalah bilangan positif",
"MATH_IS_NEGATIVE": "adalah bilangan negatif",
"MATH_IS_DIVISIBLE_BY": "dapat dibagi oleh",
"MATH_IS_TOOLTIP": "Periksa apakah angka adalah bilangan genap, bilangan ganjil, bilangan pokok, bilangan bulat, bilangan positif, bilangan negatif, atau apakan bisa dibagi oleh angka tertentu. Kembalikan benar atau salah.",
"MATH_CHANGE_HELPURL": "https://en.wikipedia.org/wiki/Programming_idiom#Incrementing_a_counter",
"MATH_CHANGE_TITLE": "ubah %1 oleh %2",
"MATH_CHANGE_TOOLTIP": "Tambahkan angka kedalam variabel '%1'.",
"MATH_ROUND_HELPURL": "https://en.wikipedia.org/wiki/Rounding",
"MATH_ROUND_TOOLTIP": "Bulatkan suatu bilangan naik atau turun.",
"MATH_ROUND_OPERATOR_ROUND": "membulatkan",
"MATH_ROUND_OPERATOR_ROUNDUP": "mengumpulkan",
"MATH_ROUND_OPERATOR_ROUNDUP": "membulatkan keatas",
"MATH_ROUND_OPERATOR_ROUNDDOWN": "membulatkan kebawah",
"MATH_ONLIST_OPERATOR_SUM": "jumlah dari list (daftar)",
"MATH_ONLIST_OPERATOR_SUM": "jumlah dari list",
"MATH_ONLIST_TOOLTIP_SUM": "Kembalikan jumlah dari seluruh bilangan dari list.",
"MATH_ONLIST_OPERATOR_MIN": "minimum dari list (daftar)",
"MATH_ONLIST_OPERATOR_MIN": "minimum dari list",
"MATH_ONLIST_TOOLTIP_MIN": "Kembalikan angka terkecil dari list.",
"MATH_ONLIST_OPERATOR_MAX": "maximum dari list (daftar)",
"MATH_ONLIST_OPERATOR_MAX": "maksimum dari list",
"MATH_ONLIST_TOOLTIP_MAX": "Kembalikan angka terbesar dari list.",
"MATH_ONLIST_OPERATOR_AVERAGE": "rata-rata dari list (daftar)",
"MATH_ONLIST_TOOLTIP_AVERAGE": "Kembalikan rata-rata (mean aritmetik) dari nilai numerik dari list (daftar).",
"MATH_ONLIST_OPERATOR_MEDIAN": "median dari list (daftar)",
"MATH_ONLIST_OPERATOR_AVERAGE": "rata-rata dari list",
"MATH_ONLIST_TOOLTIP_AVERAGE": "Kembalikan rata-rata (mean aritmetik) dari nilai numerik dari list.",
"MATH_ONLIST_OPERATOR_MEDIAN": "median dari list",
"MATH_ONLIST_TOOLTIP_MEDIAN": "Kembalikan median dari list.",
"MATH_ONLIST_OPERATOR_MODE": "mode-mode dari list (daftar)",
"MATH_ONLIST_TOOLTIP_MODE": "Kembalikan list berisi item-item yang paling umum dari dalam list.",
"MATH_ONLIST_OPERATOR_STD_DEV": "deviasi standar dari list (daftar)",
"MATH_ONLIST_OPERATOR_MODE": "mode-mode dari list",
"MATH_ONLIST_TOOLTIP_MODE": "Kembalikan list berisi item yang paling umum dari dalam list.",
"MATH_ONLIST_OPERATOR_STD_DEV": "deviasi standar dari list",
"MATH_ONLIST_TOOLTIP_STD_DEV": "Kembalikan standard deviasi dari list.",
"MATH_ONLIST_OPERATOR_RANDOM": "item acak dari list (daftar)",
"MATH_ONLIST_TOOLTIP_RANDOM": "Kembalikan element acak dari list.",
"MATH_ONLIST_OPERATOR_RANDOM": "item acak dari list",
"MATH_ONLIST_TOOLTIP_RANDOM": "Kembalikan elemen acak dari list.",
"MATH_MODULO_HELPURL": "https://en.wikipedia.org/wiki/Modulo_operation",
"MATH_MODULO_TITLE": "sisa %1 ÷ %2",
"MATH_MODULO_TITLE": "sisa dari %1 ÷ %2",
"MATH_MODULO_TOOLTIP": "Kembalikan sisa dari pembagian ke dua angka.",
"MATH_CONSTRAIN_TITLE": "Batasi %1 rendah %2 tinggi %3",
"MATH_CONSTRAIN_TOOLTIP": "Batasi angka antara batas yang ditentukan (inklusif).",
"MATH_RANDOM_INT_HELPURL": "https://en.wikipedia.org/wiki/Random_number_generation",
"MATH_RANDOM_INT_TITLE": "acak bulat dari %1 sampai %2",
"MATH_RANDOM_INT_TOOLTIP": "Mengembalikan bilangan acak antara dua batas yang ditentukan, inklusif.",
"MATH_RANDOM_INT_TOOLTIP": "Kembalikan bilangan acak antara dua batas yang ditentukan, inklusif.",
"MATH_RANDOM_FLOAT_HELPURL": "https://en.wikipedia.org/wiki/Random_number_generation",
"MATH_RANDOM_FLOAT_TITLE_RANDOM": "Nilai pecahan acak",
"MATH_RANDOM_FLOAT_TOOLTIP": "Mengembalikan nilai acak pecahan antara 0.0 (inklusif) dan 1.0 (ekslusif).",
"MATH_RANDOM_FLOAT_TITLE_RANDOM": "nilai pecahan acak",
"MATH_RANDOM_FLOAT_TOOLTIP": "Kembalikan nilai pecahan acak antara 0.0 (inklusif) dan 1.0 (eksklusif).",
"TEXT_TEXT_HELPURL": "https://en.wikipedia.org/wiki/String_(computer_science)",
"TEXT_TEXT_TOOLTIP": "Huruf, kata atau baris teks.",
"TEXT_JOIN_TITLE_CREATEWITH": "Buat teks dengan",
"TEXT_JOIN_TITLE_CREATEWITH": "buat teks dengan",
"TEXT_JOIN_TOOLTIP": "Buat teks dengan cara gabungkan sejumlah item.",
"TEXT_CREATE_JOIN_TITLE_JOIN": "join",
"TEXT_CREATE_JOIN_TOOLTIP": "Tambah, ambil, atau susun ulang teks blok.",
@ -192,7 +195,7 @@
"TEXT_LENGTH_TITLE": "panjang dari %1",
"TEXT_LENGTH_TOOLTIP": "Kembalikan sejumlah huruf (termasuk spasi) dari teks yang disediakan.",
"TEXT_ISEMPTY_TITLE": "%1 kosong",
"TEXT_ISEMPTY_TOOLTIP": "Kembalikan benar (true) jika teks yang disediakan kosong.",
"TEXT_ISEMPTY_TOOLTIP": "Kembalikan benar jika teks yang disediakan kosong.",
"TEXT_INDEXOF_TOOLTIP": "Kembalikan indeks pertama dan terakhir dari kejadian pertama/terakhir dari teks pertama dalam teks kedua. Kembalikan 0 jika teks tidak ditemukan.",
"TEXT_INDEXOF_INPUT_INTEXT": "dalam teks",
"TEXT_INDEXOF_OPERATOR_FIRST": "temukan kejadian pertama dalam teks",
@ -204,7 +207,7 @@
"TEXT_CHARAT_LAST": "ambil huruf terakhir",
"TEXT_CHARAT_RANDOM": "ambil huruf secara acak",
"TEXT_CHARAT_TOOLTIP": "Kembalikan karakter dari posisi tertentu.",
"TEXT_GET_SUBSTRING_TOOLTIP": "Mengembalikan spesifik bagian dari teks.",
"TEXT_GET_SUBSTRING_TOOLTIP": "Kembalikan spesifik bagian dari teks.",
"TEXT_GET_SUBSTRING_INPUT_IN_TEXT": "in teks",
"TEXT_GET_SUBSTRING_START_FROM_START": "ambil bagian teks (substring) dari huruf no #",
"TEXT_GET_SUBSTRING_START_FROM_END": "ambil bagian teks (substring) dari huruf ke # dari terakhir",
@ -228,22 +231,22 @@
"TEXT_PROMPT_TOOLTIP_TEXT": "Meminta pengguna untuk memberi beberapa teks.",
"LISTS_CREATE_EMPTY_HELPURL": "https://github.com/google/blockly/wiki/Lists#create-empty-list",
"LISTS_CREATE_EMPTY_TITLE": "buat list kosong",
"LISTS_CREATE_EMPTY_TOOLTIP": "Mengembalikan daftar, dengan panjang 0, tidak berisi data",
"LISTS_CREATE_WITH_TOOLTIP": "Buat sebuah daftar (list) dengan sejumlah item.",
"LISTS_CREATE_WITH_INPUT_WITH": "buat daftar (list) dengan",
"LISTS_CREATE_EMPTY_TOOLTIP": "Kembalikan list, dengan panjang 0, tidak berisi data",
"LISTS_CREATE_WITH_TOOLTIP": "Buat sebuah list dengan sejumlah item.",
"LISTS_CREATE_WITH_INPUT_WITH": "buat list dengan",
"LISTS_CREATE_WITH_CONTAINER_TITLE_ADD": "list",
"LISTS_CREATE_WITH_CONTAINER_TOOLTIP": "Tambahkan, hapus, atau susun ulang bagian untuk mengkonfigurasi blok LIST (daftar) ini.",
"LISTS_CREATE_WITH_ITEM_TOOLTIP": "Tambahkan sebuah item ke daftar (list).",
"LISTS_REPEAT_TOOLTIP": "Ciptakan daftar yang terdiri dari nilai yang diberikan diulang jumlah waktu yang ditentukan.",
"LISTS_REPEAT_TITLE": "membuat daftar dengan item %1 diulang %2 kali",
"LISTS_CREATE_WITH_CONTAINER_TOOLTIP": "Tambahkan, hapus, atau susun ulang bagian untuk mengkonfigurasi blok list ini.",
"LISTS_CREATE_WITH_ITEM_TOOLTIP": "Tambahkan sebuah item ke list.",
"LISTS_REPEAT_TOOLTIP": "Buat sebuah list yang terdiri dari nilai yang diberikan diulang sebanyak jumlah yang ditentukan.",
"LISTS_REPEAT_TITLE": "buat list dengan item %1 diulang %2 kali",
"LISTS_LENGTH_TITLE": "panjang dari %1",
"LISTS_LENGTH_TOOLTIP": "Mengembalikan panjang daftar.",
"LISTS_LENGTH_TOOLTIP": "Kembalikan panjang list.",
"LISTS_ISEMPTY_TITLE": "%1 kosong",
"LISTS_ISEMPTY_TOOLTIP": "Mengembalikan nilai benar (true) jika list kosong.",
"LISTS_INLIST": "dalam daftar",
"LISTS_ISEMPTY_TOOLTIP": "Kembalikan benar jika list kosong.",
"LISTS_INLIST": "dalam list",
"LISTS_INDEX_OF_FIRST": "cari kejadian pertama item",
"LISTS_INDEX_OF_LAST": "Cari kejadian terakhir item",
"LISTS_INDEX_OF_TOOLTIP": "Mengembalikan indeks dari kejadian pertama/terakhir item dalam daftar. Menghasilkan 0 jika teks tidak ditemukan.",
"LISTS_INDEX_OF_LAST": "cari kejadian terakhir item",
"LISTS_INDEX_OF_TOOLTIP": "Kembalikan indeks dari item pertama/terakhir kali muncul dalam list. Kembalikan 0 jika item tidak ditemukan.",
"LISTS_GET_INDEX_GET": "dapatkan",
"LISTS_GET_INDEX_GET_REMOVE": "dapatkan dan hapus",
"LISTS_GET_INDEX_REMOVE": "Hapus",
@ -252,21 +255,21 @@
"LISTS_GET_INDEX_FIRST": "pertama",
"LISTS_GET_INDEX_LAST": "terakhir",
"LISTS_GET_INDEX_RANDOM": "acak",
"LISTS_GET_INDEX_TOOLTIP_GET_FROM_START": "Sisipkan item ke dalam posisi yang telah ditentukan didalam list (daftar). Item pertama adalah item terakhir (yg paling akhir).",
"LISTS_GET_INDEX_TOOLTIP_GET_FROM_END": "Sisipkan item ke dalam posisi yang telah ditentukan didalam list (daftar). Item pertama adalah item yang terakhir.",
"LISTS_GET_INDEX_TOOLTIP_GET_FIRST": "Kembalikan item pertama dalam daftar (list).",
"LISTS_GET_INDEX_TOOLTIP_GET_LAST": "Mengembalikan item pertama dalam list (daftar).",
"LISTS_GET_INDEX_TOOLTIP_GET_RANDOM": "Mengembalikan item acak dalam list (daftar).",
"LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM_START": "Menghilangkan dan mengembalikan barang di posisi tertentu dalam list (daftar). #1 adalah item pertama.",
"LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM_END": "Menghilangkan dan mengembalikan barang di posisi tertentu dalam list (daftar). #1 adalah item terakhir.",
"LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FIRST": "Menghilangkan dan mengembalikan item pertama dalam list (daftar).",
"LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_LAST": "Menghilangkan dan mengembalikan item terakhir dalam list (daftar).",
"LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_RANDOM": "Menghilangkan dan mengembalikan barang dengan acak dalam list (daftar).",
"LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM_START": "Menghapus item dengan posisi tertentu dalam daftar. Item pertama adalah item yang terakhir.",
"LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM_END": "Menghapus item dengan posisi tertentu dalam daftar. Item pertama adalah item yang terakhir.",
"LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST": "Menghapus item pertama dalam daftar.",
"LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST": "Menghapus item terakhir dalam daftar.",
"LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM": "Menghapus sebuah item secara acak dalam list.",
"LISTS_GET_INDEX_TOOLTIP_GET_FROM_START": "Kembalikan item di posisi tertentu dalam list. #1 adalah item pertama.",
"LISTS_GET_INDEX_TOOLTIP_GET_FROM_END": "Kembalikan item di posisi tertentu dalam list. #1 adalah item terakhir.",
"LISTS_GET_INDEX_TOOLTIP_GET_FIRST": "Kembalikan item pertama dalam list.",
"LISTS_GET_INDEX_TOOLTIP_GET_LAST": "Kembalikan item terakhir dalam list.",
"LISTS_GET_INDEX_TOOLTIP_GET_RANDOM": "Kembalikan item acak dalam list.",
"LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM_START": "Hapus dan kembalikan item di posisi tertentu dalam list. #1 adalah item pertama.",
"LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM_END": "Hapus dan kembalikan item di posisi tertentu dalam list. #1 adalah item terakhir.",
"LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FIRST": "Hapus dan kembalikan item pertama dalam list.",
"LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_LAST": "Hapus dan kembalikan item terakhir dalam list.",
"LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_RANDOM": "Hapus dan kembalikan item acak dalam list.",
"LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM_START": "Hapus item di posisi tertentu dalam list. #1 adalah item pertama.",
"LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM_END": "Hapus item di posisi tertentu dalam list. #1 adalah item terakhir.",
"LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST": "Hapus item pertama dalam list.",
"LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST": "Hapus item terakhir dalam list.",
"LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM": "Hapus sebuah item acak dalam list.",
"LISTS_SET_INDEX_SET": "tetapkan",
"LISTS_SET_INDEX_INSERT": "sisipkan di",
"LISTS_SET_INDEX_INPUT_TO": "sebagai",
@ -280,32 +283,33 @@
"LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST": "Sisipkan item di bagian awal dari list.",
"LISTS_SET_INDEX_TOOLTIP_INSERT_LAST": "Tambahkan item ke bagian akhir list.",
"LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM": "Sisipkan item secara acak ke dalam list.",
"LISTS_GET_SUBLIST_START_FROM_START": "Dapatkan bagian daftar dari #",
"LISTS_GET_SUBLIST_START_FROM_END": "Dapatkan bagian list nomor # dari akhir",
"LISTS_GET_SUBLIST_START_FIRST": "Dapatkan bagian pertama dari list",
"LISTS_GET_SUBLIST_START_FROM_START": "dapatkan sub-list dari #",
"LISTS_GET_SUBLIST_START_FROM_END": "dapatkan sub-list dari nomor # dari akhir",
"LISTS_GET_SUBLIST_START_FIRST": "dapatkan sub-list dari pertama",
"LISTS_GET_SUBLIST_END_FROM_START": "ke #",
"LISTS_GET_SUBLIST_END_FROM_END": "ke # dari akhir",
"LISTS_GET_SUBLIST_END_LAST": "ke yang paling akhir",
"LISTS_GET_SUBLIST_TOOLTIP": "Membuat salinan dari bagian tertentu dari list.",
"LISTS_SPLIT_LIST_FROM_TEXT": "membuat daftar dari teks",
"LISTS_SPLIT_TEXT_FROM_LIST": "buat teks dari daftar",
"LISTS_GET_SUBLIST_TOOLTIP": "Buat salinan bagian tertentu dari list.",
"LISTS_SPLIT_LIST_FROM_TEXT": "buat list dari teks",
"LISTS_SPLIT_TEXT_FROM_LIST": "buat teks dari list",
"LISTS_SPLIT_WITH_DELIMITER": "dengan pembatas",
"LISTS_SPLIT_TOOLTIP_SPLIT": "Membagi teks ke dalam daftar teks, pisahkan pada setiap pembatas.",
"LISTS_SPLIT_TOOLTIP_JOIN": "Gabung daftar teks menjadi satu teks, yang dipisahkan oleh pembatas.",
"VARIABLES_GET_TOOLTIP": "Mengembalikan nilai variabel ini.",
"VARIABLES_GET_CREATE_SET": "Membuat 'tetapkan %1'",
"VARIABLES_GET_TOOLTIP": "Kembalikan nilai variabel ini.",
"VARIABLES_GET_CREATE_SET": "Buat 'set %1'",
"VARIABLES_SET": "tetapkan %1 untuk %2",
"VARIABLES_SET_TOOLTIP": "tetapkan variabel ini dengan input yang sama.",
"VARIABLES_SET_CREATE_GET": "Membuat 'dapatkan %1'",
"VARIABLES_SET_CREATE_GET": "Buat 'get %1'",
"PROCEDURES_DEFNORETURN_HELPURL": "https://en.wikipedia.org/wiki/Procedure_%28computer_science%29",
"PROCEDURES_DEFNORETURN_TITLE": "untuk",
"PROCEDURES_DEFNORETURN_PROCEDURE": "buat sesuatu",
"PROCEDURES_BEFORE_PARAMS": "dengan:",
"PROCEDURES_CALL_BEFORE_PARAMS": "dengan:",
"PROCEDURES_DEFNORETURN_TOOLTIP": "Menciptakan sebuah fungsi dengan tiada output.",
"PROCEDURES_DEFNORETURN_TOOLTIP": "Buat sebuah fungsi tanpa output.",
"PROCEDURES_DEFNORETURN_COMMENT": "Jelaskan fungsi ini...",
"PROCEDURES_DEFRETURN_HELPURL": "https://en.wikipedia.org/wiki/Procedure_%28computer_science%29",
"PROCEDURES_DEFRETURN_RETURN": "kembali",
"PROCEDURES_DEFRETURN_TOOLTIP": "Menciptakan sebuah fungsi dengan satu output.",
"PROCEDURES_DEFRETURN_TOOLTIP": "Buat sebuah fungsi dengan satu output.",
"PROCEDURES_ALLOW_STATEMENTS": "memungkinkan pernyataan",
"PROCEDURES_DEF_DUPLICATE_WARNING": "Peringatan: Fungsi ini memiliki parameter duplikat.",
"PROCEDURES_CALLNORETURN_HELPURL": "https://en.wikipedia.org/wiki/Procedure_%28computer_science%29",

View file

@ -15,6 +15,7 @@
"INLINE_INPUTS": "Išdėstyti vienoje eilutėje",
"DELETE_BLOCK": "Ištrinti bloką",
"DELETE_X_BLOCKS": "Ištrinti %1 blokus",
"DELETE_ALL_BLOCKS": "Ištrinti visus %1 blokus?",
"CLEAN_UP": "Išvalyti blokus",
"COLLAPSE_BLOCK": "Suskleisti bloką",
"COLLAPSE_ALL": "Suskleisti blokus",

View file

@ -49,6 +49,18 @@
<script src="../../generators/dart/variables.js"></script>
<script src="../../generators/dart/procedures.js"></script>
<script src="../../generators/lua.js"></script>
<script src="unittest_lua.js"></script>
<script src="../../generators/lua/logic.js"></script>
<script src="../../generators/lua/loops.js"></script>
<script src="../../generators/lua/math.js"></script>
<script src="../../generators/lua/text.js"></script>
<script src="../../generators/lua/lists.js"></script>
<script src="../../generators/lua/colour.js"></script>
<script src="../../generators/lua/variables.js"></script>
<script src="../../generators/lua/procedures.js"></script>
<script src="unittest.js"></script>
<script src="../../msg/messages.js"></script>
<script src="../../blocks/logic.js"></script>
@ -153,6 +165,11 @@ function toDart() {
var code = Blockly.Dart.workspaceToCode(workspace);
setOutput(code);
}
function toLua() {
var code = Blockly.Lua.workspaceToCode(workspace);
setOutput(code);
}
</script>
<style>
@ -287,6 +304,7 @@ h1 {
<input type="button" value="Python" onclick="toPython()">
<input type="button" value="PHP" onclick="toPhp()">
<input type="button" value="Dart" onclick="toDart()">
<input type="button" value="Lua" onclick="toLua()">
</p>
</td></tr><tr><td height="99%">
<textarea id="importExport" readonly="readonly" wrap="off"></textarea>

View file

@ -0,0 +1,163 @@
/**
* @license
* Visual Blocks Language
*
* Copyright 2016 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Generating Lua for unit test blocks.
* @author rodrigoq@google.com (Rodrigo Queiro)
*/
'use strict';
Blockly.Lua['unittest_main'] = function(block) {
// Container for unit tests.
var resultsVar = Blockly.Lua.variableDB_.getName('unittestResults',
Blockly.Variables.NAME_TYPE);
var functionName = Blockly.Lua.provideFunction_(
'unittest_report',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '()',
' -- Create test report.',
' local report = {}',
' local summary = {}',
' local fails = 0',
' for _, v in pairs(' + resultsVar + ') do',
' if v["success"] then',
' table.insert(summary, ".")',
' else',
' table.insert(summary, "F")',
' fails = fails + 1',
' table.insert(report, "FAIL: " .. v["title"])',
' table.insert(report, v["log"])',
' end',
' end',
' table.insert(report, 1, table.concat(summary))',
' table.insert(report, "")',
' table.insert(report, "Number of tests run: " .. #' + resultsVar + ')',
' table.insert(report, "")',
' if fails > 0 then',
' table.insert(report, "FAILED (failures=" .. fails .. ")")',
' else',
' table.insert(report, "OK")',
' end',
' return table.concat(report, "\\n")',
'end']);
// Setup global to hold test results.
var code = resultsVar + ' = {}\n';
// Run tests (unindented).
code += Blockly.Lua.statementToCode(block, 'DO')
.replace(/^ /, '').replace(/\n /g, '\n');
var reportVar = Blockly.Lua.variableDB_.getDistinctName(
'report', Blockly.Variables.NAME_TYPE);
code += reportVar + ' = ' + functionName + '()\n';
// Destroy results.
code += resultsVar + ' = nil\n';
// Print the report.
code += 'print(' + reportVar + ')\n';
return code;
};
Blockly.Lua['unittest_main'].defineAssert_ = function(block) {
var resultsVar = Blockly.Lua.variableDB_.getName('unittestResults',
Blockly.Variables.NAME_TYPE);
var functionName = Blockly.Lua.provideFunction_(
'assertEquals',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ +
'(actual, expected, message)',
' -- Asserts that a value equals another value.',
' assert(' + resultsVar + ' ~= nil, ' +
'"Orphaned assert equals: " .. message)',
' if type(actual) == "table" and type(expected) == "table" then',
' local lists_match = #actual == #expected',
' if lists_match then',
' for i, v1 in ipairs(actual) do',
' local v2 = expected[i]',
' if type(v1) == "number" and type(v2) == "number" then',
' if math.abs(v1 - v2) > 1e-9 then',
' lists_match = false',
' end',
' elseif v1 ~= v2 then',
' lists_match = false',
' end',
' end',
' end',
' if lists_match then',
' table.insert(' + resultsVar +
', {success=true, log="OK", title=message})',
' return',
' else',
' -- produce the non-matching strings for a human-readable error',
' expected = "{" .. table.concat(expected, ", ") .. "}"',
' actual = "{" .. table.concat(actual, ", ") .. "}"',
' end',
' end',
' if actual == expected then',
' table.insert(' + resultsVar +
', {success=true, log="OK", title=message})',
' else',
' table.insert(' + resultsVar + ', {success=false, ' +
'log=string.format("Expected: %s\\nActual: %s"' +
', expected, actual), title=message})',
' end',
'end']);
return functionName;
};
Blockly.Lua['unittest_assertequals'] = function(block) {
// Asserts that a value equals another value.
var message = Blockly.Lua.quote_(block.getFieldValue('MESSAGE'));
var actual = Blockly.Lua.valueToCode(block, 'ACTUAL',
Blockly.Lua.ORDER_NONE) || 'nil';
var expected = Blockly.Lua.valueToCode(block, 'EXPECTED',
Blockly.Lua.ORDER_NONE) || 'nil';
return Blockly.Lua['unittest_main'].defineAssert_() +
'(' + actual + ', ' + expected + ', ' + message + ')\n';
};
Blockly.Lua['unittest_assertvalue'] = function(block) {
// Asserts that a value is true, false, or null.
var message = Blockly.Lua.quote_(block.getFieldValue('MESSAGE'));
var actual = Blockly.Lua.valueToCode(block, 'ACTUAL',
Blockly.Lua.ORDER_NONE) || 'nil';
var expected = block.getFieldValue('EXPECTED');
if (expected == 'TRUE') {
expected = 'true';
} else if (expected == 'FALSE') {
expected = 'false';
} else if (expected == 'NULL') {
expected = 'nil';
}
return Blockly.Lua.unittest_main.defineAssert_() +
'(' + actual + ', ' + expected + ', ' + message + ')\n';
};
Blockly.Lua['unittest_fail'] = function(block) {
// Always assert an error.
var resultsVar = Blockly.Lua.variableDB_.getName('unittestResults',
Blockly.Variables.NAME_TYPE);
var message = Blockly.Lua.quote_(block.getFieldValue('MESSAGE'));
var functionName = Blockly.Lua.provideFunction_(
'unittest_fail',
['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(message)',
' -- Always assert an error.',
' assert(' + resultsVar +
' ~= nil, "Orphaned assert fail: " .. message)',
' table.insert(' + resultsVar +
', {success=false, log="Fail.", title=message})',
'end']);
return functionName + '(' + message + ')\n';
};

View file

@ -51,4 +51,8 @@
</block>
</statement>
</block>
<block type="variables_get" x="300" y="100">
<field name="VAR">naked</field>
<comment pinned="true" h="80" w="160">Intentionally non-connected variable.</comment>
</block>
</xml>

View file

@ -39,23 +39,23 @@ function verify_DB_(msg, expected, db) {
function test_DB_addConnection() {
var db = new Blockly.ConnectionDB();
var o2 = {y_: 2, sourceBlock_: {}};
db.addConnection_(o2);
db.addConnection(o2);
verify_DB_('Adding connection #2', [o2], db);
var o4 = {y_: 4, sourceBlock_: {}};
db.addConnection_(o4);
db.addConnection(o4);
verify_DB_('Adding connection #4', [o2, o4], db);
var o1 = {y_: 1, sourceBlock_: {}};
db.addConnection_(o1);
db.addConnection(o1);
verify_DB_('Adding connection #1', [o1, o2, o4], db);
var o3a = {y_: 3, sourceBlock_: {}};
db.addConnection_(o3a);
db.addConnection(o3a);
verify_DB_('Adding connection #3a', [o1, o2, o3a, o4], db);
var o3b = {y_: 3, sourceBlock_: {}};
db.addConnection_(o3b);
db.addConnection(o3b);
verify_DB_('Adding connection #3b', [o1, o2, o3b, o3a, o4], db);
}
@ -67,12 +67,12 @@ function test_DB_removeConnection() {
var o3b = {y_: 3, sourceBlock_: {}};
var o3c = {y_: 3, sourceBlock_: {}};
var o4 = {y_: 4, sourceBlock_: {}};
db.addConnection_(o1);
db.addConnection_(o2);
db.addConnection_(o3c);
db.addConnection_(o3b);
db.addConnection_(o3a);
db.addConnection_(o4);
db.addConnection(o1);
db.addConnection(o2);
db.addConnection(o3c);
db.addConnection(o3b);
db.addConnection(o3a);
db.addConnection(o4);
verify_DB_('Adding connections 1-4', [o1, o2, o3a, o3b, o3c, o4], db);
db.removeConnection_(o2);
@ -93,3 +93,190 @@ function test_DB_removeConnection() {
db.removeConnection_(o3b);
verify_DB_('Removing connection #3b', [], db);
}
function test_DB_getNeighbours() {
var db = new Blockly.ConnectionDB();
// Search an empty list.
assertEquals(helper_getNeighbours(db,
10 /* x */, 10 /* y */, 100 /* radius */).length, 0);
// Set up some connections.
for (var i = 0; i < 10; i++) {
db.addConnection(helper_createConnection(0, i,
Blockly.PREVIOUS_STATEMENT));
}
// Test block belongs at beginning.
var result = helper_getNeighbours(db, 0, 0, 4);
assertEquals(5, result.length);
for (i = 0; i < result.length; i++) {
assertNotEquals(result.indexOf(db[i]), -1); // contains
}
// Test block belongs at middle.
result = helper_getNeighbours(db, 0, 4, 2);
assertEquals(5, result.length);
for (i = 0; i < result.length; i++) {
assertNotEquals(result.indexOf(db[i + 2]), -1); // contains
}
// Test block belongs at end.
result = helper_getNeighbours(db, 0, 9, 4);
assertEquals(5, result.length);
for (i = 0; i < result.length; i++) {
assertNotEquals(result.indexOf(db[i + 5]), -1); // contains
}
// Test block has no neighbours due to being out of range in the x direction.
result = helper_getNeighbours(db, 10, 9, 4);
assertEquals(result.length, 0);
// Test block has no neighbours due to being out of range in the y direction.
result = helper_getNeighbours(db, 0, 19, 4);
assertEquals(result.length, 0);
// Test block has no neighbours due to being out of range diagonally.
result = helper_getNeighbours(db, -2, -2, 2);
assertEquals(result.length, 0);
}
function test_DB_findPositionForConnection() {
var db = new Blockly.ConnectionDB();
db.addConnection(helper_createConnection(0, 0, Blockly.PREVIOUS_STATEMENT));
db.addConnection(helper_createConnection(0, 1, Blockly.PREVIOUS_STATEMENT));
db.addConnection(helper_createConnection(0, 2, Blockly.PREVIOUS_STATEMENT));
db.addConnection(helper_createConnection(0, 4, Blockly.PREVIOUS_STATEMENT));
db.addConnection(helper_createConnection(0, 5, Blockly.PREVIOUS_STATEMENT));
assertEquals(5, db.length);
var conn = helper_createConnection(0, 3, Blockly.PREVIOUS_STATEMENT);
assertEquals(3, db.findPositionForConnection_(conn));
}
function test_DB_findConnection() {
var db = new Blockly.ConnectionDB();
for (var i = 0; i < 10; i++) {
db.addConnection(helper_createConnection(i, 0,
Blockly.PREVIOUS_STATEMENT));
db.addConnection(helper_createConnection(0, i,
Blockly.PREVIOUS_STATEMENT));
}
var conn = helper_createConnection(3, 3, Blockly.PREVIOUS_STATEMENT);
db.addConnection(conn);
assertEquals(conn, db[db.findConnection(conn)]);
conn = helper_createConnection(3, 3, Blockly.PREVIOUS_STATEMENT);
assertEquals(-1, db.findConnection(conn));
}
function test_DB_ordering() {
var db = new Blockly.ConnectionDB();
for (var i = 0; i < 10; i++) {
db.addConnection(helper_createConnection(0, 9 - i,
Blockly.PREVIOUS_STATEMENT));
}
for (i = 0; i < 10; i++) {
assertEquals(i, db[i].y_);
}
// quasi-random
var xCoords = [-29, -47, -77, 2, 43, 34, -59, -52, -90, -36, -91, 38, 87, -20,
60, 4, -57, 65, -37, -81, 57, 58, -96, 1, 67, -79, 34, 93, -90, -99, -62,
4, 11, -36, -51, -72, 3, -50, -24, -45, -92, -38, 37, 24, -47, -73, 79,
-20, 99, 43, -10, -87, 19, 35, -62, -36, 49, 86, -24, -47, -89, 33, -44,
25, -73, -91, 85, 6, 0, 89, -94, 36, -35, 84, -9, 96, -21, 52, 10, -95, 7,
-67, -70, 62, 9, -40, -95, -9, -94, 55, 57, -96, 55, 8, -48, -57, -87, 81,
23, 65];
var yCoords = [-81, 82, 5, 47, 30, 57, -12, 28, 38, 92, -25, -20, 23, -51, 73,
-90, 8, 28, -51, -15, 81, -60, -6, -16, 77, -62, -42, -24, 35, 95, -46,
-7, 61, -16, 14, 91, 57, -38, 27, -39, 92, 47, -98, 11, -33, -72, 64, 38,
-64, -88, -35, -59, -76, -94, 45, -25, -100, -95, 63, -97, 45, 98, 99, 34,
27, 52, -18, -45, 66, -32, -38, 70, -73, -23, 5, -2, -13, -9, 48, 74, -97,
-11, 35, -79, -16, -77, 83, -57, -53, 35, -44, 100, -27, -15, 5, 39, 33,
-19, -20, -95];
for (i = 0; i < xCoords.length; i++) {
db.addConnection(helper_createConnection(xCoords[i], yCoords[i],
Blockly.PREVIOUS_STATEMENT));
}
for (i = 1; i < xCoords.length; i++) {
assertTrue(db[i].y_ >= db[i - 1].y_);
}
}
function test_SearchForClosest() {
var db = new Blockly.ConnectionDB();
var sharedWorkspace = {id: "Shared workspace"};
// Search an empty list.
assertEquals(null, helper_searchDB(db, 10 /* x */, 10 /* y */,
100 /* radius */));
db.addConnection(helper_createConnection(100, 0, Blockly.PREVIOUS_STATEMENT,
sharedWorkspace));
assertEquals(null, helper_searchDB(db, 0, 0, 5, sharedWorkspace));
db = new Blockly.ConnectionDB();
for (var i = 0; i < 10; i++) {
var tempConn = helper_createConnection(0, i, Blockly.PREVIOUS_STATEMENT,
sharedWorkspace);
tempConn.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
db.addConnection(tempConn);
}
// Should be at 0, 9.
var last = db[db.length - 1];
// Correct connection is last in db; many connections in radius.
assertEquals(last, helper_searchDB(db, 0, 10, 15, sharedWorkspace));
// Nothing nearby.
assertEquals(null, helper_searchDB(db, 100, 100, 3, sharedWorkspace));
// First in db, exact match.
assertEquals(db[0], helper_searchDB(db, 0, 0, 0, sharedWorkspace));
tempConn = helper_createConnection(6, 6, Blockly.PREVIOUS_STATEMENT,
sharedWorkspace);
tempConn.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
db.addConnection(tempConn);
tempConn = helper_createConnection(5, 5, Blockly.PREVIOUS_STATEMENT,
sharedWorkspace);
tempConn.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
db.addConnection(tempConn);
var result = helper_searchDB(db, 4, 6, 3, sharedWorkspace);
assertEquals(5, result.x_);
assertEquals(5, result.y_);
}
function helper_getNeighbours(db, x, y, radius) {
return db.getNeighbours(helper_createConnection(x, y, Blockly.NEXT_STATEMENT),
radius);
}
function helper_searchDB(db, x, y, radius, shared_workspace) {
var tempConn = helper_createConnection(x, y,
Blockly.NEXT_STATEMENT, shared_workspace);
tempConn.sourceBlock_ = helper_makeSourceBlock(shared_workspace);
return db.searchForClosest(tempConn, radius, 0, 0);
}
function helper_makeSourceBlock(sharedWorkspace) {
return {workspace: sharedWorkspace,
parentBlock_: null,
getParent: function() { return null; },
movable_: true,
isMovable: function() { return true; },
isShadow: function() { return false; }
};
}
function helper_createConnection(x, y, type, opt_shared_workspace) {
var workspace = opt_shared_workspace ? opt_shared_workspace : {};
var conn = new Blockly.Connection({workspace: workspace}, type);
conn.x_ = x;
conn.y_ = y;
return conn;
}

View file

@ -33,10 +33,14 @@ var dummyWorkspace;
function connectionTest_setUp() {
dummyWorkspace = {};
input = new Blockly.Connection({workspace: dummyWorkspace}, Blockly.INPUT_VALUE);
output = new Blockly.Connection({workspace: dummyWorkspace}, Blockly.OUTPUT_VALUE);
previous = new Blockly.Connection({workspace: dummyWorkspace}, Blockly.PREVIOUS_STATEMENT);
next = new Blockly.Connection({workspace: dummyWorkspace}, Blockly.NEXT_STATEMENT);
input = new Blockly.Connection({workspace: dummyWorkspace},
Blockly.INPUT_VALUE);
output = new Blockly.Connection({workspace: dummyWorkspace},
Blockly.OUTPUT_VALUE);
previous = new Blockly.Connection({workspace: dummyWorkspace},
Blockly.PREVIOUS_STATEMENT);
next = new Blockly.Connection({workspace: dummyWorkspace},
Blockly.NEXT_STATEMENT);
}
function connectionTest_tearDown() {
@ -48,13 +52,14 @@ function connectionTest_tearDown() {
}
/**
* These tests check that the reasons for failures to connect are consistent (internal view of
* error states).
* These tests check that the reasons for failures to connect are consistent
* (internal view of error states).
*/
function testCanConnectWithReason_TargetNull() {
connectionTest_setUp();
assertEquals(Blockly.Connection.REASON_TARGET_NULL, input.canConnectWithReason_(null));
assertEquals(Blockly.Connection.REASON_TARGET_NULL,
input.canConnectWithReason_(null));
connectionTest_tearDown();
}
@ -62,9 +67,11 @@ function testCanConnectWithReason_TargetNull() {
function testCanConnectWithReason_Disconnect() {
connectionTest_setUp();
var tempConnection = new Blockly.Connection({workspace: dummyWorkspace}, Blockly.OUTPUT_VALUE);
var tempConnection = new Blockly.Connection({workspace: dummyWorkspace},
Blockly.OUTPUT_VALUE);
Blockly.Connection.connectReciprocally(input, tempConnection);
assertEquals(Blockly.Connection.REASON_MUST_DISCONNECT, input.canConnectWithReason_(output));
assertEquals(Blockly.Connection.REASON_MUST_DISCONNECT,
input.canConnectWithReason_(output));
connectionTest_tearDown();
}
@ -73,9 +80,11 @@ function testCanConnectWithReason_DifferentWorkspaces() {
connectionTest_setUp();
input = new Blockly.Connection({workspace: {}}, Blockly.INPUT_VALUE);
output = new Blockly.Connection({workspace: dummyWorkspace}, Blockly.OUTPUT_VALUE);
output = new Blockly.Connection({workspace: dummyWorkspace},
Blockly.OUTPUT_VALUE);
assertEquals(Blockly.Connection.REASON_DIFFERENT_WORKSPACES, input.canConnectWithReason_(output));
assertEquals(Blockly.Connection.REASON_DIFFERENT_WORKSPACES,
input.canConnectWithReason_(output));
connectionTest_tearDown();
}
@ -86,7 +95,8 @@ function testCanConnectWithReason_Self() {
var block = {type_: "test block"};
input.sourceBlock_ = block;
assertEquals(Blockly.Connection.REASON_SELF_CONNECTION, input.canConnectWithReason_(input));
assertEquals(Blockly.Connection.REASON_SELF_CONNECTION,
input.canConnectWithReason_(input));
connectionTest_tearDown();
}
@ -94,17 +104,25 @@ function testCanConnectWithReason_Self() {
function testCanConnectWithReason_Type() {
connectionTest_setUp();
assertEquals(Blockly.Connection.REASON_WRONG_TYPE, input.canConnectWithReason_(previous));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE, input.canConnectWithReason_(next));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
input.canConnectWithReason_(previous));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
input.canConnectWithReason_(next));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE, output.canConnectWithReason_(previous));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE, output.canConnectWithReason_(next));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
output.canConnectWithReason_(previous));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
output.canConnectWithReason_(next));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE, previous.canConnectWithReason_(input));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE, previous.canConnectWithReason_(output));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
previous.canConnectWithReason_(input));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
previous.canConnectWithReason_(output));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE, next.canConnectWithReason_(input));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE, next.canConnectWithReason_(output));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
next.canConnectWithReason_(input));
assertEquals(Blockly.Connection.REASON_WRONG_TYPE,
next.canConnectWithReason_(output));
connectionTest_tearDown();
}
@ -112,17 +130,21 @@ function testCanConnectWithReason_Type() {
function testCanConnectWithReason_CanConnect() {
connectionTest_setUp();
assertEquals(Blockly.Connection.CAN_CONNECT, previous.canConnectWithReason_(next));
assertEquals(Blockly.Connection.CAN_CONNECT, next.canConnectWithReason_(previous));
assertEquals(Blockly.Connection.CAN_CONNECT, input.canConnectWithReason_(output));
assertEquals(Blockly.Connection.CAN_CONNECT, output.canConnectWithReason_(input));
assertEquals(Blockly.Connection.CAN_CONNECT,
previous.canConnectWithReason_(next));
assertEquals(Blockly.Connection.CAN_CONNECT,
next.canConnectWithReason_(previous));
assertEquals(Blockly.Connection.CAN_CONNECT,
input.canConnectWithReason_(output));
assertEquals(Blockly.Connection.CAN_CONNECT,
output.canConnectWithReason_(input));
connectionTest_tearDown();
}
/**
* The next set of tests checks that exceptions are being thrown at the correct times (external
* view of errors).
* The next set of tests checks that exceptions are being thrown at the correct
* times (external view of errors).
*/
function testCheckConnection_Self() {
connectionTest_setUp();
@ -222,6 +244,40 @@ function testCheckConnection_TypeNextOutput() {
connectionTest_tearDown();
}
function test_isConnectionAllowed() {
var sharedWorkspace = {};
// Two connections of opposite types near each other.
var one = helper_createConnection(5 /* x */, 10 /* y */,
Blockly.INPUT_VALUE);
one.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
var two = helper_createConnection(10 /* x */, 15 /* y */,
Blockly.OUTPUT_VALUE);
two.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
assertTrue(one.isConnectionAllowed(two, 20.0));
// Move connections farther apart.
two.x_ = 100;
two.y_ = 100;
assertFalse(one.isConnectionAllowed(two, 20.0));
// Don't offer to connect an already connected left (male) value plug to
// an available right (female) value plug.
var three = helper_createConnection(0, 0, Blockly.OUTPUT_VALUE);
three.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
assertTrue(one.isConnectionAllowed(three, 20.0));
var four = helper_createConnection(0, 0, Blockly.INPUT_VALUE);
four.sourceBlock_ = helper_makeSourceBlock(sharedWorkspace);
Blockly.Connection.connectReciprocally(three, four);
assertFalse(one.isConnectionAllowed(three, 20.0));
// Don't connect two connections on the same block.
two.sourceBlock_ = one.sourceBlock_;
assertFalse(one.isConnectionAllowed(two, 1000.0));
}
function testCheckConnection_Okay() {
connectionTest_setUp();
previous.checkConnection_(next);

76
tests/jsunit/db_test.js Normal file
View file

@ -0,0 +1,76 @@
/**
* @license
* Blockly Tests
*
* Copyright 2016 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
function test_DB_getNeighbours() {
var db = new Blockly.ConnectionDB();
// Search an empty list.
assertEquals(helper_getNeighbours(db, 10 /* x */, 10 /* y */, 100 /* radius */).length, 0);
// Set up some connections.
for (var i = 0; i < 10; i++) {
db.addConnection_(helper_createConnection(0, i, Blockly.PREVIOUS_STATEMENT));
}
// Test block belongs at beginning
var result = helper_getNeighbours(db, 0, 0, 4);
assertEquals(5, result.length);
for (i = 0; i < result.length; i++) {
assertNotEquals(result.indexOf(db[i]), -1); // contains
}
// Test block belongs at middle
result = helper_getNeighbours(db, 0, 4, 2);
assertEquals(5, result.length);
for (i = 0; i < result.length; i++) {
assertNotEquals(result.indexOf(db[i + 2]), -1); // contains
}
// Test block belongs at end
result = helper_getNeighbours(db, 0, 9, 4);
assertEquals(5, result.length);
for (i = 0; i < result.length; i++) {
assertNotEquals(result.indexOf(db[i + 5]), -1); // contains
}
// Test block has no neighbours due to being out of range in the x direction
result = helper_getNeighbours(db, 10, 9, 4);
assertEquals(result.length, 0);
// Test block has no neighbours due to being out of range in the y direction
result = helper_getNeighbours(db, 0, 19, 4);
assertEquals(result.length, 0);
// Test block has no neighbours due to being out of range diagonally
result = helper_getNeighbours(db, -2, -2, 2);
assertEquals(result.length, 0);
}
function helper_getNeighbours(db, x, y, radius) {
return db.getNeighbours(helper_createConnection(x, y, Blockly.NEXT_STATEMENT), radius);
}
function helper_createConnection(x, y, type) {
var conn = new Blockly.Connection({workspace: {}}, type);
conn.x_ = x;
conn.y_ = y;
return conn;
}