summaryrefslogtreecommitdiff
path: root/blockly/core/generator.js
diff options
context:
space:
mode:
Diffstat (limited to 'blockly/core/generator.js')
-rw-r--r--blockly/core/generator.js369
1 files changed, 369 insertions, 0 deletions
diff --git a/blockly/core/generator.js b/blockly/core/generator.js
new file mode 100644
index 0000000..fecc355
--- /dev/null
+++ b/blockly/core/generator.js
@@ -0,0 +1,369 @@
+/**
+ * @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 generating executable code from
+ * Blockly code.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Generator');
+
+goog.require('Blockly.Block');
+goog.require('goog.asserts');
+
+
+/**
+ * Class for a code generator that translates the blocks into a language.
+ * @param {string} name Language name of this generator.
+ * @constructor
+ */
+Blockly.Generator = function(name) {
+ this.name_ = name;
+ this.FUNCTION_NAME_PLACEHOLDER_REGEXP_ =
+ new RegExp(this.FUNCTION_NAME_PLACEHOLDER_, 'g');
+};
+
+/**
+ * Category to separate generated function names from variables and procedures.
+ */
+Blockly.Generator.NAME_TYPE = 'generated_function';
+
+/**
+ * Arbitrary code to inject into locations that risk causing infinite loops.
+ * Any instances of '%1' will be replaced by the block ID that failed.
+ * E.g. ' checkTimeout(%1);\n'
+ * @type {?string}
+ */
+Blockly.Generator.prototype.INFINITE_LOOP_TRAP = null;
+
+/**
+ * Arbitrary code to inject before every statement.
+ * Any instances of '%1' will be replaced by the block ID of the statement.
+ * E.g. 'highlight(%1);\n'
+ * @type {?string}
+ */
+Blockly.Generator.prototype.STATEMENT_PREFIX = null;
+
+/**
+ * The method of indenting. Defaults to two spaces, but language generators
+ * may override this to increase indent or change to tabs.
+ * @type {string}
+ */
+Blockly.Generator.prototype.INDENT = ' ';
+
+/**
+ * Maximum length for a comment before wrapping. Does not account for
+ * indenting level.
+ * @type {number}
+ */
+Blockly.Generator.prototype.COMMENT_WRAP = 60;
+
+/**
+ * List of outer-inner pairings that do NOT require parentheses.
+ * @type {!Array.<!Array.<number>>}
+ */
+Blockly.Generator.prototype.ORDER_OVERRIDES = [];
+
+/**
+ * Generate code for all blocks in the workspace to the specified language.
+ * @param {Blockly.Workspace} workspace Workspace to generate code from.
+ * @return {string} Generated code.
+ */
+Blockly.Generator.prototype.workspaceToCode = function(workspace) {
+ if (!workspace) {
+ // Backwards compatibility from before there could be multiple workspaces.
+ console.warn('No workspace specified in workspaceToCode call. Guessing.');
+ workspace = Blockly.getMainWorkspace();
+ }
+ var code = [];
+ this.init(workspace);
+ var blocks = workspace.getTopBlocks(true);
+ for (var x = 0, block; block = blocks[x]; x++) {
+ var line = this.blockToCode(block);
+ if (goog.isArray(line)) {
+ // Value blocks return tuples of code and operator order.
+ // Top-level blocks don't care about operator order.
+ line = line[0];
+ }
+ if (line) {
+ if (block.outputConnection && this.scrubNakedValue) {
+ // This block is a naked value. Ask the language's code generator if
+ // it wants to append a semicolon, or something.
+ line = this.scrubNakedValue(line);
+ }
+ code.push(line);
+ }
+ }
+ code = code.join('\n'); // Blank line between each section.
+ code = this.finish(code);
+ // Final scrubbing of whitespace.
+ code = code.replace(/^\s+\n/, '');
+ code = code.replace(/\n\s+$/, '\n');
+ code = code.replace(/[ \t]+\n/g, '\n');
+ return code;
+};
+
+// The following are some helpful functions which can be used by multiple
+// languages.
+
+/**
+ * Prepend a common prefix onto each line of code.
+ * @param {string} text The lines of code.
+ * @param {string} prefix The common prefix.
+ * @return {string} The prefixed lines of code.
+ */
+Blockly.Generator.prototype.prefixLines = function(text, prefix) {
+ return prefix + text.replace(/(?!\n$)\n/g, '\n' + prefix);
+};
+
+/**
+ * Recursively spider a tree of blocks, returning all their comments.
+ * @param {!Blockly.Block} block The block from which to start spidering.
+ * @return {string} Concatenated list of comments.
+ */
+Blockly.Generator.prototype.allNestedComments = function(block) {
+ var comments = [];
+ var blocks = block.getDescendants();
+ for (var i = 0; i < blocks.length; i++) {
+ var comment = blocks[i].getCommentText();
+ if (comment) {
+ comments.push(comment);
+ }
+ }
+ // Append an empty string to create a trailing line break when joined.
+ if (comments.length) {
+ comments.push('');
+ }
+ return comments.join('\n');
+};
+
+/**
+ * Generate code for the specified block (and attached blocks).
+ * @param {Blockly.Block} block The block to generate code for.
+ * @return {string|!Array} For statement blocks, the generated code.
+ * For value blocks, an array containing the generated code and an
+ * operator order value. Returns '' if block is null.
+ */
+Blockly.Generator.prototype.blockToCode = function(block) {
+ if (!block) {
+ return '';
+ }
+ if (block.disabled) {
+ // Skip past this block if it is disabled.
+ return this.blockToCode(block.getNextBlock());
+ }
+
+ var func = this[block.type];
+ goog.asserts.assertFunction(func,
+ 'Language "%s" does not know how to generate code for block type "%s".',
+ this.name_, block.type);
+ // First argument to func.call is the value of 'this' in the generator.
+ // Prior to 24 September 2013 'this' was the only way to access the block.
+ // The current prefered method of accessing the block is through the second
+ // argument to func.call, which becomes the first parameter to the generator.
+ var code = func.call(block, block);
+ if (goog.isArray(code)) {
+ // Value blocks return tuples of code and operator order.
+ goog.asserts.assert(block.outputConnection,
+ 'Expecting string from statement block "%s".', block.type);
+ return [this.scrub_(block, code[0]), code[1]];
+ } else if (goog.isString(code)) {
+ if (this.STATEMENT_PREFIX) {
+ code = this.STATEMENT_PREFIX.replace(/%1/g, '\'' + block.id + '\'') +
+ code;
+ }
+ return this.scrub_(block, code);
+ } else if (code === null) {
+ // Block has handled code generation itself.
+ return '';
+ } else {
+ goog.asserts.fail('Invalid code generated: %s', code);
+ }
+};
+
+/**
+ * Generate code representing the specified value input.
+ * @param {!Blockly.Block} block The block containing the input.
+ * @param {string} name The name of the input.
+ * @param {number} outerOrder The maximum binding strength (minimum order value)
+ * of any operators adjacent to "block".
+ * @return {string} Generated code or '' if no blocks are connected or the
+ * specified input does not exist.
+ */
+Blockly.Generator.prototype.valueToCode = function(block, name, outerOrder) {
+ if (isNaN(outerOrder)) {
+ goog.asserts.fail('Expecting valid order from block "%s".', block.type);
+ }
+ var targetBlock = block.getInputTargetBlock(name);
+ if (!targetBlock) {
+ return '';
+ }
+ var tuple = this.blockToCode(targetBlock);
+ if (tuple === '') {
+ // Disabled block.
+ return '';
+ }
+ // Value blocks must return code and order of operations info.
+ // Statement blocks must only return code.
+ goog.asserts.assertArray(tuple, 'Expecting tuple from value block "%s".',
+ targetBlock.type);
+ var code = tuple[0];
+ var innerOrder = tuple[1];
+ if (isNaN(innerOrder)) {
+ goog.asserts.fail('Expecting valid order from value block "%s".',
+ targetBlock.type);
+ }
+ if (!code) {
+ return '';
+ }
+
+ // Add parentheses if needed.
+ var parensNeeded = false;
+ var outerOrderClass = Math.floor(outerOrder);
+ var innerOrderClass = Math.floor(innerOrder);
+ if (outerOrderClass <= innerOrderClass) {
+ if (outerOrderClass == innerOrderClass &&
+ (outerOrderClass == 0 || outerOrderClass == 99)) {
+ // Don't generate parens around NONE-NONE and ATOMIC-ATOMIC pairs.
+ // 0 is the atomic order, 99 is the none order. No parentheses needed.
+ // In all known languages multiple such code blocks are not order
+ // sensitive. In fact in Python ('a' 'b') 'c' would fail.
+ } else {
+ // The operators outside this code are stonger than the operators
+ // inside this code. To prevent the code from being pulled apart,
+ // wrap the code in parentheses.
+ parensNeeded = true;
+ // Check for special exceptions.
+ for (var i = 0; i < this.ORDER_OVERRIDES.length; i++) {
+ if (this.ORDER_OVERRIDES[i][0] == outerOrder &&
+ this.ORDER_OVERRIDES[i][1] == innerOrder) {
+ parensNeeded = false;
+ break;
+ }
+ }
+ }
+ }
+ if (parensNeeded) {
+ // Technically, this should be handled on a language-by-language basis.
+ // However all known (sane) languages use parentheses for grouping.
+ code = '(' + code + ')';
+ }
+ return code;
+};
+
+/**
+ * Generate code representing the statement. Indent the code.
+ * @param {!Blockly.Block} block The block containing the input.
+ * @param {string} name The name of the input.
+ * @return {string} Generated code or '' if no blocks are connected.
+ */
+Blockly.Generator.prototype.statementToCode = function(block, name) {
+ var targetBlock = block.getInputTargetBlock(name);
+ var code = this.blockToCode(targetBlock);
+ // Value blocks must return code and order of operations info.
+ // Statement blocks must only return code.
+ goog.asserts.assertString(code, 'Expecting code from statement block "%s".',
+ targetBlock && targetBlock.type);
+ if (code) {
+ code = this.prefixLines(/** @type {string} */ (code), this.INDENT);
+ }
+ return code;
+};
+
+/**
+ * Add an infinite loop trap to the contents of a loop.
+ * If loop is empty, add a statment prefix for the loop block.
+ * @param {string} branch Code for loop contents.
+ * @param {string} id ID of enclosing block.
+ * @return {string} Loop contents, with infinite loop trap added.
+ */
+Blockly.Generator.prototype.addLoopTrap = function(branch, id) {
+ if (this.INFINITE_LOOP_TRAP) {
+ branch = this.INFINITE_LOOP_TRAP.replace(/%1/g, '\'' + id + '\'') + branch;
+ }
+ if (this.STATEMENT_PREFIX) {
+ branch += this.prefixLines(this.STATEMENT_PREFIX.replace(/%1/g,
+ '\'' + id + '\''), this.INDENT);
+ }
+ return branch;
+};
+
+/**
+ * Comma-separated list of reserved words.
+ * @type {string}
+ * @private
+ */
+Blockly.Generator.prototype.RESERVED_WORDS_ = '';
+
+/**
+ * Add one or more words to the list of reserved words for this language.
+ * @param {string} words Comma-separated list of words to add to the list.
+ * No spaces. Duplicates are ok.
+ */
+Blockly.Generator.prototype.addReservedWords = function(words) {
+ this.RESERVED_WORDS_ += words + ',';
+};
+
+/**
+ * This is used as a placeholder in functions defined using
+ * Blockly.Generator.provideFunction_. It must not be legal code that could
+ * legitimately appear in a function definition (or comment), and it must
+ * not confuse the regular expression parser.
+ * @type {string}
+ * @private
+ */
+Blockly.Generator.prototype.FUNCTION_NAME_PLACEHOLDER_ = '{leCUI8hutHZI4480Dc}';
+
+/**
+ * Define a function to be included in the generated code.
+ * The first time this is called with a given desiredName, the code is
+ * saved and an actual name is generated. Subsequent calls with the
+ * same desiredName have no effect but have the same return value.
+ *
+ * It is up to the caller to make sure the same desiredName is not
+ * used for different code values.
+ *
+ * The code gets output when Blockly.Generator.finish() is called.
+ *
+ * @param {string} desiredName The desired name of the function (e.g., isPrime).
+ * @param {!Array.<string>} code A list of statements. Use ' ' for indents.
+ * @return {string} The actual name of the new function. This may differ
+ * from desiredName if the former has already been taken by the user.
+ * @private
+ */
+Blockly.Generator.prototype.provideFunction_ = function(desiredName, code) {
+ if (!this.definitions_[desiredName]) {
+ var functionName = this.variableDB_.getDistinctName(desiredName,
+ Blockly.Procedures.NAME_TYPE);
+ this.functionNames_[desiredName] = functionName;
+ var codeText = code.join('\n').replace(
+ this.FUNCTION_NAME_PLACEHOLDER_REGEXP_, functionName);
+ // Change all ' ' indents into the desired indent.
+ var oldCodeText;
+ while (oldCodeText != codeText) {
+ oldCodeText = codeText;
+ codeText = codeText.replace(/^(( )*) /gm, '$1' + this.INDENT);
+ }
+ this.definitions_[desiredName] = codeText;
+ }
+ return this.functionNames_[desiredName];
+};