blockly 7.20211209.4 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/blockly.d.ts +18963 -18432
  2. package/blockly.min.js +852 -844
  3. package/blockly_compressed.js +669 -664
  4. package/blockly_compressed.js.map +1 -1
  5. package/blocks/blocks.js +47 -0
  6. package/blocks/colour.js +13 -3
  7. package/blocks/lists.js +22 -13
  8. package/blocks/logic.js +13 -3
  9. package/blocks/loops.js +24 -11
  10. package/blocks/math.js +12 -3
  11. package/blocks/procedures.js +41 -27
  12. package/blocks/text.js +22 -13
  13. package/blocks/variables.js +14 -3
  14. package/blocks/variables_dynamic.js +13 -3
  15. package/blocks_compressed.js +146 -141
  16. package/blocks_compressed.js.map +1 -1
  17. package/core/block.js +1869 -1814
  18. package/core/block_drag_surface.js +201 -200
  19. package/core/block_dragger.js +377 -373
  20. package/core/block_svg.js +1593 -1479
  21. package/core/blockly.js +8 -22
  22. package/core/blocks.js +9 -2
  23. package/core/browser_events.js +22 -5
  24. package/core/bubble.js +841 -797
  25. package/core/bubble_dragger.js +213 -206
  26. package/core/bump_objects.js +2 -2
  27. package/core/clipboard.js +9 -9
  28. package/core/comment.js +353 -332
  29. package/core/common.js +46 -17
  30. package/core/component_manager.js +181 -174
  31. package/core/config.js +87 -0
  32. package/core/connection.js +595 -584
  33. package/core/connection_checker.js +242 -244
  34. package/core/connection_db.js +235 -230
  35. package/core/contextmenu.js +9 -6
  36. package/core/contextmenu_items.js +1 -2
  37. package/core/contextmenu_registry.js +93 -89
  38. package/core/css.js +474 -474
  39. package/core/delete_area.js +45 -42
  40. package/core/drag_target.js +57 -56
  41. package/core/dropdowndiv.js +153 -163
  42. package/core/events/events.js +2 -2
  43. package/core/events/events_abstract.js +89 -77
  44. package/core/events/events_block_base.js +37 -36
  45. package/core/events/events_block_change.js +130 -124
  46. package/core/events/events_block_create.js +73 -71
  47. package/core/events/events_block_delete.js +84 -82
  48. package/core/events/events_block_drag.js +50 -49
  49. package/core/events/events_block_move.js +147 -140
  50. package/core/events/events_bubble_open.js +51 -50
  51. package/core/events/events_click.js +48 -44
  52. package/core/events/events_comment_base.js +72 -69
  53. package/core/events/events_comment_change.js +63 -61
  54. package/core/events/events_comment_create.js +44 -42
  55. package/core/events/events_comment_delete.js +42 -40
  56. package/core/events/events_comment_move.js +106 -104
  57. package/core/events/events_marker_move.js +65 -64
  58. package/core/events/events_selected.js +46 -45
  59. package/core/events/events_theme_change.js +36 -35
  60. package/core/events/events_toolbox_item_select.js +46 -45
  61. package/core/events/events_trashcan_open.js +37 -36
  62. package/core/events/events_ui.js +47 -46
  63. package/core/events/events_ui_base.js +30 -29
  64. package/core/events/events_var_base.js +37 -36
  65. package/core/events/events_var_create.js +50 -48
  66. package/core/events/events_var_delete.js +50 -48
  67. package/core/events/events_var_rename.js +51 -49
  68. package/core/events/events_viewport.js +66 -65
  69. package/core/events/utils.js +29 -14
  70. package/core/events/workspace_events.js +49 -55
  71. package/core/extensions.js +4 -3
  72. package/core/field.js +1061 -997
  73. package/core/field_angle.js +462 -442
  74. package/core/field_checkbox.js +194 -182
  75. package/core/field_colour.js +519 -505
  76. package/core/field_dropdown.js +617 -598
  77. package/core/field_image.js +229 -220
  78. package/core/field_label.js +102 -91
  79. package/core/field_label_serializable.js +42 -41
  80. package/core/field_multilineinput.js +372 -358
  81. package/core/field_number.js +272 -253
  82. package/core/field_textinput.js +499 -467
  83. package/core/field_variable.js +458 -420
  84. package/core/flyout_base.js +1005 -952
  85. package/core/flyout_button.js +277 -260
  86. package/core/flyout_horizontal.js +304 -302
  87. package/core/flyout_metrics_manager.js +64 -64
  88. package/core/flyout_vertical.js +306 -300
  89. package/core/generator.js +459 -446
  90. package/core/gesture.js +829 -813
  91. package/core/grid.js +166 -163
  92. package/core/icon.js +168 -159
  93. package/core/inject.js +7 -5
  94. package/core/input.js +257 -248
  95. package/core/insertion_marker_manager.js +655 -624
  96. package/core/internal_constants.js +0 -129
  97. package/core/keyboard_nav/ast_node.js +605 -596
  98. package/core/keyboard_nav/basic_cursor.js +166 -165
  99. package/core/keyboard_nav/cursor.js +99 -97
  100. package/core/keyboard_nav/marker.js +83 -79
  101. package/core/keyboard_nav/tab_navigate_cursor.js +18 -23
  102. package/core/marker_manager.js +153 -141
  103. package/core/menu.js +377 -372
  104. package/core/menuitem.js +223 -217
  105. package/core/metrics_manager.js +403 -390
  106. package/core/mutator.js +468 -437
  107. package/core/names.js +229 -188
  108. package/core/options.js +290 -284
  109. package/core/procedures.js +29 -17
  110. package/core/registry.js +19 -16
  111. package/core/rendered_connection.js +482 -463
  112. package/core/renderers/common/block_rendering.js +9 -3
  113. package/core/renderers/common/constants.js +1119 -1112
  114. package/core/renderers/common/debug.js +14 -0
  115. package/core/renderers/common/debugger.js +338 -316
  116. package/core/renderers/common/drawer.js +380 -370
  117. package/core/renderers/common/i_path_object.js +2 -2
  118. package/core/renderers/common/info.js +626 -618
  119. package/core/renderers/common/marker_svg.js +579 -541
  120. package/core/renderers/common/path_object.js +203 -200
  121. package/core/renderers/common/renderer.js +220 -218
  122. package/core/renderers/geras/constants.js +36 -36
  123. package/core/renderers/geras/drawer.js +155 -147
  124. package/core/renderers/geras/highlight_constants.js +244 -238
  125. package/core/renderers/geras/highlighter.js +231 -179
  126. package/core/renderers/geras/info.js +392 -369
  127. package/core/renderers/geras/measurables/inline_input.js +25 -19
  128. package/core/renderers/geras/measurables/statement_input.js +23 -17
  129. package/core/renderers/geras/path_object.js +106 -121
  130. package/core/renderers/geras/renderer.js +96 -98
  131. package/core/renderers/measurables/base.js +30 -18
  132. package/core/renderers/measurables/bottom_row.js +83 -80
  133. package/core/renderers/measurables/connection.js +22 -15
  134. package/core/renderers/measurables/external_value_input.js +35 -22
  135. package/core/renderers/measurables/field.js +35 -20
  136. package/core/renderers/measurables/hat.js +18 -13
  137. package/core/renderers/measurables/icon.js +24 -17
  138. package/core/renderers/measurables/in_row_spacer.js +15 -13
  139. package/core/renderers/measurables/inline_input.js +43 -33
  140. package/core/renderers/measurables/input_connection.js +41 -28
  141. package/core/renderers/measurables/input_row.js +50 -44
  142. package/core/renderers/measurables/jagged_edge.js +14 -12
  143. package/core/renderers/measurables/next_connection.js +16 -14
  144. package/core/renderers/measurables/output_connection.js +26 -20
  145. package/core/renderers/measurables/previous_connection.js +16 -15
  146. package/core/renderers/measurables/round_corner.js +20 -18
  147. package/core/renderers/measurables/row.js +184 -168
  148. package/core/renderers/measurables/spacer_row.js +38 -23
  149. package/core/renderers/measurables/square_corner.js +18 -16
  150. package/core/renderers/measurables/statement_input.js +23 -20
  151. package/core/renderers/measurables/top_row.js +88 -85
  152. package/core/renderers/minimalist/constants.js +8 -7
  153. package/core/renderers/minimalist/drawer.js +11 -10
  154. package/core/renderers/minimalist/info.js +18 -18
  155. package/core/renderers/minimalist/renderer.js +40 -39
  156. package/core/renderers/thrasos/info.js +258 -248
  157. package/core/renderers/thrasos/renderer.js +20 -20
  158. package/core/renderers/zelos/constants.js +898 -873
  159. package/core/renderers/zelos/drawer.js +186 -169
  160. package/core/renderers/zelos/info.js +502 -479
  161. package/core/renderers/zelos/marker_svg.js +129 -115
  162. package/core/renderers/zelos/measurables/bottom_row.js +31 -30
  163. package/core/renderers/zelos/measurables/inputs.js +22 -21
  164. package/core/renderers/zelos/measurables/row_elements.js +14 -13
  165. package/core/renderers/zelos/measurables/top_row.js +34 -33
  166. package/core/renderers/zelos/path_object.js +181 -180
  167. package/core/renderers/zelos/renderer.js +91 -92
  168. package/core/scrollbar.js +759 -713
  169. package/core/scrollbar_pair.js +250 -245
  170. package/core/serialization/blocks.js +19 -9
  171. package/core/serialization/workspaces.js +3 -2
  172. package/core/shortcut_registry.js +286 -277
  173. package/core/sprites.js +31 -0
  174. package/core/theme.js +135 -141
  175. package/core/theme_manager.js +147 -143
  176. package/core/toolbox/category.js +602 -576
  177. package/core/toolbox/collapsible_category.js +226 -227
  178. package/core/toolbox/separator.js +70 -61
  179. package/core/toolbox/toolbox.js +934 -927
  180. package/core/toolbox/toolbox_item.js +115 -99
  181. package/core/tooltip.js +108 -35
  182. package/core/touch.js +8 -3
  183. package/core/touch_gesture.js +254 -251
  184. package/core/trashcan.js +606 -595
  185. package/core/utils/coordinate.js +97 -95
  186. package/core/utils/dom.js +2 -2
  187. package/core/utils/global.js +2 -0
  188. package/core/utils/rect.js +41 -37
  189. package/core/utils/sentinel.js +25 -0
  190. package/core/utils/size.js +30 -27
  191. package/core/utils/svg.js +18 -16
  192. package/core/variable_map.js +325 -341
  193. package/core/variable_model.js +55 -54
  194. package/core/variables.js +9 -2
  195. package/core/variables_dynamic.js +3 -1
  196. package/core/warning.js +126 -120
  197. package/core/widgetdiv.js +4 -4
  198. package/core/workspace.js +685 -664
  199. package/core/workspace_audio.js +124 -118
  200. package/core/workspace_comment.js +308 -298
  201. package/core/workspace_comment_svg.js +1029 -951
  202. package/core/workspace_drag_surface_svg.js +147 -140
  203. package/core/workspace_dragger.js +70 -71
  204. package/core/workspace_svg.js +2322 -2297
  205. package/core/xml.js +30 -20
  206. package/core/zoom_controls.js +431 -439
  207. package/dart_compressed.js +40 -43
  208. package/dart_compressed.js.map +1 -1
  209. package/generators/dart/colour.js +56 -64
  210. package/generators/dart/lists.js +61 -50
  211. package/generators/dart/math.js +160 -148
  212. package/generators/dart/text.js +83 -61
  213. package/generators/javascript/colour.js +37 -34
  214. package/generators/javascript/lists.js +50 -43
  215. package/generators/javascript/math.js +123 -139
  216. package/generators/javascript/text.js +67 -81
  217. package/generators/lua/colour.js +25 -23
  218. package/generators/lua/lists.js +97 -69
  219. package/generators/lua/logic.js +1 -2
  220. package/generators/lua/math.js +182 -144
  221. package/generators/lua/text.js +116 -99
  222. package/generators/php/colour.js +38 -32
  223. package/generators/php/lists.js +109 -89
  224. package/generators/php/math.js +90 -81
  225. package/generators/php/text.js +63 -61
  226. package/generators/python/colour.js +18 -18
  227. package/generators/python/lists.js +38 -30
  228. package/generators/python/loops.js +12 -8
  229. package/generators/python/math.js +104 -106
  230. package/generators/python/text.js +34 -30
  231. package/javascript_compressed.js +37 -39
  232. package/javascript_compressed.js.map +1 -1
  233. package/lua_compressed.js +39 -42
  234. package/lua_compressed.js.map +1 -1
  235. package/msg/az.js +2 -2
  236. package/msg/be.js +4 -4
  237. package/msg/cs.js +15 -15
  238. package/msg/de.js +1 -1
  239. package/msg/diq.js +1 -1
  240. package/msg/eo.js +1 -1
  241. package/msg/es.js +1 -1
  242. package/msg/fa.js +1 -1
  243. package/msg/fr.js +4 -4
  244. package/msg/he.js +1 -1
  245. package/msg/hr.js +2 -2
  246. package/msg/hy.js +2 -2
  247. package/msg/id.js +12 -12
  248. package/msg/inh.js +14 -14
  249. package/msg/ja.js +7 -7
  250. package/msg/lv.js +29 -29
  251. package/msg/pa.js +3 -3
  252. package/msg/smn.js +436 -0
  253. package/msg/te.js +1 -1
  254. package/msg/yue.js +1 -1
  255. package/msg/zh-hans.js +3 -3
  256. package/msg/zh-hant.js +3 -3
  257. package/package.json +7 -6
  258. package/php_compressed.js +38 -42
  259. package/php_compressed.js.map +1 -1
  260. package/python_compressed.js +26 -25
  261. package/python_compressed.js.map +1 -1
  262. package/blocks/all.js +0 -23
