generator.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. /**
  2. * @license
  3. * Visual Blocks Editor
  4. *
  5. * Copyright 2012 Google Inc.
  6. * https://developers.google.com/blockly/
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. */
  20. /**
  21. * @fileoverview Utility functions for generating executable code from
  22. * Blockly code.
  23. * @author fraser@google.com (Neil Fraser)
  24. */
  25. 'use strict';
  26. goog.provide('Blockly.Generator');
  27. goog.require('Blockly.Block');
  28. goog.require('goog.asserts');
  29. /**
  30. * Class for a code generator that translates the blocks into a language.
  31. * @param {string} name Language name of this generator.
  32. * @constructor
  33. */
  34. Blockly.Generator = function(name) {
  35. this.name_ = name;
  36. this.FUNCTION_NAME_PLACEHOLDER_REGEXP_ =
  37. new RegExp(this.FUNCTION_NAME_PLACEHOLDER_, 'g');
  38. };
  39. /**
  40. * Category to separate generated function names from variables and procedures.
  41. */
  42. Blockly.Generator.NAME_TYPE = 'generated_function';
  43. /**
  44. * Arbitrary code to inject into locations that risk causing infinite loops.
  45. * Any instances of '%1' will be replaced by the block ID that failed.
  46. * E.g. ' checkTimeout(%1);\n'
  47. * @type {?string}
  48. */
  49. Blockly.Generator.prototype.INFINITE_LOOP_TRAP = null;
  50. /**
  51. * Arbitrary code to inject before every statement.
  52. * Any instances of '%1' will be replaced by the block ID of the statement.
  53. * E.g. 'highlight(%1);\n'
  54. * @type {?string}
  55. */
  56. Blockly.Generator.prototype.STATEMENT_PREFIX = null;
  57. /**
  58. * The method of indenting. Defaults to two spaces, but language generators
  59. * may override this to increase indent or change to tabs.
  60. * @type {string}
  61. */
  62. Blockly.Generator.prototype.INDENT = ' ';
  63. /**
  64. * Maximum length for a comment before wrapping. Does not account for
  65. * indenting level.
  66. * @type {number}
  67. */
  68. Blockly.Generator.prototype.COMMENT_WRAP = 60;
  69. /**
  70. * List of outer-inner pairings that do NOT require parentheses.
  71. * @type {!Array.<!Array.<number>>}
  72. */
  73. Blockly.Generator.prototype.ORDER_OVERRIDES = [];
  74. /**
  75. * Generate code for all blocks in the workspace to the specified language.
  76. * @param {Blockly.Workspace} workspace Workspace to generate code from.
  77. * @return {string} Generated code.
  78. */
  79. Blockly.Generator.prototype.workspaceToCode = function(workspace) {
  80. if (!workspace) {
  81. // Backwards compatibility from before there could be multiple workspaces.
  82. console.warn('No workspace specified in workspaceToCode call. Guessing.');
  83. workspace = Blockly.getMainWorkspace();
  84. }
  85. var code = [];
  86. this.init(workspace);
  87. var blocks = workspace.getTopBlocks(true);
  88. for (var x = 0, block; block = blocks[x]; x++) {
  89. var line = this.blockToCode(block);
  90. if (goog.isArray(line)) {
  91. // Value blocks return tuples of code and operator order.
  92. // Top-level blocks don't care about operator order.
  93. line = line[0];
  94. }
  95. if (line) {
  96. if (block.outputConnection && this.scrubNakedValue) {
  97. // This block is a naked value. Ask the language's code generator if
  98. // it wants to append a semicolon, or something.
  99. line = this.scrubNakedValue(line);
  100. }
  101. code.push(line);
  102. }
  103. }
  104. code = code.join('\n'); // Blank line between each section.
  105. code = this.finish(code);
  106. // Final scrubbing of whitespace.
  107. code = code.replace(/^\s+\n/, '');
  108. code = code.replace(/\n\s+$/, '\n');
  109. code = code.replace(/[ \t]+\n/g, '\n');
  110. return code;
  111. };
  112. // The following are some helpful functions which can be used by multiple
  113. // languages.
  114. /**
  115. * Prepend a common prefix onto each line of code.
  116. * @param {string} text The lines of code.
  117. * @param {string} prefix The common prefix.
  118. * @return {string} The prefixed lines of code.
  119. */
  120. Blockly.Generator.prototype.prefixLines = function(text, prefix) {
  121. return prefix + text.replace(/(?!\n$)\n/g, '\n' + prefix);
  122. };
  123. /**
  124. * Recursively spider a tree of blocks, returning all their comments.
  125. * @param {!Blockly.Block} block The block from which to start spidering.
  126. * @return {string} Concatenated list of comments.
  127. */
  128. Blockly.Generator.prototype.allNestedComments = function(block) {
  129. var comments = [];
  130. var blocks = block.getDescendants();
  131. for (var i = 0; i < blocks.length; i++) {
  132. var comment = blocks[i].getCommentText();
  133. if (comment) {
  134. comments.push(comment);
  135. }
  136. }
  137. // Append an empty string to create a trailing line break when joined.
  138. if (comments.length) {
  139. comments.push('');
  140. }
  141. return comments.join('\n');
  142. };
  143. /**
  144. * Generate code for the specified block (and attached blocks).
  145. * @param {Blockly.Block} block The block to generate code for.
  146. * @return {string|!Array} For statement blocks, the generated code.
  147. * For value blocks, an array containing the generated code and an
  148. * operator order value. Returns '' if block is null.
  149. */
  150. Blockly.Generator.prototype.blockToCode = function(block) {
  151. if (!block) {
  152. return '';
  153. }
  154. if (block.disabled) {
  155. // Skip past this block if it is disabled.
  156. return this.blockToCode(block.getNextBlock());
  157. }
  158. var func = this[block.type];
  159. goog.asserts.assertFunction(func,
  160. 'Language "%s" does not know how to generate code for block type "%s".',
  161. this.name_, block.type);
  162. // First argument to func.call is the value of 'this' in the generator.
  163. // Prior to 24 September 2013 'this' was the only way to access the block.
  164. // The current prefered method of accessing the block is through the second
  165. // argument to func.call, which becomes the first parameter to the generator.
  166. var code = func.call(block, block);
  167. if (goog.isArray(code)) {
  168. // Value blocks return tuples of code and operator order.
  169. goog.asserts.assert(block.outputConnection,
  170. 'Expecting string from statement block "%s".', block.type);
  171. return [this.scrub_(block, code[0]), code[1]];
  172. } else if (goog.isString(code)) {
  173. if (this.STATEMENT_PREFIX) {
  174. code = this.STATEMENT_PREFIX.replace(/%1/g, '\'' + block.id + '\'') +
  175. code;
  176. }
  177. return this.scrub_(block, code);
  178. } else if (code === null) {
  179. // Block has handled code generation itself.
  180. return '';
  181. } else {
  182. goog.asserts.fail('Invalid code generated: %s', code);
  183. }
  184. };
  185. /**
  186. * Generate code representing the specified value input.
  187. * @param {!Blockly.Block} block The block containing the input.
  188. * @param {string} name The name of the input.
  189. * @param {number} outerOrder The maximum binding strength (minimum order value)
  190. * of any operators adjacent to "block".
  191. * @return {string} Generated code or '' if no blocks are connected or the
  192. * specified input does not exist.
  193. */
  194. Blockly.Generator.prototype.valueToCode = function(block, name, outerOrder) {
  195. if (isNaN(outerOrder)) {
  196. goog.asserts.fail('Expecting valid order from block "%s".', block.type);
  197. }
  198. var targetBlock = block.getInputTargetBlock(name);
  199. if (!targetBlock) {
  200. return '';
  201. }
  202. var tuple = this.blockToCode(targetBlock);
  203. if (tuple === '') {
  204. // Disabled block.
  205. return '';
  206. }
  207. // Value blocks must return code and order of operations info.
  208. // Statement blocks must only return code.
  209. goog.asserts.assertArray(tuple, 'Expecting tuple from value block "%s".',
  210. targetBlock.type);
  211. var code = tuple[0];
  212. var innerOrder = tuple[1];
  213. if (isNaN(innerOrder)) {
  214. goog.asserts.fail('Expecting valid order from value block "%s".',
  215. targetBlock.type);
  216. }
  217. if (!code) {
  218. return '';
  219. }
  220. // Add parentheses if needed.
  221. var parensNeeded = false;
  222. var outerOrderClass = Math.floor(outerOrder);
  223. var innerOrderClass = Math.floor(innerOrder);
  224. if (outerOrderClass <= innerOrderClass) {
  225. if (outerOrderClass == innerOrderClass &&
  226. (outerOrderClass == 0 || outerOrderClass == 99)) {
  227. // Don't generate parens around NONE-NONE and ATOMIC-ATOMIC pairs.
  228. // 0 is the atomic order, 99 is the none order. No parentheses needed.
  229. // In all known languages multiple such code blocks are not order
  230. // sensitive. In fact in Python ('a' 'b') 'c' would fail.
  231. } else {
  232. // The operators outside this code are stonger than the operators
  233. // inside this code. To prevent the code from being pulled apart,
  234. // wrap the code in parentheses.
  235. parensNeeded = true;
  236. // Check for special exceptions.
  237. for (var i = 0; i < this.ORDER_OVERRIDES.length; i++) {
  238. if (this.ORDER_OVERRIDES[i][0] == outerOrder &&
  239. this.ORDER_OVERRIDES[i][1] == innerOrder) {
  240. parensNeeded = false;
  241. break;
  242. }
  243. }
  244. }
  245. }
  246. if (parensNeeded) {
  247. // Technically, this should be handled on a language-by-language basis.
  248. // However all known (sane) languages use parentheses for grouping.
  249. code = '(' + code + ')';
  250. }
  251. return code;
  252. };
  253. /**
  254. * Generate code representing the statement. Indent the code.
  255. * @param {!Blockly.Block} block The block containing the input.
  256. * @param {string} name The name of the input.
  257. * @return {string} Generated code or '' if no blocks are connected.
  258. */
  259. Blockly.Generator.prototype.statementToCode = function(block, name) {
  260. var targetBlock = block.getInputTargetBlock(name);
  261. var code = this.blockToCode(targetBlock);
  262. // Value blocks must return code and order of operations info.
  263. // Statement blocks must only return code.
  264. goog.asserts.assertString(code, 'Expecting code from statement block "%s".',
  265. targetBlock && targetBlock.type);
  266. if (code) {
  267. code = this.prefixLines(/** @type {string} */ (code), this.INDENT);
  268. }
  269. return code;
  270. };
  271. /**
  272. * Add an infinite loop trap to the contents of a loop.
  273. * If loop is empty, add a statment prefix for the loop block.
  274. * @param {string} branch Code for loop contents.
  275. * @param {string} id ID of enclosing block.
  276. * @return {string} Loop contents, with infinite loop trap added.
  277. */
  278. Blockly.Generator.prototype.addLoopTrap = function(branch, id) {
  279. if (this.INFINITE_LOOP_TRAP) {
  280. branch = this.INFINITE_LOOP_TRAP.replace(/%1/g, '\'' + id + '\'') + branch;
  281. }
  282. if (this.STATEMENT_PREFIX) {
  283. branch += this.prefixLines(this.STATEMENT_PREFIX.replace(/%1/g,
  284. '\'' + id + '\''), this.INDENT);
  285. }
  286. return branch;
  287. };
  288. /**
  289. * Comma-separated list of reserved words.
  290. * @type {string}
  291. * @private
  292. */
  293. Blockly.Generator.prototype.RESERVED_WORDS_ = '';
  294. /**
  295. * Add one or more words to the list of reserved words for this language.
  296. * @param {string} words Comma-separated list of words to add to the list.
  297. * No spaces. Duplicates are ok.
  298. */
  299. Blockly.Generator.prototype.addReservedWords = function(words) {
  300. this.RESERVED_WORDS_ += words + ',';
  301. };
  302. /**
  303. * This is used as a placeholder in functions defined using
  304. * Blockly.Generator.provideFunction_. It must not be legal code that could
  305. * legitimately appear in a function definition (or comment), and it must
  306. * not confuse the regular expression parser.
  307. * @type {string}
  308. * @private
  309. */
  310. Blockly.Generator.prototype.FUNCTION_NAME_PLACEHOLDER_ = '{leCUI8hutHZI4480Dc}';
  311. /**
  312. * Define a function to be included in the generated code.
  313. * The first time this is called with a given desiredName, the code is
  314. * saved and an actual name is generated. Subsequent calls with the
  315. * same desiredName have no effect but have the same return value.
  316. *
  317. * It is up to the caller to make sure the same desiredName is not
  318. * used for different code values.
  319. *
  320. * The code gets output when Blockly.Generator.finish() is called.
  321. *
  322. * @param {string} desiredName The desired name of the function (e.g., isPrime).
  323. * @param {!Array.<string>} code A list of statements. Use ' ' for indents.
  324. * @return {string} The actual name of the new function. This may differ
  325. * from desiredName if the former has already been taken by the user.
  326. * @private
  327. */
  328. Blockly.Generator.prototype.provideFunction_ = function(desiredName, code) {
  329. if (!this.definitions_[desiredName]) {
  330. var functionName = this.variableDB_.getDistinctName(desiredName,
  331. Blockly.Procedures.NAME_TYPE);
  332. this.functionNames_[desiredName] = functionName;
  333. var codeText = code.join('\n').replace(
  334. this.FUNCTION_NAME_PLACEHOLDER_REGEXP_, functionName);
  335. // Change all ' ' indents into the desired indent.
  336. var oldCodeText;
  337. while (oldCodeText != codeText) {
  338. oldCodeText = codeText;
  339. codeText = codeText.replace(/^(( )*) /gm, '$1' + this.INDENT);
  340. }
  341. this.definitions_[desiredName] = codeText;
  342. }
  343. return this.functionNames_[desiredName];
  344. };