generator.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  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. block.setLineNumber(""+x);
  90. var line = this.blockToCode(block);
  91. if (goog.isArray(line)) {
  92. // Value blocks return tuples of code and operator order.
  93. // Top-level blocks don't care about operator order.
  94. line = line[0];
  95. }
  96. if (line) {
  97. if (block.outputConnection && this.scrubNakedValue) {
  98. // This block is a naked value. Ask the language's code generator if
  99. // it wants to append a semicolon, or something.
  100. line = this.scrubNakedValue(line);
  101. }
  102. // If this block isn't at the top, then we can store its position.
  103. if (x != 0) {
  104. var location = block.getRelativeToSurfaceXY();
  105. //code.push("#location:"+location.x+","+location.y);
  106. }
  107. code.push(line);
  108. }
  109. }
  110. code = code.join('\n'); // Blank line between each section.
  111. code = this.finish(code);
  112. // Final scrubbing of whitespace.
  113. code = code.replace(/^\s+\n/, '');
  114. code = code.replace(/\n\s+$/, '\n');
  115. code = code.replace(/[ \t]+\n/g, '\n');
  116. return code;
  117. };
  118. // The following are some helpful functions which can be used by multiple
  119. // languages.
  120. /**
  121. * Prepend a common prefix onto each line of code.
  122. * @param {string} text The lines of code.
  123. * @param {string} prefix The common prefix.
  124. * @return {string} The prefixed lines of code.
  125. */
  126. Blockly.Generator.prototype.prefixLines = function(text, prefix) {
  127. return prefix + text.replace(/(?!\n$)\n/g, '\n' + prefix);
  128. };
  129. /**
  130. * Recursively spider a tree of blocks, returning all their comments.
  131. * @param {!Blockly.Block} block The block from which to start spidering.
  132. * @return {string} Concatenated list of comments.
  133. */
  134. Blockly.Generator.prototype.allNestedComments = function(block) {
  135. var comments = [];
  136. var blocks = block.getDescendants();
  137. for (var i = 0; i < blocks.length; i++) {
  138. var comment = blocks[i].getCommentText();
  139. if (comment) {
  140. comments.push(comment);
  141. }
  142. }
  143. // Append an empty string to create a trailing line break when joined.
  144. if (comments.length) {
  145. comments.push('');
  146. }
  147. return comments.join('\n');
  148. };
  149. /**
  150. * Generate code for the specified block (and attached blocks).
  151. * @param {Blockly.Block} block The block to generate code for.
  152. * @return {string|!Array} For statement blocks, the generated code.
  153. * For value blocks, an array containing the generated code and an
  154. * operator order value. Returns '' if block is null.
  155. */
  156. Blockly.Generator.prototype.blockToCode = function(block) {
  157. if (!block) {
  158. return '';
  159. }
  160. if (block.disabled) {
  161. // Skip past this block if it is disabled.
  162. return this.blockToCode(block.getNextBlock());
  163. }
  164. var func = this[block.type];
  165. goog.asserts.assertFunction(func,
  166. 'Language "%s" does not know how to generate code for block type "%s".',
  167. this.name_, block.type);
  168. // First argument to func.call is the value of 'this' in the generator.
  169. // Prior to 24 September 2013 'this' was the only way to access the block.
  170. // The current prefered method of accessing the block is through the second
  171. // argument to func.call, which becomes the first parameter to the generator.
  172. var code = func.call(block, block);
  173. if (goog.isArray(code)) {
  174. // Value blocks return tuples of code and operator order.
  175. goog.asserts.assert(block.outputConnection,
  176. 'Expecting string from statement block "%s".', block.type);
  177. return [this.scrub_(block, code[0]), code[1]];
  178. } else if (goog.isString(code)) {
  179. var id = block.id.replace(/\$/g, '$$$$'); // Issue 251.
  180. if (this.STATEMENT_PREFIX) {
  181. code = this.STATEMENT_PREFIX.replace(/%1/g, '\'' + id + '\'') +
  182. code;
  183. }
  184. return this.scrub_(block, code);
  185. } else if (code === null) {
  186. // Block has handled code generation itself.
  187. return '';
  188. } else {
  189. goog.asserts.fail('Invalid code generated: %s', code);
  190. }
  191. };
  192. /**
  193. * Generate code representing the specified value input.
  194. * @param {!Blockly.Block} block The block containing the input.
  195. * @param {string} name The name of the input.
  196. * @param {number} outerOrder The maximum binding strength (minimum order value)
  197. * of any operators adjacent to "block".
  198. * @return {string} Generated code or '' if no blocks are connected or the
  199. * specified input does not exist.
  200. */
  201. Blockly.Generator.prototype.valueToCode = function(block, name, outerOrder) {
  202. if (isNaN(outerOrder)) {
  203. goog.asserts.fail('Expecting valid order from block "%s".', block.type);
  204. }
  205. var targetBlock = block.getInputTargetBlock(name);
  206. if (!targetBlock) {
  207. return '';
  208. }
  209. var tuple = this.blockToCode(targetBlock);
  210. if (tuple === '') {
  211. // Disabled block.
  212. return '';
  213. }
  214. // Value blocks must return code and order of operations info.
  215. // Statement blocks must only return code.
  216. goog.asserts.assertArray(tuple, 'Expecting tuple from value block "%s".',
  217. targetBlock.type);
  218. var code = tuple[0];
  219. var innerOrder = tuple[1];
  220. if (isNaN(innerOrder)) {
  221. goog.asserts.fail('Expecting valid order from value block "%s".',
  222. targetBlock.type);
  223. }
  224. if (!code) {
  225. return '';
  226. }
  227. // Add parentheses if needed.
  228. var parensNeeded = false;
  229. var outerOrderClass = Math.floor(outerOrder);
  230. var innerOrderClass = Math.floor(innerOrder);
  231. if (outerOrderClass <= innerOrderClass) {
  232. if (outerOrderClass == innerOrderClass &&
  233. (outerOrderClass == 0 || outerOrderClass == 99)) {
  234. // Don't generate parens around NONE-NONE and ATOMIC-ATOMIC pairs.
  235. // 0 is the atomic order, 99 is the none order. No parentheses needed.
  236. // In all known languages multiple such code blocks are not order
  237. // sensitive. In fact in Python ('a' 'b') 'c' would fail.
  238. } else {
  239. // The operators outside this code are stonger than the operators
  240. // inside this code. To prevent the code from being pulled apart,
  241. // wrap the code in parentheses.
  242. parensNeeded = true;
  243. // Check for special exceptions.
  244. for (var i = 0; i < this.ORDER_OVERRIDES.length; i++) {
  245. if (this.ORDER_OVERRIDES[i][0] == outerOrder &&
  246. this.ORDER_OVERRIDES[i][1] == innerOrder) {
  247. parensNeeded = false;
  248. break;
  249. }
  250. }
  251. }
  252. }
  253. if (parensNeeded) {
  254. // Technically, this should be handled on a language-by-language basis.
  255. // However all known (sane) languages use parentheses for grouping.
  256. code = '(' + code + ')';
  257. }
  258. return code;
  259. };
  260. /**
  261. * Generate code representing the statement. Indent the code.
  262. * @param {!Blockly.Block} block The block containing the input.
  263. * @param {string} name The name of the input.
  264. * @return {string} Generated code or '' if no blocks are connected.
  265. */
  266. Blockly.Generator.prototype.statementToCode = function(block, name) {
  267. var targetBlock = block.getInputTargetBlock(name);
  268. var code = this.blockToCode(targetBlock);
  269. // Value blocks must return code and order of operations info.
  270. // Statement blocks must only return code.
  271. goog.asserts.assertString(code, 'Expecting code from statement block "%s".',
  272. targetBlock && targetBlock.type);
  273. if (code) {
  274. code = this.prefixLines(/** @type {string} */ (code), this.INDENT);
  275. }
  276. return code;
  277. };
  278. /**
  279. * Add an infinite loop trap to the contents of a loop.
  280. * If loop is empty, add a statment prefix for the loop block.
  281. * @param {string} branch Code for loop contents.
  282. * @param {string} id ID of enclosing block.
  283. * @return {string} Loop contents, with infinite loop trap added.
  284. */
  285. Blockly.Generator.prototype.addLoopTrap = function(branch, id) {
  286. id = id.replace(/\$/g, '$$$$'); // Issue 251.
  287. if (this.INFINITE_LOOP_TRAP) {
  288. branch = this.INFINITE_LOOP_TRAP.replace(/%1/g, '\'' + id + '\'') + branch;
  289. }
  290. if (this.STATEMENT_PREFIX) {
  291. branch += this.prefixLines(this.STATEMENT_PREFIX.replace(/%1/g,
  292. '\'' + id + '\''), this.INDENT);
  293. }
  294. return branch;
  295. };
  296. /**
  297. * The method of indenting. Defaults to two spaces, but language generators
  298. * may override this to increase indent or change to tabs.
  299. * @type {string}
  300. */
  301. Blockly.Generator.prototype.INDENT = ' ';
  302. /**
  303. * Comma-separated list of reserved words.
  304. * @type {string}
  305. * @private
  306. */
  307. Blockly.Generator.prototype.RESERVED_WORDS_ = '';
  308. /**
  309. * Add one or more words to the list of reserved words for this language.
  310. * @param {string} words Comma-separated list of words to add to the list.
  311. * No spaces. Duplicates are ok.
  312. */
  313. Blockly.Generator.prototype.addReservedWords = function(words) {
  314. this.RESERVED_WORDS_ += words + ',';
  315. };
  316. /**
  317. * This is used as a placeholder in functions defined using
  318. * Blockly.Generator.provideFunction_. It must not be legal code that could
  319. * legitimately appear in a function definition (or comment), and it must
  320. * not confuse the regular expression parser.
  321. * @type {string}
  322. * @private
  323. */
  324. Blockly.Generator.prototype.FUNCTION_NAME_PLACEHOLDER_ = '{leCUI8hutHZI4480Dc}';
  325. /**
  326. * Define a function to be included in the generated code.
  327. * The first time this is called with a given desiredName, the code is
  328. * saved and an actual name is generated. Subsequent calls with the
  329. * same desiredName have no effect but have the same return value.
  330. *
  331. * It is up to the caller to make sure the same desiredName is not
  332. * used for different code values.
  333. *
  334. * The code gets output when Blockly.Generator.finish() is called.
  335. *
  336. * @param {string} desiredName The desired name of the function (e.g., isPrime).
  337. * @param {!Array.<string>} code A list of statements. Use ' ' for indents.
  338. * @return {string} The actual name of the new function. This may differ
  339. * from desiredName if the former has already been taken by the user.
  340. * @private
  341. */
  342. Blockly.Generator.prototype.provideFunction_ = function(desiredName, code) {
  343. if (!this.definitions_[desiredName]) {
  344. var functionName = this.variableDB_.getDistinctName(desiredName,
  345. Blockly.Procedures.NAME_TYPE);
  346. this.functionNames_[desiredName] = functionName;
  347. var codeText = code.join('\n').replace(
  348. this.FUNCTION_NAME_PLACEHOLDER_REGEXP_, functionName);
  349. // Change all ' ' indents into the desired indent.
  350. // To avoid an infinite loop of replacements, change all indents to '\0'
  351. // character first, then replace them all with the indent.
  352. // We are assuming that no provided functions contain a literal null char.
  353. var oldCodeText;
  354. while (oldCodeText != codeText) {
  355. oldCodeText = codeText;
  356. codeText = codeText.replace(/^(( )*) /gm, '$1\0');
  357. }
  358. codeText = codeText.replace(/\0/g, this.INDENT);
  359. this.definitions_[desiredName] = codeText;
  360. }
  361. return this.functionNames_[desiredName];
  362. };