diff options
Diffstat (limited to 'blockly/core/generator.js')
-rw-r--r-- | blockly/core/generator.js | 369 |
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]; +}; |