123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- /**
- * @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++) {
- block.setLineNumber(""+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);
- }
- // If this block isn't at the top, then we can store its position.
- if (x != 0) {
- var location = block.getRelativeToSurfaceXY();
- //code.push("#location:"+location.x+","+location.y);
- }
- 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)) {
- var id = block.id.replace(/\$/g, '$$$$'); // Issue 251.
- if (this.STATEMENT_PREFIX) {
- code = this.STATEMENT_PREFIX.replace(/%1/g, '\'' + 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) {
- id = id.replace(/\$/g, '$$$$'); // Issue 251.
- 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;
- };
- /**
- * 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 = ' ';
- /**
- * 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.
- // To avoid an infinite loop of replacements, change all indents to '\0'
- // character first, then replace them all with the indent.
- // We are assuming that no provided functions contain a literal null char.
- var oldCodeText;
- while (oldCodeText != codeText) {
- oldCodeText = codeText;
- codeText = codeText.replace(/^(( )*) /gm, '$1\0');
- }
- codeText = codeText.replace(/\0/g, this.INDENT);
- this.definitions_[desiredName] = codeText;
- }
- return this.functionNames_[desiredName];
- };
|