summaryrefslogtreecommitdiff
path: root/blockly/core/procedures.js
diff options
context:
space:
mode:
authorDavid Barksdale <amatus@amatus.name>2016-09-10 17:58:46 -0500
committerDavid Barksdale <amatus@amatus.name>2016-09-10 18:32:35 -0500
commit475f9f3ac7688e58505690d420cafe6ae8bb8b5f (patch)
treeb1bf14b10751fe4e9e146ad7244ec86bb123893d /blockly/core/procedures.js
parent923561056ddb63ce82fd1ec2a5e249bbdae267bf (diff)
Merge blockly sub-tree
Diffstat (limited to 'blockly/core/procedures.js')
-rw-r--r--blockly/core/procedures.js287
1 files changed, 287 insertions, 0 deletions
diff --git a/blockly/core/procedures.js b/blockly/core/procedures.js
new file mode 100644
index 0000000..beb4a17
--- /dev/null
+++ b/blockly/core/procedures.js
@@ -0,0 +1,287 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 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 Utility functions for handling procedures.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Procedures');
+
+goog.require('Blockly.Blocks');
+goog.require('Blockly.Field');
+goog.require('Blockly.Names');
+goog.require('Blockly.Workspace');
+
+
+/**
+ * Category to separate procedure names from variables and generated functions.
+ */
+Blockly.Procedures.NAME_TYPE = 'PROCEDURE';
+
+/**
+ * Find all user-created procedure definitions in a workspace.
+ * @param {!Blockly.Workspace} root Root workspace.
+ * @return {!Array.<!Array.<!Array>>} Pair of arrays, the
+ * first contains procedures without return variables, the second with.
+ * Each procedure is defined by a three-element list of name, parameter
+ * list, and return value boolean.
+ */
+Blockly.Procedures.allProcedures = function(root) {
+ var blocks = root.getAllBlocks();
+ var proceduresReturn = [];
+ var proceduresNoReturn = [];
+ for (var i = 0; i < blocks.length; i++) {
+ if (blocks[i].getProcedureDef) {
+ var tuple = blocks[i].getProcedureDef();
+ if (tuple) {
+ if (tuple[2]) {
+ proceduresReturn.push(tuple);
+ } else {
+ proceduresNoReturn.push(tuple);
+ }
+ }
+ }
+ }
+ proceduresNoReturn.sort(Blockly.Procedures.procTupleComparator_);
+ proceduresReturn.sort(Blockly.Procedures.procTupleComparator_);
+ return [proceduresNoReturn, proceduresReturn];
+};
+
+/**
+ * Comparison function for case-insensitive sorting of the first element of
+ * a tuple.
+ * @param {!Array} ta First tuple.
+ * @param {!Array} tb Second tuple.
+ * @return {number} -1, 0, or 1 to signify greater than, equality, or less than.
+ * @private
+ */
+Blockly.Procedures.procTupleComparator_ = function(ta, tb) {
+ return ta[0].toLowerCase().localeCompare(tb[0].toLowerCase());
+};
+
+/**
+ * Ensure two identically-named procedures don't exist.
+ * @param {string} name Proposed procedure name.
+ * @param {!Blockly.Block} block Block to disambiguate.
+ * @return {string} Non-colliding name.
+ */
+Blockly.Procedures.findLegalName = function(name, block) {
+ if (block.isInFlyout) {
+ // Flyouts can have multiple procedures called 'do something'.
+ return name;
+ }
+ while (!Blockly.Procedures.isLegalName_(name, block.workspace, block)) {
+ // Collision with another procedure.
+ var r = name.match(/^(.*?)(\d+)$/);
+ if (!r) {
+ name += '2';
+ } else {
+ name = r[1] + (parseInt(r[2], 10) + 1);
+ }
+ }
+ return name;
+};
+
+/**
+ * Does this procedure have a legal name? Illegal names include names of
+ * procedures already defined.
+ * @param {string} name The questionable name.
+ * @param {!Blockly.Workspace} workspace The workspace to scan for collisions.
+ * @param {Blockly.Block=} opt_exclude Optional block to exclude from
+ * comparisons (one doesn't want to collide with oneself).
+ * @return {boolean} True if the name is legal.
+ * @private
+ */
+Blockly.Procedures.isLegalName_ = function(name, workspace, opt_exclude) {
+ var blocks = workspace.getAllBlocks();
+ // Iterate through every block and check the name.
+ for (var i = 0; i < blocks.length; i++) {
+ if (blocks[i] == opt_exclude) {
+ continue;
+ }
+ if (blocks[i].getProcedureDef) {
+ var procName = blocks[i].getProcedureDef();
+ if (Blockly.Names.equals(procName[0], name)) {
+ return false;
+ }
+ }
+ }
+ return true;
+};
+
+/**
+ * Rename a procedure. Called by the editable field.
+ * @param {string} name The proposed new name.
+ * @return {string} The accepted name.
+ * @this {!Blockly.Field}
+ */
+Blockly.Procedures.rename = function(name) {
+ // Strip leading and trailing whitespace. Beyond this, all names are legal.
+ name = name.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
+
+ // Ensure two identically-named procedures don't exist.
+ var legalName = Blockly.Procedures.findLegalName(name, this.sourceBlock_);
+ var oldName = this.text_;
+ if (oldName != name && oldName != legalName) {
+ // Rename any callers.
+ var blocks = this.sourceBlock_.workspace.getAllBlocks();
+ for (var i = 0; i < blocks.length; i++) {
+ if (blocks[i].renameProcedure) {
+ blocks[i].renameProcedure(oldName, legalName);
+ }
+ }
+ }
+ return legalName;
+};
+
+/**
+ * Construct the blocks required by the flyout for the procedure category.
+ * @param {!Blockly.Workspace} workspace The workspace contianing procedures.
+ * @return {!Array.<!Element>} Array of XML block elements.
+ */
+Blockly.Procedures.flyoutCategory = function(workspace) {
+ var xmlList = [];
+ if (Blockly.Blocks['procedures_defnoreturn']) {
+ // <block type="procedures_defnoreturn" gap="16"></block>
+ var block = goog.dom.createDom('block');
+ block.setAttribute('type', 'procedures_defnoreturn');
+ block.setAttribute('gap', 16);
+ xmlList.push(block);
+ }
+ if (Blockly.Blocks['procedures_defreturn']) {
+ // <block type="procedures_defreturn" gap="16"></block>
+ var block = goog.dom.createDom('block');
+ block.setAttribute('type', 'procedures_defreturn');
+ block.setAttribute('gap', 16);
+ xmlList.push(block);
+ }
+ if (Blockly.Blocks['procedures_ifreturn']) {
+ // <block type="procedures_ifreturn" gap="16"></block>
+ var block = goog.dom.createDom('block');
+ block.setAttribute('type', 'procedures_ifreturn');
+ block.setAttribute('gap', 16);
+ xmlList.push(block);
+ }
+ if (xmlList.length) {
+ // Add slightly larger gap between system blocks and user calls.
+ xmlList[xmlList.length - 1].setAttribute('gap', 24);
+ }
+
+ function populateProcedures(procedureList, templateName) {
+ for (var i = 0; i < procedureList.length; i++) {
+ var name = procedureList[i][0];
+ var args = procedureList[i][1];
+ // <block type="procedures_callnoreturn" gap="16">
+ // <mutation name="do something">
+ // <arg name="x"></arg>
+ // </mutation>
+ // </block>
+ var block = goog.dom.createDom('block');
+ block.setAttribute('type', templateName);
+ block.setAttribute('gap', 16);
+ var mutation = goog.dom.createDom('mutation');
+ mutation.setAttribute('name', name);
+ block.appendChild(mutation);
+ for (var j = 0; j < args.length; j++) {
+ var arg = goog.dom.createDom('arg');
+ arg.setAttribute('name', args[j]);
+ mutation.appendChild(arg);
+ }
+ xmlList.push(block);
+ }
+ }
+
+ var tuple = Blockly.Procedures.allProcedures(workspace);
+ populateProcedures(tuple[0], 'procedures_callnoreturn');
+ populateProcedures(tuple[1], 'procedures_callreturn');
+ return xmlList;
+};
+
+/**
+ * Find all the callers of a named procedure.
+ * @param {string} name Name of procedure.
+ * @param {!Blockly.Workspace} workspace The workspace to find callers in.
+ * @return {!Array.<!Blockly.Block>} Array of caller blocks.
+ */
+Blockly.Procedures.getCallers = function(name, workspace) {
+ var callers = [];
+ var blocks = workspace.getAllBlocks();
+ // Iterate through every block and check the name.
+ for (var i = 0; i < blocks.length; i++) {
+ if (blocks[i].getProcedureCall) {
+ var procName = blocks[i].getProcedureCall();
+ // Procedure name may be null if the block is only half-built.
+ if (procName && Blockly.Names.equals(procName, name)) {
+ callers.push(blocks[i]);
+ }
+ }
+ }
+ return callers;
+};
+
+/**
+ * When a procedure definition changes its parameters, find and edit all its
+ * callers.
+ * @param {!Blockly.Block} defBlock Procedure definition block.
+ */
+Blockly.Procedures.mutateCallers = function(defBlock) {
+ var oldRecordUndo = Blockly.Events.recordUndo;
+ var name = defBlock.getProcedureDef()[0];
+ var xmlElement = defBlock.mutationToDom(true);
+ var callers = Blockly.Procedures.getCallers(name, defBlock.workspace);
+ for (var i = 0, caller; caller = callers[i]; i++) {
+ var oldMutationDom = caller.mutationToDom();
+ var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom);
+ caller.domToMutation(xmlElement);
+ var newMutationDom = caller.mutationToDom();
+ var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom);
+ if (oldMutation != newMutation) {
+ // Fire a mutation on every caller block. But don't record this as an
+ // undo action since it is deterministically tied to the procedure's
+ // definition mutation.
+ Blockly.Events.recordUndo = false;
+ Blockly.Events.fire(new Blockly.Events.Change(
+ caller, 'mutation', null, oldMutation, newMutation));
+ Blockly.Events.recordUndo = oldRecordUndo;
+ }
+ }
+};
+
+/**
+ * Find the definition block for the named procedure.
+ * @param {string} name Name of procedure.
+ * @param {!Blockly.Workspace} workspace The workspace to search.
+ * @return {Blockly.Block} The procedure definition block, or null not found.
+ */
+Blockly.Procedures.getDefinition = function(name, workspace) {
+ // Assume that a procedure definition is a top block.
+ var blocks = workspace.getTopBlocks(false);
+ for (var i = 0; i < blocks.length; i++) {
+ if (blocks[i].getProcedureDef) {
+ var tuple = blocks[i].getProcedureDef();
+ if (tuple && Blockly.Names.equals(tuple[0], name)) {
+ return blocks[i];
+ }
+ }
+ }
+ return null;
+};