lists.js 13 KB

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