lists.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. /**
  2. * @license
  3. * Visual Blocks Language
  4. *
  5. * Copyright 2016 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 Generating Lua for list blocks.
  22. * @author rodrigoq@google.com (Rodrigo Queiro)
  23. */
  24. 'use strict';
  25. goog.provide('Blockly.Lua.lists');
  26. goog.require('Blockly.Lua');
  27. Blockly.Lua['lists_create_empty'] = function(block) {
  28. // Create an empty list.
  29. // List literals must be parenthesized before indexing into.
  30. return ['({})', Blockly.Lua.ORDER_ATOMIC];
  31. };
  32. Blockly.Lua['lists_create_with'] = function(block) {
  33. // Create a list with any number of elements of any type.
  34. var code = new Array(block.itemCount_);
  35. for (var n = 0; n < block.itemCount_; n++) {
  36. code[n] = Blockly.Lua.valueToCode(block, 'ADD' + n,
  37. Blockly.Lua.ORDER_NONE) || 'None';
  38. }
  39. code = '({' + code.join(', ') + '})';
  40. return [code, Blockly.Lua.ORDER_ATOMIC];
  41. };
  42. Blockly.Lua['lists_repeat'] = function(block) {
  43. // Create a list with one element repeated.
  44. var functionName = Blockly.Lua.provideFunction_(
  45. 'create_list_repeated',
  46. ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(item, count)',
  47. ' local t = {}',
  48. ' for i = 1, count do',
  49. ' table.insert(t, item)',
  50. ' end',
  51. ' return t',
  52. 'end']);
  53. var argument0 = Blockly.Lua.valueToCode(block, 'ITEM',
  54. Blockly.Lua.ORDER_NONE) || 'None';
  55. var argument1 = Blockly.Lua.valueToCode(block, 'NUM',
  56. Blockly.Lua.ORDER_NONE) || '0';
  57. var code = functionName + '(' + argument0 + ', ' + argument1 + ')';
  58. return [code, Blockly.Lua.ORDER_HIGH];
  59. };
  60. Blockly.Lua['lists_length'] = function(block) {
  61. // String or array length.
  62. var argument0 = Blockly.Lua.valueToCode(block, 'VALUE',
  63. Blockly.Lua.ORDER_HIGH) || '({})';
  64. return ['#' + argument0, Blockly.Lua.ORDER_HIGH];
  65. };
  66. Blockly.Lua['lists_isEmpty'] = function(block) {
  67. // Is the string null or array empty?
  68. var argument0 = Blockly.Lua.valueToCode(block, 'VALUE',
  69. Blockly.Lua.ORDER_HIGH) || '({})';
  70. var code = '#' + argument0 + ' == 0';
  71. return [code, Blockly.Lua.ORDER_RELATIONAL];
  72. };
  73. Blockly.Lua['lists_indexOf'] = function(block) {
  74. // Find an item in the list.
  75. var argument0 = Blockly.Lua.valueToCode(block, 'FIND',
  76. Blockly.Lua.ORDER_NONE) || '\'\'';
  77. var argument1 = Blockly.Lua.valueToCode(block, 'VALUE',
  78. Blockly.Lua.ORDER_NONE) || '({})';
  79. var functionName;
  80. if (block.getFieldValue('END') == 'FIRST') {
  81. functionName = Blockly.Lua.provideFunction_(
  82. 'first_index',
  83. ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t, elem)',
  84. ' for k, v in ipairs(t) do',
  85. ' if v == elem then',
  86. ' return k',
  87. ' end',
  88. ' end',
  89. ' return 0',
  90. 'end']);
  91. } else {
  92. functionName = Blockly.Lua.provideFunction_(
  93. 'last_index',
  94. ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t, elem)',
  95. ' for i = #t, 1, -1 do',
  96. ' if t[i] == elem then',
  97. ' return i',
  98. ' end',
  99. ' end',
  100. ' return 0',
  101. 'end']);
  102. }
  103. var code = functionName + '(' + argument1 + ', ' + argument0 + ')';
  104. return [code, Blockly.Lua.ORDER_HIGH];
  105. };
  106. /**
  107. * Returns an expression calculating the index into a list.
  108. * @private
  109. * @param {string} listname Name of the list, used to calculate length.
  110. * @param {string} where The method of indexing, selected by dropdown in Blockly
  111. * @param {string=} opt_at The optional offset when indexing from start/end.
  112. * @return {string} Index expression.
  113. */
  114. Blockly.Lua.lists.getIndex_ = function(listname, where, opt_at) {
  115. if (where == 'FIRST') {
  116. return '1';
  117. } else if (where == 'FROM_END') {
  118. return '#' + listname + ' + 1 - ' + opt_at;
  119. } else if (where == 'LAST') {
  120. return '#' + listname;
  121. } else if (where == 'RANDOM') {
  122. return 'math.random(#' + listname + ')';
  123. } else {
  124. return opt_at;
  125. }
  126. };
  127. /**
  128. * Counter for generating unique symbols.
  129. * @private
  130. * @type {number}
  131. */
  132. Blockly.Lua.lists.gensym_counter_ = 0;
  133. /**
  134. * Generate a unique symbol.
  135. * @private
  136. * @return {string} unique symbol, eg 'G123'
  137. */
  138. Blockly.Lua.lists.gensym_ = function() {
  139. return 'G' + Blockly.Lua.lists.gensym_counter_++;
  140. };
  141. Blockly.Lua['lists_getIndex'] = function(block) {
  142. // Get element at index.
  143. // Note: Until January 2013 this block did not have MODE or WHERE inputs.
  144. var mode = block.getFieldValue('MODE') || 'GET';
  145. var where = block.getFieldValue('WHERE') || 'FROM_START';
  146. var at = Blockly.Lua.valueToCode(block, 'AT',
  147. Blockly.Lua.ORDER_ADDITIVE) || '1';
  148. var list = Blockly.Lua.valueToCode(block, 'VALUE',
  149. Blockly.Lua.ORDER_HIGH) || '({})';
  150. var getIndex_ = Blockly.Lua.lists.getIndex_;
  151. var gensym_ = Blockly.Lua.lists.gensym_;
  152. // If `list` would be evaluated more than once (which is the case for LAST,
  153. // FROM_END, and RANDOM) and is non-trivial, make sure to access it only once.
  154. if ((where == 'LAST' || where == 'FROM_END' || where == 'RANDOM') &&
  155. !list.match(/^\w+$/)) {
  156. // `list` is an expression, so we may not evaluate it more than once.
  157. if (mode == 'REMOVE') {
  158. // We can use multiple statements.
  159. var listVar = Blockly.Lua.variableDB_.getDistinctName(
  160. 'tmp_list', Blockly.Variables.NAME_TYPE);
  161. var code = listVar + ' = ' + list + '\n' +
  162. 'table.remove(' + listVar + ', ' + getIndex_(listVar, where, at) +
  163. ')\n';
  164. return code;
  165. } else {
  166. // We need to create a procedure to avoid reevaluating values.
  167. if (mode == 'GET') {
  168. // Note that getIndex_() ignores `at` when `where` == 'LAST' or
  169. // 'RANDOM', so we only need one procedure for each of those 'where'
  170. // values. The value for 'FROM_END' depends on `at`, so we will
  171. // generate a unique procedure (name) each time.
  172. var functionName = Blockly.Lua.provideFunction_(
  173. 'list_get_' + where.toLowerCase() +
  174. (where == 'FROM_END' ? '_' + gensym_() : ''),
  175. ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)',
  176. ' return t[' + getIndex_('t', where, at) + ']',
  177. 'end']);
  178. } else { // mode == 'GET_REMOVE'
  179. // We need to create a procedure.
  180. var functionName = Blockly.Lua.provideFunction_(
  181. 'list_remove_' + where.toLowerCase() +
  182. (where == 'FROM_END' ? '_' + gensym_() : ''),
  183. ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t)',
  184. ' return table.remove(t, ' + getIndex_('t', where, at) + ')',
  185. 'end']);
  186. }
  187. var code = functionName + '(' + list + ')';
  188. return [code, Blockly.Lua.ORDER_HIGH];
  189. }
  190. } else {
  191. // Either `list` is a simple variable, or we only need to refer to `list`
  192. // once.
  193. if (mode == 'GET') {
  194. var code = list + '[' + getIndex_(list, where, at) + ']';
  195. return [code, Blockly.Lua.ORDER_HIGH];
  196. } else {
  197. var code = 'table.remove(' + list + ', ' + getIndex_(list, where, at) +
  198. ')';
  199. if (mode == 'GET_REMOVE') {
  200. return [code, Blockly.Lua.ORDER_HIGH];
  201. } else { // `mode` == 'REMOVE'
  202. return code + '\n';
  203. }
  204. }
  205. }
  206. };
  207. Blockly.Lua['lists_setIndex'] = function(block) {
  208. // Set element at index.
  209. // Note: Until February 2013 this block did not have MODE or WHERE inputs.
  210. var list = Blockly.Lua.valueToCode(block, 'LIST',
  211. Blockly.Lua.ORDER_HIGH) || '({})';
  212. var mode = block.getFieldValue('MODE') || 'SET';
  213. var where = block.getFieldValue('WHERE') || 'FROM_START';
  214. var at = Blockly.Lua.valueToCode(block, 'AT',
  215. Blockly.Lua.ORDER_ADDITIVE) || '1';
  216. var value = Blockly.Lua.valueToCode(block, 'TO',
  217. Blockly.Lua.ORDER_NONE) || 'None';
  218. var getIndex_ = Blockly.Lua.lists.getIndex_;
  219. // If `list` would be evaluated more than once (which is the case for LAST,
  220. // FROM_END, and RANDOM) and is non-trivial, make sure to access it only once.
  221. if ((where == 'LAST' || where == 'FROM_END' || where == 'RANDOM') &&
  222. !list.match(/^\w+$/)) {
  223. // `list` is an expression, so we may not evaluate it more than once.
  224. if (where == 'RANDOM' || where == 'LAST') {
  225. // In these cases, `at` is implicit. getIndex_() ignores its value.
  226. if (mode == 'SET') {
  227. var functionName = Blockly.Lua.provideFunction_(
  228. 'list_set_' + where.toLowerCase(),
  229. ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t, val)',
  230. ' t[' + getIndex_('t', where, at) + '] = val',
  231. 'end']);
  232. } else { // `mode` == 'INSERT'
  233. var functionName = Blockly.Lua.provideFunction_(
  234. 'list_insert_' + where.toLowerCase(),
  235. ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(t, val)',
  236. ' table.insert(t, ' +
  237. // LAST is a special case, because we want to insert
  238. // *after* not *before*, the existing last element.
  239. getIndex_('t', where, at) + (where == 'LAST' ? ' + 1' : '') +
  240. ', val)',
  241. 'end']);
  242. }
  243. var code = functionName + '(' + list + ', ' + value + ')\n';
  244. return code;
  245. } else { // `where` = 'FROM_END'
  246. if (mode == 'SET') {
  247. var functionName = Blockly.Lua.provideFunction_(
  248. 'list_set_from_end',
  249. ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ +
  250. '(t, index, val)',
  251. ' t[#t + 1 - index] = val',
  252. 'end']);
  253. } else { // `mode` == 'INSERT'
  254. var functionName = Blockly.Lua.provideFunction_(
  255. 'list_insert_from_end',
  256. ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ +
  257. '(t, index, val)',
  258. ' table.insert(t, #t + 1 - index, val)',
  259. 'end']);
  260. }
  261. var code = functionName + '(' + list + ', ' + at + ', ' + value + ')\n';
  262. return code;
  263. }
  264. } else {
  265. // It's okay to have multiple references to the list.
  266. if (mode == 'SET') {
  267. var code = list + '[' + getIndex_(list, where, at) + '] = ' + value;
  268. } else { // `mode` == 'INSERT'
  269. // LAST is a special case, because we want to insert
  270. // *after* not *before*, the existing last element.
  271. var code = 'table.insert(' + list + ', ' +
  272. (getIndex_(list, where, at) + (where == 'LAST' ? ' + 1' : '')) +
  273. ', ' + value + ')';
  274. }
  275. return code + '\n';
  276. }
  277. };
  278. Blockly.Lua['lists_getSublist'] = function(block) {
  279. // Get sublist.
  280. var list = Blockly.Lua.valueToCode(block, 'LIST',
  281. Blockly.Lua.ORDER_HIGH) || '({})';
  282. var where1 = block.getFieldValue('WHERE1');
  283. var where2 = block.getFieldValue('WHERE2');
  284. var at1 = Blockly.Lua.valueToCode(block, 'AT1',
  285. Blockly.Lua.ORDER_ADDITIVE) || '1';
  286. var at2 = Blockly.Lua.valueToCode(block, 'AT2',
  287. Blockly.Lua.ORDER_ADDITIVE) || '1';
  288. var getIndex_ = Blockly.Lua.lists.getIndex_;
  289. var functionName = Blockly.Lua.provideFunction_(
  290. 'list_sublist_' + Blockly.Lua.lists.gensym_(),
  291. ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ + '(source)',
  292. ' local t = {}',
  293. ' local start = ' + getIndex_('source', where1, at1),
  294. ' local finish = ' + getIndex_('source', where2, at2),
  295. ' for i = start, finish do',
  296. ' table.insert(t, source[i])',
  297. ' end',
  298. ' return t',
  299. 'end']);
  300. var code = functionName + '(' + list + ')';
  301. return [code, Blockly.Lua.ORDER_HIGH];
  302. };
  303. Blockly.Lua['lists_sort'] = function(block) {
  304. // Block for sorting a list.
  305. var listCode = Blockly.Lua.valueToCode(
  306. block, 'LIST', Blockly.Lua.ORDER_HIGH) || '({})';
  307. var direction = block.getFieldValue('DIRECTION') === '1' ? 1 : -1;
  308. var type = block.getFieldValue('TYPE');
  309. var functionName = Blockly.Lua.provideFunction_(
  310. 'list_sort',
  311. ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ +
  312. '(list, typev, direction)',
  313. ' local t = {}',
  314. ' for n,v in pairs(list) do table.insert(t, v) end', // Shallow-copy.
  315. ' local compareFuncs = {',
  316. ' NUMERIC = function(a, b)',
  317. ' return (tonumber(tostring(a)) or 0)',
  318. ' < (tonumber(tostring(b)) or 0) end,',
  319. ' TEXT = function(a, b)',
  320. ' return tostring(a) < tostring(b) end,',
  321. ' IGNORE_CASE = function(a, b)',
  322. ' return string.lower(tostring(a)) < string.lower(tostring(b)) end',
  323. ' }',
  324. ' local compareTemp = compareFuncs[typev]',
  325. ' local compare = compareTemp',
  326. ' if direction == -1',
  327. ' then compare = function(a, b) return compareTemp(b, a) end',
  328. ' end',
  329. ' table.sort(t, compare)',
  330. ' return t',
  331. 'end']);
  332. var code = functionName +
  333. '(' + listCode + ',"' + type + '", ' + direction + ')';
  334. return [code, Blockly.Lua.ORDER_HIGH];
  335. };
  336. Blockly.Lua['lists_split'] = function(block) {
  337. // Block for splitting text into a list, or joining a list into text.
  338. var value_input = Blockly.Lua.valueToCode(block, 'INPUT',
  339. Blockly.Lua.ORDER_NONE);
  340. var value_delim = Blockly.Lua.valueToCode(block, 'DELIM',
  341. Blockly.Lua.ORDER_NONE) || '\'\'';
  342. var mode = block.getFieldValue('MODE');
  343. var functionName;
  344. if (mode == 'SPLIT') {
  345. if (!value_input) {
  346. value_input = '\'\'';
  347. }
  348. functionName = Blockly.Lua.provideFunction_(
  349. 'list_string_split',
  350. ['function ' + Blockly.Lua.FUNCTION_NAME_PLACEHOLDER_ +
  351. '(input, delim)',
  352. ' local t = {}',
  353. ' local pos = 1',
  354. ' while true do',
  355. ' next_delim = string.find(input, delim, pos)',
  356. ' if next_delim == nil then',
  357. ' table.insert(t, string.sub(input, pos))',
  358. ' break',
  359. ' else',
  360. ' table.insert(t, string.sub(input, pos, next_delim-1))',
  361. ' pos = next_delim + #delim',
  362. ' end',
  363. ' end',
  364. ' return t',
  365. 'end']);
  366. } else if (mode == 'JOIN') {
  367. if (!value_input) {
  368. value_input = '({})';
  369. }
  370. functionName = 'table.concat';
  371. } else {
  372. throw 'Unknown mode: ' + mode;
  373. }
  374. var code = functionName + '(' + value_input + ', ' + value_delim + ')';
  375. return [code, Blockly.Lua.ORDER_HIGH];
  376. };