2013-10-30 14:46:03 -07:00
|
|
|
/**
|
2014-01-28 03:00:09 -08:00
|
|
|
* @license
|
2013-10-30 14:46:03 -07:00
|
|
|
* Visual Blocks Editor
|
|
|
|
*
|
|
|
|
* Copyright 2012 Google Inc.
|
2014-10-07 13:09:55 -07:00
|
|
|
* https://developers.google.com/blockly/
|
2013-10-30 14:46:03 -07:00
|
|
|
*
|
|
|
|
* 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');
|
2015-07-23 13:09:06 -07:00
|
|
|
goog.require('goog.asserts');
|
2013-10-30 14:46:03 -07:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.RESERVED_WORDS_ = '';
|
2014-09-08 14:26:52 -07:00
|
|
|
this.FUNCTION_NAME_PLACEHOLDER_REGEXP_ =
|
|
|
|
new RegExp(this.FUNCTION_NAME_PLACEHOLDER_, 'g');
|
2013-10-30 14:46:03 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Category to separate generated function names from variables and procedures.
|
|
|
|
*/
|
|
|
|
Blockly.Generator.NAME_TYPE = 'generated_function';
|
|
|
|
|
2014-09-08 14:26:52 -07:00
|
|
|
/**
|
|
|
|
* 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'
|
2015-07-13 15:03:22 -07:00
|
|
|
* @type {?string}
|
2014-09-08 14:26:52 -07:00
|
|
|
*/
|
|
|
|
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'
|
2015-07-13 15:03:22 -07:00
|
|
|
* @type {?string}
|
2014-09-08 14:26:52 -07:00
|
|
|
*/
|
|
|
|
Blockly.Generator.prototype.STATEMENT_PREFIX = null;
|
|
|
|
|
2013-10-30 14:46:03 -07:00
|
|
|
/**
|
|
|
|
* Generate code for all blocks in the workspace to the specified language.
|
2015-04-28 13:51:25 -07:00
|
|
|
* @param {Blockly.Workspace} workspace Workspace to generate code from.
|
2013-10-30 14:46:03 -07:00
|
|
|
* @return {string} Generated code.
|
|
|
|
*/
|
2015-04-28 13:51:25 -07:00
|
|
|
Blockly.Generator.prototype.workspaceToCode = function(workspace) {
|
|
|
|
if (!workspace) {
|
|
|
|
// Backwards compatability from before there could be multiple workspaces.
|
|
|
|
console.warn('No workspace specified in workspaceToCode call. Guessing.');
|
|
|
|
workspace = Blockly.getMainWorkspace();
|
|
|
|
}
|
2013-10-30 14:46:03 -07:00
|
|
|
var code = [];
|
2014-12-23 11:22:02 -08:00
|
|
|
this.init(workspace);
|
|
|
|
var blocks = workspace.getTopBlocks(true);
|
2013-10-30 14:46:03 -07:00
|
|
|
for (var x = 0, block; block = blocks[x]; x++) {
|
|
|
|
var line = this.blockToCode(block);
|
2014-09-08 14:26:52 -07:00
|
|
|
if (goog.isArray(line)) {
|
2013-10-30 14:46:03 -07:00
|
|
|
// 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(.)/g, '\n' + prefix + '$1');
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 x = 0; x < blocks.length; x++) {
|
|
|
|
var comment = blocks[x].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.
|
2014-09-08 14:26:52 -07:00
|
|
|
return this.blockToCode(block.getNextBlock());
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
var func = this[block.type];
|
2015-07-23 13:09:06 -07:00
|
|
|
goog.asserts.assertFunction(func,
|
|
|
|
'Language "%s" does not know how to generate code for block type "%s".',
|
|
|
|
this.name_, block.type);
|
2013-10-30 14:46:03 -07:00
|
|
|
// 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);
|
2014-09-08 14:26:52 -07:00
|
|
|
if (goog.isArray(code)) {
|
2013-10-30 14:46:03 -07:00
|
|
|
// Value blocks return tuples of code and operator order.
|
|
|
|
return [this.scrub_(block, code[0]), code[1]];
|
2014-09-08 14:26:52 -07:00
|
|
|
} else if (goog.isString(code)) {
|
|
|
|
if (this.STATEMENT_PREFIX) {
|
|
|
|
code = this.STATEMENT_PREFIX.replace(/%1/g, '\'' + block.id + '\'') +
|
|
|
|
code;
|
|
|
|
}
|
2013-10-30 14:46:03 -07:00
|
|
|
return this.scrub_(block, code);
|
2014-09-08 14:26:52 -07:00
|
|
|
} else if (code === null) {
|
|
|
|
// Block has handled code generation itself.
|
|
|
|
return '';
|
|
|
|
} else {
|
2015-07-23 13:09:06 -07:00
|
|
|
goog.asserts.fail('Invalid code generated: %s', code);
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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} order 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, order) {
|
|
|
|
if (isNaN(order)) {
|
2015-07-23 13:09:06 -07:00
|
|
|
goog.asserts.fail('Expecting valid order from block "%s".', block.type);
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
|
|
|
var targetBlock = block.getInputTargetBlock(name);
|
|
|
|
if (!targetBlock) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
var tuple = this.blockToCode(targetBlock);
|
|
|
|
if (tuple === '') {
|
|
|
|
// Disabled block.
|
|
|
|
return '';
|
|
|
|
}
|
2015-07-23 13:09:06 -07:00
|
|
|
// 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);
|
2013-10-30 14:46:03 -07:00
|
|
|
var code = tuple[0];
|
|
|
|
var innerOrder = tuple[1];
|
|
|
|
if (isNaN(innerOrder)) {
|
2015-07-23 13:09:06 -07:00
|
|
|
goog.asserts.fail('Expecting valid order from value block "%s".',
|
|
|
|
targetBlock.type);
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
2015-03-03 21:42:29 -08:00
|
|
|
if (code && order <= innerOrder) {
|
|
|
|
if (order == innerOrder && (order == 0 || order == 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.
|
|
|
|
// Technically, this should be handled on a language-by-language basis.
|
|
|
|
// However all known (sane) languages use parentheses for grouping.
|
|
|
|
code = '(' + code + ')';
|
|
|
|
}
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
|
|
|
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);
|
2015-07-23 13:09:06 -07:00
|
|
|
// Value blocks must return code and order of operations info.
|
|
|
|
// Statement blocks must only return code.
|
|
|
|
goog.asserts.assertString(code,
|
2015-07-30 08:31:13 -07:00
|
|
|
'Expecting code from statement block "%s".',
|
|
|
|
targetBlock && targetBlock.type);
|
2013-10-30 14:46:03 -07:00
|
|
|
if (code) {
|
2014-09-08 14:26:52 -07:00
|
|
|
code = this.prefixLines(/** @type {string} */ (code), this.INDENT);
|
2013-10-30 14:46:03 -07:00
|
|
|
}
|
|
|
|
return code;
|
|
|
|
};
|
|
|
|
|
2014-09-08 14:26:52 -07:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The method of indenting. Defaults to two spaces, but language generators
|
|
|
|
* may override this to increase indent or change to tabs.
|
|
|
|
*/
|
|
|
|
Blockly.Generator.prototype.INDENT = ' ';
|
|
|
|
|
2013-10-30 14:46:03 -07:00
|
|
|
/**
|
|
|
|
* 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 + ',';
|
|
|
|
};
|
2014-01-11 03:00:02 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
2014-09-08 14:26:52 -07:00
|
|
|
* @private
|
2014-01-11 03:00:02 -08:00
|
|
|
*/
|
|
|
|
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).
|
2014-09-08 14:26:52 -07:00
|
|
|
* @param {!Array.<string>} code A list of Python statements.
|
2014-01-11 03:00:02 -08:00
|
|
|
* @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, this.NAME_TYPE);
|
|
|
|
this.functionNames_[desiredName] = functionName;
|
|
|
|
this.definitions_[desiredName] = code.join('\n').replace(
|
|
|
|
this.FUNCTION_NAME_PLACEHOLDER_REGEXP_, functionName);
|
|
|
|
}
|
|
|
|
return this.functionNames_[desiredName];
|
|
|
|
};
|