@@ -41,7 +41,7 @@ Python['math_arithmetic'] = function(block) {
41
41
  'MINUS': [' - ', Python.ORDER_ADDITIVE],
42
42
  'MULTIPLY': [' * ', Python.ORDER_MULTIPLICATIVE],
43
43
  'DIVIDE': [' / ', Python.ORDER_MULTIPLICATIVE],
44
- 'POWER': [' ** ', Python.ORDER_EXPONENTIATION]
44
+ 'POWER': [' ** ', Python.ORDER_EXPONENTIATION],
45
45
  };
46
46
  const tuple = OPERATORS[block.getFieldValue('OP')];
47
47
  const operator = tuple[0];
@@ -142,7 +142,7 @@ Python['math_constant'] = function(block) {
142
142
  'GOLDEN_RATIO': ['(1 + math.sqrt(5)) / 2', Python.ORDER_MULTIPLICATIVE],
143
143
  'SQRT2': ['math.sqrt(2)', Python.ORDER_MEMBER],
144
144
  'SQRT1_2': ['math.sqrt(1.0 / 2)', Python.ORDER_MEMBER],
145
- 'INFINITY': ['float(\'inf\')', Python.ORDER_ATOMIC]
145
+ 'INFINITY': ['float(\'inf\')', Python.ORDER_ATOMIC],
146
146
  };
