lists.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. /**
  2. * @license
  3. * Visual Blocks Language
  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 Generating JavaScript for list blocks.
  22. * @author fraser@google.com (Neil Fraser)
  23. */
  24. 'use strict';
  25. goog.provide('Blockly.JavaScript.lists');
  26. goog.require('Blockly.JavaScript');
  27. Blockly.JavaScript['lists_create_empty'] = function(block) {
  28. // Create an empty list.
  29. return ['[]', Blockly.JavaScript.ORDER_ATOMIC];
  30. };
  31. Blockly.JavaScript['lists_create_with'] = function(block) {
  32. // Create a list with any number of elements of any type.
  33. var code = new Array(block.itemCount_);
  34. for (var n = 0; n < block.itemCount_; n++) {
  35. code[n] = Blockly.JavaScript.valueToCode(block, 'ADD' + n,
  36. Blockly.JavaScript.ORDER_COMMA) || 'null';
  37. }
  38. code = '[' + code.join(', ') + ']';
  39. return [code, Blockly.JavaScript.ORDER_ATOMIC];
  40. };
  41. Blockly.JavaScript['lists_repeat'] = function(block) {
  42. // Create a list with one element repeated.
  43. var functionName = Blockly.JavaScript.provideFunction_(
  44. 'lists_repeat',
  45. [ 'function ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ +
  46. '(value, n) {',
  47. ' var array = [];',
  48. ' for (var i = 0; i < n; i++) {',
  49. ' array[i] = value;',
  50. ' }',
  51. ' return array;',
  52. '}']);
  53. var argument0 = Blockly.JavaScript.valueToCode(block, 'ITEM',
  54. Blockly.JavaScript.ORDER_COMMA) || 'null';
  55. var argument1 = Blockly.JavaScript.valueToCode(block, 'NUM',
  56. Blockly.JavaScript.ORDER_COMMA) || '0';
  57. var code = functionName + '(' + argument0 + ', ' + argument1 + ')';
  58. return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
  59. };
  60. Blockly.JavaScript['lists_length'] = function(block) {
  61. // String or array length.
  62. var argument0 = Blockly.JavaScript.valueToCode(block, 'VALUE',
  63. Blockly.JavaScript.ORDER_FUNCTION_CALL) || '[]';
  64. return [argument0 + '.length', Blockly.JavaScript.ORDER_MEMBER];
  65. };
  66. Blockly.JavaScript['lists_isEmpty'] = function(block) {
  67. // Is the string null or array empty?
  68. var argument0 = Blockly.JavaScript.valueToCode(block, 'VALUE',
  69. Blockly.JavaScript.ORDER_MEMBER) || '[]';
  70. return ['!' + argument0 + '.length', Blockly.JavaScript.ORDER_LOGICAL_NOT];
  71. };
  72. Blockly.JavaScript['lists_indexOf'] = function(block) {
  73. // Find an item in the list.
  74. var operator = block.getFieldValue('END') == 'FIRST' ?
  75. 'indexOf' : 'lastIndexOf';
  76. var argument0 = Blockly.JavaScript.valueToCode(block, 'FIND',
  77. Blockly.JavaScript.ORDER_NONE) || '\'\'';
  78. var argument1 = Blockly.JavaScript.valueToCode(block, 'VALUE',
  79. Blockly.JavaScript.ORDER_MEMBER) || '[]';
  80. var code = argument1 + '.' + operator + '(' + argument0 + ') + 1';
  81. return [code, Blockly.JavaScript.ORDER_MEMBER];
  82. };
  83. Blockly.JavaScript['lists_getIndex'] = function(block) {
  84. // Get element at index.
  85. // Note: Until January 2013 this block did not have MODE or WHERE inputs.
  86. var mode = block.getFieldValue('MODE') || 'GET';
  87. var where = block.getFieldValue('WHERE') || 'FROM_START';
  88. var at = Blockly.JavaScript.valueToCode(block, 'AT',
  89. Blockly.JavaScript.ORDER_UNARY_NEGATION) || '1';
  90. var list = Blockly.JavaScript.valueToCode(block, 'VALUE',
  91. Blockly.JavaScript.ORDER_MEMBER) || '[]';
  92. if (where == 'FIRST') {
  93. if (mode == 'GET') {
  94. var code = list + '[0]';
  95. return [code, Blockly.JavaScript.ORDER_MEMBER];
  96. } else if (mode == 'GET_REMOVE') {
  97. var code = list + '.shift()';
  98. return [code, Blockly.JavaScript.ORDER_MEMBER];
  99. } else if (mode == 'REMOVE') {
  100. return list + '.shift();\n';
  101. }
  102. } else if (where == 'LAST') {
  103. if (mode == 'GET') {
  104. var code = list + '.slice(-1)[0]';
  105. return [code, Blockly.JavaScript.ORDER_MEMBER];
  106. } else if (mode == 'GET_REMOVE') {
  107. var code = list + '.pop()';
  108. return [code, Blockly.JavaScript.ORDER_MEMBER];
  109. } else if (mode == 'REMOVE') {
  110. return list + '.pop();\n';
  111. }
  112. } else if (where == 'FROM_START') {
  113. // Blockly uses one-based indicies.
  114. if (Blockly.isNumber(at)) {
  115. // If the index is a naked number, decrement it right now.
  116. at = parseFloat(at) - 1;
  117. } else {
  118. // If the index is dynamic, decrement it in code.
  119. at += ' - 1';
  120. }
  121. if (mode == 'GET') {
  122. var code = list + '[' + at + ']';
  123. return [code, Blockly.JavaScript.ORDER_MEMBER];
  124. } else if (mode == 'GET_REMOVE') {
  125. var code = list + '.splice(' + at + ', 1)[0]';
  126. return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
  127. } else if (mode == 'REMOVE') {
  128. return list + '.splice(' + at + ', 1);\n';
  129. }
  130. } else if (where == 'FROM_END') {
  131. if (mode == 'GET') {
  132. var code = list + '.slice(-' + at + ')[0]';
  133. return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
  134. } else if (mode == 'GET_REMOVE' || mode == 'REMOVE') {
  135. var functionName = Blockly.JavaScript.provideFunction_(
  136. 'lists_remove_from_end',
  137. [ 'function ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ +
  138. '(list, x) {',
  139. ' x = list.length - x;',
  140. ' return list.splice(x, 1)[0];',
  141. '}']);
  142. code = functionName + '(' + list + ', ' + at + ')';
  143. if (mode == 'GET_REMOVE') {
  144. return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
  145. } else if (mode == 'REMOVE') {
  146. return code + ';\n';
  147. }
  148. }
  149. } else if (where == 'RANDOM') {
  150. var functionName = Blockly.JavaScript.provideFunction_(
  151. 'lists_get_random_item',
  152. [ 'function ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ +
  153. '(list, remove) {',
  154. ' var x = Math.floor(Math.random() * list.length);',
  155. ' if (remove) {',
  156. ' return list.splice(x, 1)[0];',
  157. ' } else {',
  158. ' return list[x];',
  159. ' }',
  160. '}']);
  161. code = functionName + '(' + list + ', ' + (mode != 'GET') + ')';
  162. if (mode == 'GET' || mode == 'GET_REMOVE') {
  163. return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
  164. } else if (mode == 'REMOVE') {
  165. return code + ';\n';
  166. }
  167. }
  168. throw 'Unhandled combination (lists_getIndex).';
  169. };
  170. Blockly.JavaScript['lists_setIndex'] = function(block) {
  171. // Set element at index.
  172. // Note: Until February 2013 this block did not have MODE or WHERE inputs.
  173. var list = Blockly.JavaScript.valueToCode(block, 'LIST',
  174. Blockly.JavaScript.ORDER_MEMBER) || '[]';
  175. var mode = block.getFieldValue('MODE') || 'GET';
  176. var where = block.getFieldValue('WHERE') || 'FROM_START';
  177. var at = Blockly.JavaScript.valueToCode(block, 'AT',
  178. Blockly.JavaScript.ORDER_NONE) || '1';
  179. var value = Blockly.JavaScript.valueToCode(block, 'TO',
  180. Blockly.JavaScript.ORDER_ASSIGNMENT) || 'null';
  181. // Cache non-trivial values to variables to prevent repeated look-ups.
  182. // Closure, which accesses and modifies 'list'.
  183. function cacheList() {
  184. if (list.match(/^\w+$/)) {
  185. return '';
  186. }
  187. var listVar = Blockly.JavaScript.variableDB_.getDistinctName(
  188. 'tmp_list', Blockly.Variables.NAME_TYPE);
  189. var code = 'var ' + listVar + ' = ' + list + ';\n';
  190. list = listVar;
  191. return code;
  192. }
  193. if (where == 'FIRST') {
  194. if (mode == 'SET') {
  195. return list + '[0] = ' + value + ';\n';
  196. } else if (mode == 'INSERT') {
  197. return list + '.unshift(' + value + ');\n';
  198. }
  199. } else if (where == 'LAST') {
  200. if (mode == 'SET') {
  201. var code = cacheList();
  202. code += list + '[' + list + '.length - 1] = ' + value + ';\n';
  203. return code;
  204. } else if (mode == 'INSERT') {
  205. return list + '.push(' + value + ');\n';
  206. }
  207. } else if (where == 'FROM_START') {
  208. // Blockly uses one-based indicies.
  209. if (Blockly.isNumber(at)) {
  210. // If the index is a naked number, decrement it right now.
  211. at = parseFloat(at) - 1;
  212. } else {
  213. // If the index is dynamic, decrement it in code.
  214. at += ' - 1';
  215. }
  216. if (mode == 'SET') {
  217. return list + '[' + at + '] = ' + value + ';\n';
  218. } else if (mode == 'INSERT') {
  219. return list + '.splice(' + at + ', 0, ' + value + ');\n';
  220. }
  221. } else if (where == 'FROM_END') {
  222. var code = cacheList();
  223. if (mode == 'SET') {
  224. code += list + '[' + list + '.length - ' + at + '] = ' + value + ';\n';
  225. return code;
  226. } else if (mode == 'INSERT') {
  227. code += list + '.splice(' + list + '.length - ' + at + ', 0, ' + value +
  228. ');\n';
  229. return code;
  230. }
  231. } else if (where == 'RANDOM') {
  232. var code = cacheList();
  233. var xVar = Blockly.JavaScript.variableDB_.getDistinctName(
  234. 'tmp_x', Blockly.Variables.NAME_TYPE);
  235. code += 'var ' + xVar + ' = Math.floor(Math.random() * ' + list +
  236. '.length);\n';
  237. if (mode == 'SET') {
  238. code += list + '[' + xVar + '] = ' + value + ';\n';
  239. return code;
  240. } else if (mode == 'INSERT') {
  241. code += list + '.splice(' + xVar + ', 0, ' + value + ');\n';
  242. return code;
  243. }
  244. }
  245. throw 'Unhandled combination (lists_setIndex).';
  246. };
  247. Blockly.JavaScript['lists_getSublist'] = function(block) {
  248. // Get sublist.
  249. var list = Blockly.JavaScript.valueToCode(block, 'LIST',
  250. Blockly.JavaScript.ORDER_MEMBER) || '[]';
  251. var where1 = block.getFieldValue('WHERE1');
  252. var where2 = block.getFieldValue('WHERE2');
  253. var at1 = Blockly.JavaScript.valueToCode(block, 'AT1',
  254. Blockly.JavaScript.ORDER_NONE) || '1';
  255. var at2 = Blockly.JavaScript.valueToCode(block, 'AT2',
  256. Blockly.JavaScript.ORDER_NONE) || '1';
  257. if (where1 == 'FIRST' && where2 == 'LAST') {
  258. var code = list + '.concat()';
  259. } else {
  260. var functionName = Blockly.JavaScript.provideFunction_(
  261. 'lists_get_sublist',
  262. [ 'function ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ +
  263. '(list, where1, at1, where2, at2) {',
  264. ' function getAt(where, at) {',
  265. ' if (where == \'FROM_START\') {',
  266. ' at--;',
  267. ' } else if (where == \'FROM_END\') {',
  268. ' at = list.length - at;',
  269. ' } else if (where == \'FIRST\') {',
  270. ' at = 0;',
  271. ' } else if (where == \'LAST\') {',
  272. ' at = list.length - 1;',
  273. ' } else {',
  274. ' throw \'Unhandled option (lists_getSublist).\';',
  275. ' }',
  276. ' return at;',
  277. ' }',
  278. ' at1 = getAt(where1, at1);',
  279. ' at2 = getAt(where2, at2) + 1;',
  280. ' return list.slice(at1, at2);',
  281. '}']);
  282. var code = functionName + '(' + list + ', \'' +
  283. where1 + '\', ' + at1 + ', \'' + where2 + '\', ' + at2 + ')';
  284. }
  285. return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
  286. };
  287. Blockly.JavaScript['lists_sort'] = function(block) {
  288. // Block for sorting a list.
  289. var listCode = Blockly.JavaScript.valueToCode(
  290. block, 'LIST',
  291. Blockly.JavaScript.ORDER_FUNCTION_CALL) || '[]';
  292. var direction = block.getFieldValue('DIRECTION') === '1' ? 1 : -1;
  293. var type = block.getFieldValue('TYPE');
  294. var getCompareFunctionName = Blockly.JavaScript.provideFunction_(
  295. 'lists_get_sort_compare',
  296. ['function ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ +
  297. '(type, direction) {',
  298. ' var compareFuncs = {',
  299. ' "NUMERIC": function(a, b) {',
  300. ' return parseFloat(a) - parseFloat(b); },',
  301. ' "TEXT": function(a, b) {',
  302. ' return a.toString().localeCompare(b.toString(), "en"); },',
  303. ' "IGNORE_CASE": function(a, b) {',
  304. ' return a.toString().localeCompare(b.toString(), "en",',
  305. ' {"sensitivity": "base"}); },',
  306. ' };',
  307. ' var compare = compareFuncs[type];',
  308. ' return function(a, b) { return compare(a, b) * direction; }',
  309. '}']);
  310. return ['(' + listCode + ').slice().sort(' +
  311. getCompareFunctionName + '("' + type + '", ' + direction + '))',
  312. Blockly.JavaScript.ORDER_FUNCTION_CALL];
  313. };
  314. Blockly.JavaScript['lists_split'] = function(block) {
  315. // Block for splitting text into a list, or joining a list into text.
  316. var value_input = Blockly.JavaScript.valueToCode(block, 'INPUT',
  317. Blockly.JavaScript.ORDER_MEMBER);
  318. var value_delim = Blockly.JavaScript.valueToCode(block, 'DELIM',
  319. Blockly.JavaScript.ORDER_NONE) || '\'\'';
  320. var mode = block.getFieldValue('MODE');
  321. if (mode == 'SPLIT') {
  322. if (!value_input) {
  323. value_input = '\'\'';
  324. }
  325. var functionName = 'split';
  326. } else if (mode == 'JOIN') {
  327. if (!value_input) {
  328. value_input = '[]';
  329. }
  330. var functionName = 'join';
  331. } else {
  332. throw 'Unknown mode: ' + mode;
  333. }
  334. var code = value_input + '.' + functionName + '(' + value_delim + ')';
  335. return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
  336. };