147
147
  const constant = block.getFieldValue('CONSTANT');
148
148
  if (constant !== 'INFINITY') {
@@ -152,65 +152,62 @@ Python['math_constant'] = function(block) {
152
152
  };
153
153
 
154
154
  Python['math_number_property'] = function(block) {
155
- // Check if a number is even, odd, prime, whole, positive, or negative
156
- // or if it is divisible by certain number. Returns true or false.
157
- const number_to_check =
158
- Python.valueToCode(
159
- block, 'NUMBER_TO_CHECK', Python.ORDER_MULTIPLICATIVE) ||
160
- '0';
161
- const dropdown_property = block.getFieldValue('PROPERTY');
155
+ // Check if a number is even, odd, prime, whole, positive, or negative
156
+ // or if it is divisible by certain number. Returns true or false.
157
+ const PROPERTIES = {
158
+ 'EVEN': [' % 2 == 0', Python.ORDER_MULTIPLICATIVE, Python.ORDER_RELATIONAL],
159
+ 'ODD': [' % 2 == 1', Python.ORDER_MULTIPLICATIVE, Python.ORDER_RELATIONAL],
160
+ 'WHOLE': [' % 1 == 0', Python.ORDER_MULTIPLICATIVE,
161
+ Python.ORDER_RELATIONAL],
162
+ 'POSITIVE': [' > 0', Python.ORDER_RELATIONAL, Python.ORDER_RELATIONAL],
163
+ 'NEGATIVE': [' < 0', Python.ORDER_RELATIONAL, Python.ORDER_RELATIONAL],
164
+ 'DIVISIBLE_BY': [null, Python.ORDER_MULTIPLICATIVE,
165
+ Python.ORDER_RELATIONAL],
166
+ 'PRIME': [null, Python.ORDER_NONE, Python.ORDER_FUNCTION_CALL],
167
+ }
168
+ const dropdownProperty = block.getFieldValue('PROPERTY');
169
+ const [suffix, inputOrder, outputOrder] = PROPERTIES[dropdownProperty];
170
+ const numberToCheck = Python.valueToCode(block, 'NUMBER_TO_CHECK',
171
+ inputOrder) || '0';
162
172
  let code;
163
- if (dropdown_property === 'PRIME') {
173
+ if (dropdownProperty === 'PRIME') {
174
+ // Prime is a special case as it is not a one-liner test.
164
175
  Python.definitions_['import_math'] = 'import math';
165
176
  Python.definitions_['from_numbers_import_Number'] =
166
177
  'from numbers import Number';
167
- const functionName = Python.provideFunction_('math_isPrime', [
168
- 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(n):',
169
- ' # https://en.wikipedia.org/wiki/Primality_test#Naive_methods',
170
- ' # If n is not a number but a string, try parsing it.',
171
- ' if not isinstance(n, Number):', ' try:', ' n = float(n)',
172
- ' except:', ' return False',
173
- ' if n == 2 or n == 3:', ' return True',
174
- ' # False if n is negative, is 1, or not whole,' +
175
- ' or if n is divisible by 2 or 3.',
176
- ' if n <= 1 or n % 1 != 0 or n % 2 == 0 or n % 3 == 0:',
177
- ' return False',
178
- ' # Check all the numbers of form 6k +/- 1, up to sqrt(n).',
179
- ' for x in range(6, int(math.sqrt(n)) + 2, 6):',
180
- ' if n % (x - 1) == 0 or n % (x + 1) == 0:', ' return False',
181
- ' return True'
182
- ]);
183
- code = functionName + '(' + number_to_check + ')';
184
- return [code, Python.ORDER_FUNCTION_CALL];
185
- }
186
- switch (dropdown_property) {
187
- case 'EVEN':
188
- code = number_to_check + ' % 2 == 0';
189
- break;
190
- case 'ODD':
191
- code = number_to_check + ' % 2 == 1';
192
- break;
193
- case 'WHOLE':
194
- code = number_to_check + ' % 1 == 0';
195
- break;
196
- case 'POSITIVE':
197
- code = number_to_check + ' > 0';
198
- break;
199
- case 'NEGATIVE':
200
- code = number_to_check + ' < 0';
201
- break;
202
- case 'DIVISIBLE_BY': {
203
- const divisor =
204
- Python.valueToCode(block, 'DIVISOR', Python.ORDER_MULTIPLICATIVE);
205
- // If 'divisor' is some code that evals to 0, Python will raise an error.
206
- if (!divisor || divisor === '0') {
207
- return ['False', Python.ORDER_ATOMIC];
208
- }
209
- code = number_to_check + ' % ' + divisor + ' == 0';
210
- break;
178
+ const functionName = Python.provideFunction_('math_isPrime', `
179
+ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(n):
180
+ # https://en.wikipedia.org/wiki/Primality_test#Naive_methods
181
+ # If n is not a number but a string, try parsing it.
182
+ if not isinstance(n, Number):
183
+ try:
184
+ n = float(n)
185
+ except:
186
+ return False
187
+ if n == 2 or n == 3:
188
+ return True
189
+ # False if n is negative, is 1, or not whole, or if n is divisible by 2 or 3.
190
+ if n <= 1 or n % 1 != 0 or n % 2 == 0 or n % 3 == 0:
191
+ return False
192
+ # Check all the numbers of form 6k +/- 1, up to sqrt(n).
193
+ for x in range(6, int(math.sqrt(n)) + 2, 6):
194
+ if n % (x - 1) == 0 or n % (x + 1) == 0:
195
+ return False
196
+ return True
197
+ `);
198
+ code = functionName + '(' + numberToCheck + ')';
199
+ } else if (dropdownProperty === 'DIVISIBLE_BY') {
200
+ const divisor = Python.valueToCode(block, 'DIVISOR',
201
+ Python.ORDER_MULTIPLICATIVE) || '0';
202
+ // If 'divisor' is some code that evals to 0, Python will raise an error.
203
+ if (divisor === '0') {
204
+ return ['False', Python.ORDER_ATOMIC];
211
205
  }
212
- }
213
- return [code, Python.ORDER_RELATIONAL];
206
+ code = numberToCheck + ' % ' + divisor + ' == 0';
207
+ } else {
208
+ code = numberToCheck + suffix;
209
+ };
210
+ return [code, outputOrder];
214
211
  };
215
212
 
216
213
  Python['math_change'] = function(block) {
@@ -248,71 +245,72 @@ Python['math_on_list'] = function(block) {
248
245
  case 'AVERAGE': {
249
246
  Python.definitions_['from_numbers_import_Number'] =
250
247
  'from numbers import Number';
251
- const functionName = Python.provideFunction_(
252
- 'math_mean',
253
- // This operation excludes null and values that aren't int or float:
254
- // math_mean([null, null, "aString", 1, 9]) -> 5.0
255
- [
256
- 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(myList):',
257
- ' localList = [e for e in myList if isinstance(e, Number)]',
258
- ' if not localList: return',
259
- ' return float(sum(localList)) / len(localList)'
260
- ]);
248
+ // This operation excludes null and values that aren't int or float:
249
+ // math_mean([null, null, "aString", 1, 9]) -> 5.0
250
+ const functionName = Python.provideFunction_('math_mean', `
251
+ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(myList):
252
+ localList = [e for e in myList if isinstance(e, Number)]
253
+ if not localList: return
254
+ return float(sum(localList)) / len(localList)
255
+ `);
261
256
  code = functionName + '(' + list + ')';
262
257
  break;
263
258
  }
264
259
  case 'MEDIAN': {
265
260
  Python.definitions_['from_numbers_import_Number'] =
266
261
  'from numbers import Number';
267
- const functionName = Python.provideFunction_(
268
- 'math_median',
269
- // This operation excludes null values:
270
- // math_median([null, null, 1, 3]) -> 2.0
271
- [
272
- 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(myList):',
273
- ' localList = sorted([e for e in myList if isinstance(e, Number)])',
274
- ' if not localList: return', ' if len(localList) % 2 == 0:',
275
- ' return (localList[len(localList) // 2 - 1] + ' +
276
- 'localList[len(localList) // 2]) / 2.0',
277
- ' else:', ' return localList[(len(localList) - 1) // 2]'
278
- ]);
262
+ // This operation excludes null values:
263
+ // math_median([null, null, 1, 3]) -> 2.0
264
+ const functionName = Python.provideFunction_( 'math_median', `
265
+ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(myList):
266
+ localList = sorted([e for e in myList if isinstance(e, Number)])
267
+ if not localList: return
268
+ if len(localList) % 2 == 0:
269
+ return (localList[len(localList) // 2 - 1] + localList[len(localList) // 2]) / 2.0
270
+ else:
271
+ return localList[(len(localList) - 1) // 2]
272
+ `);
279
273
  code = functionName + '(' + list + ')';
280
274
  break;
281
275
  }
282
276
  case 'MODE': {
283
- const functionName = Python.provideFunction_(
284
- 'math_modes',
285
- // As a list of numbers can contain more than one mode,
286
- // the returned result is provided as an array.
287
- // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1]
288
- [
289
- 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(some_list):',
290
- ' modes = []',
291
- ' # Using a lists of [item, count] to keep count rather than dict',
292
- ' # to avoid "unhashable" errors when the counted item is ' +
293
- 'itself a list or dict.',
294
- ' counts = []', ' maxCount = 1', ' for item in some_list:',
295
- ' found = False', ' for count in counts:',
296
- ' if count[0] == item:', ' count[1] += 1',
297
- ' maxCount = max(maxCount, count[1])',
298
- ' found = True',
299
- ' if not found:', ' counts.append([item, 1])',
300
- ' for counted_item, item_count in counts:',
301
- ' if item_count == maxCount:',
302
- ' modes.append(counted_item)', ' return modes'
303
- ]);
277
+ // As a list of numbers can contain more than one mode,
278
+ // the returned result is provided as an array.
279
+ // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1]
280
+ const functionName = Python.provideFunction_('math_modes', `
281
+ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(some_list):
282
+ modes = []
283
+ # Using a lists of [item, count] to keep count rather than dict
284
+ # to avoid "unhashable" errors when the counted item is itself a list or dict.
285
+ counts = []
286
+ maxCount = 1
287
+ for item in some_list:
288
+ found = False
289
+ for count in counts:
290
+ if count[0] == item:
291
+ count[1] += 1
292
+ maxCount = max(maxCount, count[1])
293
+ found = True
294
+ if not found:
295
+ counts.append([item, 1])
296
+ for counted_item, item_count in counts:
297
+ if item_count == maxCount:
298
+ modes.append(counted_item)
299
+ return modes
300
+ `);
304
301
  code = functionName + '(' + list + ')';
305
302
  break;
306
303
  }
307
304
  case 'STD_DEV': {
308
305
  Python.definitions_['import_math'] = 'import math';
309
- const functionName = Python.provideFunction_('math_standard_deviation', [
310
- 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(numbers):',
311
- ' n = len(numbers)', ' if n == 0: return',
312
- ' mean = float(sum(numbers)) / n',
313
- ' variance = sum((x - mean) ** 2 for x in numbers) / n',
314
- ' return math.sqrt(variance)'
315
- ]);
306
+ const functionName = Python.provideFunction_('math_standard_deviation', `
307
+ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(numbers):
308
+ n = len(numbers)
309
+ if n == 0: return
310
+ mean = float(sum(numbers)) / n
311
+ variance = sum((x - mean) ** 2 for x in numbers) / n
312
+ return math.sqrt(variance)
313
+ `);
316
314
  code = functionName + '(' + list + ')';
317
315
  break;
318
316
  }
@@ -55,18 +55,18 @@ Python['text_join'] = function(block) {
55
55
  // Should we allow joining by '-' or ',' or any other characters?
56
56
  switch (block.itemCount_) {
57
57
  case 0:
58
- return ['\'\'', Python.ORDER_ATOMIC];
58
+ return ["''", Python.ORDER_ATOMIC];
59
59
  case 1: {
60
60
  const element =
61
- Python.valueToCode(block, 'ADD0', Python.ORDER_NONE) || '\'\'';
61
+ Python.valueToCode(block, 'ADD0', Python.ORDER_NONE) || "''";
62
62
  const codeAndOrder = forceString(element);
63
63
  return codeAndOrder;
64
64
  }
65
65
  case 2: {
66
66
  const element0 =
67
- Python.valueToCode(block, 'ADD0', Python.ORDER_NONE) || '\'\'';
67
+ Python.valueToCode(block, 'ADD0', Python.ORDER_NONE) || "''";
68
68
  const element1 =
69
- Python.valueToCode(block, 'ADD1', Python.ORDER_NONE) || '\'\'';
69
+ Python.valueToCode(block, 'ADD1', Python.ORDER_NONE) || "''";
70
70
  const code = forceString(element0)[0] + ' + ' + forceString(element1)[0];
71
71
  return [code, Python.ORDER_ADDITIVE];
72
72
  }
@@ -74,7 +74,7 @@ Python['text_join'] = function(block) {
74
74
  const elements = [];
75
75
  for (let i = 0; i < block.itemCount_; i++) {
76
76
  elements[i] =
77
- Python.valueToCode(block, 'ADD' + i, Python.ORDER_NONE) || '\'\'';
77
+ Python.valueToCode(block, 'ADD' + i, Python.ORDER_NONE) || "''";
78
78
  }
79
79
  const tempVar = Python.nameDB_.getDistinctName('x', NameType.VARIABLE);
80
80
  const code = '\'\'.join([str(' + tempVar + ') for ' + tempVar + ' in [' +
@@ -88,19 +88,19 @@ Python['text_append'] = function(block) {
88
88
  // Append to a variable in place.
89
89
  const varName =
90
90
  Python.nameDB_.getName(block.getFieldValue('VAR'), NameType.VARIABLE);
91
- const value = Python.valueToCode(block, 'TEXT', Python.ORDER_NONE) || '\'\'';
91
+ const value = Python.valueToCode(block, 'TEXT', Python.ORDER_NONE) || "''";
92
92
  return varName + ' = str(' + varName + ') + ' + forceString(value)[0] + '\n';
93
93
  };
94
94
 
95
95
  Python['text_length'] = function(block) {
96
96
  // Is the string null or array empty?
97
- const text = Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || '\'\'';
97
+ const text = Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || "''";
98
98
  return ['len(' + text + ')', Python.ORDER_FUNCTION_CALL];
99
99
  };
100
100
 
101
101
  Python['text_isEmpty'] = function(block) {
102
102
  // Is the string null or array empty?
103
- const text = Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || '\'\'';
103
+ const text = Python.valueToCode(block, 'VALUE', Python.ORDER_NONE) || "''";
104
104
  const code = 'not len(' + text + ')';
105
105
  return [code, Python.ORDER_LOGICAL_NOT];
106
106
  };
@@ -110,9 +110,9 @@ Python['text_indexOf'] = function(block) {
110
110
  // Should we allow for non-case sensitive???
111
111
  const operator = block.getFieldValue('END') === 'FIRST' ? 'find' : 'rfind';
112
112
  const substring =
113
- Python.valueToCode(block, 'FIND', Python.ORDER_NONE) || '\'\'';
113
+ Python.valueToCode(block, 'FIND', Python.ORDER_NONE) || "''";
114
114
  const text =
115
- Python.valueToCode(block, 'VALUE', Python.ORDER_MEMBER) || '\'\'';
115
+ Python.valueToCode(block, 'VALUE', Python.ORDER_MEMBER) || "''";
116
116
  const code = text + '.' + operator + '(' + substring + ')';
117
117
  if (block.workspace.options.oneBasedIndex) {
118
118
  return [code + ' + 1', Python.ORDER_ADDITIVE];
@@ -126,7 +126,7 @@ Python['text_charAt'] = function(block) {
126
126
  const where = block.getFieldValue('WHERE') || 'FROM_START';
127
127
  const textOrder =
128
128
  (where === 'RANDOM') ? Python.ORDER_NONE : Python.ORDER_MEMBER;
129
- const text = Python.valueToCode(block, 'VALUE', textOrder) || '\'\'';
129
+ const text = Python.valueToCode(block, 'VALUE', textOrder) || "''";
130
130
  switch (where) {
131
131
  case 'FIRST': {
132
132
  const code = text + '[0]';
@@ -148,10 +148,11 @@ Python['text_charAt'] = function(block) {
148
148
  }
149
149
  case 'RANDOM': {
150
150
  Python.definitions_['import_random'] = 'import random';
151
- const functionName = Python.provideFunction_('text_random_letter', [
152
- 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(text):',
153
- ' x = int(random.random() * len(text))', ' return text[x];'
154
- ]);
151
+ const functionName = Python.provideFunction_('text_random_letter', `
152
+ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(text):
153
+ x = int(random.random() * len(text))
154
+ return text[x]
155
+ `);
155
156
  const code = functionName + '(' + text + ')';
156
157
  return [code, Python.ORDER_FUNCTION_CALL];
157
158
  }
@@ -164,7 +165,7 @@ Python['text_getSubstring'] = function(block) {
164
165
  const where1 = block.getFieldValue('WHERE1');
165
166
  const where2 = block.getFieldValue('WHERE2');
166
167
  const text =
167
- Python.valueToCode(block, 'STRING', Python.ORDER_MEMBER) || '\'\'';
168
+ Python.valueToCode(block, 'STRING', Python.ORDER_MEMBER) || "''";
168
169
  let at1;
169
170
  switch (where1) {
170
171
  case 'FROM_START':
@@ -217,7 +218,7 @@ Python['text_changeCase'] = function(block) {
217
218
  'TITLECASE': '.title()'
218
219
  };
219
220
  const operator = OPERATORS[block.getFieldValue('CASE')];
220
- const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || '\'\'';
221
+ const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || "''";
221
222
  const code = text + operator;
222
223
  return [code, Python.ORDER_FUNCTION_CALL];
223
224
  };
@@ -230,30 +231,33 @@ Python['text_trim'] = function(block) {
230
231
  'BOTH': '.strip()'
231
232
  };
232
233
  const operator = OPERATORS[block.getFieldValue('MODE')];
233
- const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || '\'\'';
234
+ const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || "''";
234
235
  const code = text + operator;
235
236
  return [code, Python.ORDER_FUNCTION_CALL];
236
237
  };
237
238
 
238
239
  Python['text_print'] = function(block) {
239
240
  // Print statement.
240
- const msg = Python.valueToCode(block, 'TEXT', Python.ORDER_NONE) || '\'\'';
241
+ const msg = Python.valueToCode(block, 'TEXT', Python.ORDER_NONE) || "''";
241
242
  return 'print(' + msg + ')\n';
242
243
  };
243
244
 
244
245
  Python['text_prompt_ext'] = function(block) {
245
246
  // Prompt function.
246
- const functionName = Python.provideFunction_('text_prompt', [
247
- 'def ' + Python.FUNCTION_NAME_PLACEHOLDER_ + '(msg):', ' try:',
248
- ' return raw_input(msg)', ' except NameError:', ' return input(msg)'
249
- ]);
247
+ const functionName = Python.provideFunction_('text_prompt', `
248
+ def ${Python.FUNCTION_NAME_PLACEHOLDER_}(msg):
249
+ try:
250
+ return raw_input(msg)
251
+ except NameError:
252
+ return input(msg)
253
+ `);
250
254
  let msg;
251
255
  if (block.getField('TEXT')) {
252
256
  // Internal message.
253
257
  msg = Python.quote_(block.getFieldValue('TEXT'));
254
258
  } else {
255
259
  // External message.
256
- msg = Python.valueToCode(block, 'TEXT', Python.ORDER_NONE) || '\'\'';
260
+ msg = Python.valueToCode(block, 'TEXT', Python.ORDER_NONE) || "''";
257
261
  }
258
262
  let code = functionName + '(' + msg + ')';
259
263
  const toNumber = block.getFieldValue('TYPE') === 'NUMBER';
@@ -266,22 +270,22 @@ Python['text_prompt_ext'] = function(block) {
266
270
  Python['text_prompt'] = Python['text_prompt_ext'];
267
271
 
268
272
  Python['text_count'] = function(block) {
269
- const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || '\'\'';
270
- const sub = Python.valueToCode(block, 'SUB', Python.ORDER_NONE) || '\'\'';
273
+ const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || "''";
274
+ const sub = Python.valueToCode(block, 'SUB', Python.ORDER_NONE) || "''";
271
275
  const code = text + '.count(' + sub + ')';
272
276
  return [code, Python.ORDER_FUNCTION_CALL];
273
277
  };
274
278
 
275
279
  Python['text_replace'] = function(block) {
276
- const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || '\'\'';
277
- const from = Python.valueToCode(block, 'FROM', Python.ORDER_NONE) || '\'\'';
278
- const to = Python.valueToCode(block, 'TO', Python.ORDER_NONE) || '\'\'';
280
+ const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || "''";
281
+ const from = Python.valueToCode(block, 'FROM', Python.ORDER_NONE) || "''";
282
+ const to = Python.valueToCode(block, 'TO', Python.ORDER_NONE) || "''";
279
283
  const code = text + '.replace(' + from + ', ' + to + ')';
280
284
  return [code, Python.ORDER_MEMBER];
281
285
  };
282
286
 
283
287
  Python['text_reverse'] = function(block) {
284
- const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || '\'\'';
288
+ const text = Python.valueToCode(block, 'TEXT', Python.ORDER_MEMBER) || "''";
285
289
  const code = text + '[::-1]';
286
290
  return [code, Python.ORDER_MEMBER];
287
291
  };