kapusta 0.13.2 → 0.14.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +50 -11
  3. data/bin/check-all +6 -1
  4. data/bin/compile-examples +7 -2
  5. data/examples/anagram.kap +3 -3
  6. data/examples/app/args.kap +9 -0
  7. data/examples/arrange-coins.kap +3 -3
  8. data/examples/baseball-game.kap +5 -5
  9. data/examples/best-time-to-buy-sell-stock.kap +2 -2
  10. data/examples/binary-search.kap +2 -2
  11. data/examples/binary-to-decimal.kap +1 -1
  12. data/examples/blocks-and-kwargs.kap +2 -2
  13. data/examples/count-effects.kap +1 -1
  14. data/examples/count-items-matching-rule.kap +18 -0
  15. data/examples/doto-hygiene.kap +2 -2
  16. data/examples/doto.kap +2 -2
  17. data/examples/egg-count.kap +1 -1
  18. data/examples/exceptions.kap +1 -1
  19. data/examples/falling-drops.kap +7 -7
  20. data/examples/fennel-parity-examples.txt +3 -6
  21. data/examples/good-pairs.kap +18 -0
  22. data/examples/greet.kap +1 -1
  23. data/examples/happy-number.kap +2 -2
  24. data/examples/left-right-difference.kap +60 -0
  25. data/examples/length-of-last-word.kap +4 -4
  26. data/examples/manhattan-distance.kap +1 -1
  27. data/examples/maximum-subarray.kap +23 -7
  28. data/examples/minimum-start-value.kap +19 -0
  29. data/examples/move-zeroes.kap +2 -2
  30. data/examples/mruby-runtime-examples.txt +8 -2
  31. data/examples/non-constant-local.kap +1 -1
  32. data/examples/number-of-1-bits.kap +1 -1
  33. data/examples/number-of-steps.kap +1 -1
  34. data/examples/palindrome.kap +2 -2
  35. data/examples/pangram.kap +4 -4
  36. data/examples/pcall.kap +3 -3
  37. data/examples/pipeline.kap +4 -4
  38. data/examples/plus-one.kap +3 -3
  39. data/examples/raindrops.kap +1 -1
  40. data/examples/range-width.kap +14 -0
  41. data/examples/recent-counter.kap +6 -6
  42. data/examples/require-local-args.kap +9 -0
  43. data/examples/require-local.kap +7 -0
  44. data/examples/require-module-local.kap +4 -0
  45. data/examples/require-module.kap +4 -0
  46. data/examples/reverse-integer.kap +1 -1
  47. data/examples/roman-to-integer.kap +3 -3
  48. data/examples/running-sum.kap +20 -0
  49. data/examples/safe-lookup.kap +2 -2
  50. data/examples/single-number.kap +1 -1
  51. data/examples/stack.kap +14 -14
  52. data/examples/subtract-product-sum.kap +1 -1
  53. data/examples/summary-ranges.kap +45 -0
  54. data/examples/threading.kap +5 -5
  55. data/examples/tset.kap +1 -1
  56. data/examples/two-sum-hash.kap +3 -3
  57. data/examples/two-sum.kap +1 -1
  58. data/examples/ugly-number.kap +1 -1
  59. data/examples/underground-system.kap +39 -0
  60. data/examples/valid-parentheses-1.kap +6 -6
  61. data/exe/kapusta-ls +49 -2
  62. data/lib/kapusta/ast.rb +8 -0
  63. data/lib/kapusta/compiler/emitter/bindings.rb +111 -89
  64. data/lib/kapusta/compiler/emitter/collections.rb +32 -40
  65. data/lib/kapusta/compiler/emitter/control_flow.rb +33 -31
  66. data/lib/kapusta/compiler/emitter/expressions.rb +21 -5
  67. data/lib/kapusta/compiler/emitter/interop.rb +168 -48
  68. data/lib/kapusta/compiler/emitter/patterns.rb +12 -14
  69. data/lib/kapusta/compiler/emitter/support.rb +63 -81
  70. data/lib/kapusta/compiler/language.rb +522 -0
  71. data/lib/kapusta/compiler/lua_compat.rb +23 -28
  72. data/lib/kapusta/compiler/macro_expander.rb +30 -30
  73. data/lib/kapusta/compiler/macro_lowerer.rb +12 -24
  74. data/lib/kapusta/compiler/normalizer.rb +25 -17
  75. data/lib/kapusta/compiler.rb +3 -24
  76. data/lib/kapusta/env.rb +2 -2
  77. data/lib/kapusta/errors.rb +2 -1
  78. data/lib/kapusta/formatter/ast_helpers.rb +78 -0
  79. data/lib/kapusta/formatter/cli.rb +125 -0
  80. data/lib/kapusta/formatter/line_helpers.rb +44 -0
  81. data/lib/kapusta/formatter/validator.rb +32 -0
  82. data/lib/kapusta/formatter.rb +354 -325
  83. data/lib/kapusta/lsp/identifier.rb +1 -1
  84. data/lib/kapusta/lsp/rename.rb +21 -11
  85. data/lib/kapusta/lsp/scope_walker.rb +122 -212
  86. data/lib/kapusta/lsp/workspace_index.rb +17 -5
  87. data/lib/kapusta/reader.rb +4 -2
  88. data/lib/kapusta/version.rb +1 -1
  89. data/lib/kapusta.rb +39 -6
  90. data/spec/cli_spec.rb +13 -0
  91. data/spec/examples_errors_spec.rb +3 -1
  92. data/spec/examples_spec.rb +67 -15
  93. data/spec/formatter_spec.rb +246 -0
  94. data/spec/lsp_spec.rb +69 -0
  95. data/spec/require_spec.rb +294 -0
  96. metadata +20 -2
  97. data/examples/describe.kap +0 -9
@@ -0,0 +1,522 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kapusta
4
+ module Compiler
5
+ module Language
6
+ FunctionForm = Struct.new(:head, :name, :params, :body, :prefix_length, keyword_init: true) do
7
+ def named? = !name.nil?
8
+
9
+ def anonymous? = name.nil?
10
+ end
11
+ ModuleForm = Struct.new(:name, :body, :prefix_length, keyword_init: true)
12
+ ClassForm = Struct.new(:name, :supers, :body, :prefix_length, keyword_init: true)
13
+ TryForm = Struct.new(:body, :clauses, keyword_init: true)
14
+ CatchClause = Struct.new(:klass, :bind_sym, :body, keyword_init: true)
15
+ FinallyClause = Struct.new(:body, keyword_init: true)
16
+ BindingForm = Struct.new(:head, :target, :value, keyword_init: true) do
17
+ def mutable? = head == 'var'
18
+ end
19
+ GlobalForm = Struct.new(:name, :value, keyword_init: true)
20
+ SetForm = Struct.new(:target, :value, keyword_init: true)
21
+ DotTarget = Struct.new(:object, :keys, keyword_init: true)
22
+ TsetForm = Struct.new(:table, :key, :value, keyword_init: true)
23
+ ConditionalBodyForm = Struct.new(:condition, :body, keyword_init: true) do
24
+ def body? = !body.empty?
25
+ end
26
+ ValuesForm = Struct.new(:key, :value, keyword_init: true)
27
+ LuaIteratorForm = Struct.new(:head, :collection, keyword_init: true) do
28
+ def name = head.name
29
+ end
30
+ LuaPcallForm = Struct.new(:callable, :args, keyword_init: true)
31
+ LuaXpcallForm = Struct.new(:callable, :handler, :args, keyword_init: true)
32
+ SigilForm = Struct.new(:head, :name, keyword_init: true) do
33
+ def kind = head.to_sym
34
+ end
35
+ MacroDefinitionForm = Struct.new(:name, :params, :body, keyword_init: true)
36
+ ImportMacrosForm = Struct.new(:destructure, :module_arg, keyword_init: true)
37
+ LetForm = Struct.new(:bindings, :body, keyword_init: true) do
38
+ def binding_pairs
39
+ return [] unless bindings.is_a?(Vec)
40
+
41
+ bindings.items.each_slice(2).map { |pattern, value| [pattern, value] }
42
+ end
43
+ end
44
+ CaseForm = Struct.new(:subject, :clauses, keyword_init: true) do
45
+ def arm_pairs
46
+ clauses.each_slice(2).to_a
47
+ end
48
+
49
+ def complete_arms
50
+ arm_pairs.select { |pair| pair.length == 2 }
51
+ end
52
+ end
53
+ WherePattern = Struct.new(:inner, :guards, keyword_init: true)
54
+ OrPattern = Struct.new(:alternatives, keyword_init: true)
55
+ PinPattern = Struct.new(:name, keyword_init: true)
56
+ UnpackCall = Struct.new(:value, keyword_init: true)
57
+ HashFnForm = Struct.new(:body, keyword_init: true)
58
+ IterationForm = Struct.new(:bindings, :body, keyword_init: true) do
59
+ def items = bindings.is_a?(Vec) ? bindings.items : []
60
+
61
+ def iter_expr = items.last
62
+
63
+ def binding_pats = items[0...-1] || []
64
+ end
65
+ AccumulateForm = Struct.new(:bindings, :body, keyword_init: true) do
66
+ def items = bindings.is_a?(Vec) ? bindings.items : []
67
+
68
+ def acc_name = items[0]
69
+
70
+ def initial = items[1]
71
+
72
+ def iter_items = items[2..] || []
73
+
74
+ def iter_expr = iter_items.last
75
+
76
+ def binding_pats = iter_items[0...-1] || []
77
+ end
78
+ FaccumulateForm = Struct.new(:bindings, :body, keyword_init: true) do
79
+ def items = bindings.is_a?(Vec) ? bindings.items : []
80
+
81
+ def acc_name = items[0]
82
+
83
+ def initial = items[1]
84
+
85
+ def counter = items[2]
86
+
87
+ def start = items[3]
88
+
89
+ def finish = items[4]
90
+
91
+ def step = items[5]
92
+ end
93
+ CountedForForm = Struct.new(:bindings, :body, keyword_init: true) do
94
+ def items = bindings.is_a?(Vec) ? bindings.items : []
95
+
96
+ def counter = items[0]
97
+
98
+ def start = items[1]
99
+
100
+ def finish = items[2]
101
+
102
+ def extras = items[3..] || []
103
+
104
+ def each_extra
105
+ i = 0
106
+ while i < extras.length
107
+ item = extras[i]
108
+ if item.is_a?(Sym) && item.name == '&until'
109
+ yield :until, extras[i + 1]
110
+ i += 2
111
+ else
112
+ yield :step, item
113
+ i += 1
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ FUNCTION_HEADS = %w[fn lambda λ].freeze
120
+ FUNCTION_DEFINITION_HEADS = (FUNCTION_HEADS + %w[defn]).freeze
121
+ MACRO_FUNCTION_HEADS = (FUNCTION_HEADS + %w[macro]).freeze
122
+ BINDING_HEADS = %w[local var].freeze
123
+ HEADER_HEADS = %w[class module].freeze
124
+ HEADER_SCOPES = %i[module class].freeze
125
+ DEFINITION_SCOPES = ([:toplevel] + HEADER_SCOPES).freeze
126
+ THREAD_HEADS = %w[-> ->> -?> -?>>].freeze
127
+ PIPELINE_HEADS = (THREAD_HEADS + %w[doto]).freeze
128
+ SHORT_PIPELINE_HEADS = %w[-?> -?>>].freeze
129
+ THREAD_FIRST_HEADS = %w[-> -?>].freeze
130
+ SEQUENCE_STATEMENT_HEADS = %w[let while for each case match].freeze
131
+ MULTILINE_BODY_HEADS = %w[
132
+ if case match let try catch finally do for -> ->> -?> -?>> doto
133
+ fn lambda λ macro
134
+ ].freeze
135
+ FLAT_BODY_HEADS = %w[
136
+ fn lambda λ macro when unless for each icollect collect fcollect
137
+ accumulate faccumulate
138
+ ].freeze
139
+ NEVER_FLAT_HEADS = %w[let case match try catch finally do -> ->> -?> -?>> doto].freeze
140
+ QUASI_HEADS = %w[
141
+ quasi-sym quasi-list quasi-list-tail quasi-vec quasi-vec-tail
142
+ quasi-hash quasi-gensym
143
+ ].freeze
144
+ CORE_SPECIAL_FORMS = %w[
145
+ fn defn lambda λ let local var global set if when unless case match
146
+ while for each do values
147
+ -> ->> -?> -?>> doto
148
+ icollect collect fcollect accumulate faccumulate
149
+ hashfn
150
+ . ?: :
151
+ ..
152
+ length
153
+ require
154
+ module class end
155
+ try catch finally
156
+ raise
157
+ ivar cvar gvar
158
+ ruby
159
+ tset
160
+ and or not
161
+ = not= < <= > >=
162
+ + - * / %
163
+ print
164
+ macro macros import-macros
165
+ quasi-sym quasi-list quasi-list-tail quasi-vec quasi-vec-tail quasi-hash quasi-gensym
166
+ ].freeze
167
+ SPECIAL_FORMS = (CORE_SPECIAL_FORMS + LuaCompat::SPECIAL_FORMS).freeze
168
+
169
+ module_function
170
+
171
+ def list_head(form)
172
+ return unless form.is_a?(List) && !form.empty?
173
+
174
+ form.head
175
+ end
176
+
177
+ def list_head_name(form)
178
+ head = list_head(form)
179
+ head.name if head.is_a?(Sym)
180
+ end
181
+
182
+ def list_head?(form, *names)
183
+ name = list_head_name(form)
184
+ !name.nil? && names.flatten.include?(name)
185
+ end
186
+
187
+ def end_form?(form) = list_head?(form, 'end')
188
+
189
+ def do_form?(form) = list_head?(form, 'do')
190
+
191
+ def defn_form?(form) = list_head?(form, 'defn')
192
+
193
+ def header_form?(form)
194
+ name = list_head_name(form)
195
+ !name.nil? && header_head?(name)
196
+ end
197
+
198
+ def sequence_statement_form?(form)
199
+ name = list_head_name(form)
200
+ !name.nil? && sequence_statement_head?(name)
201
+ end
202
+
203
+ def binding_form?(form)
204
+ name = list_head_name(form)
205
+ !name.nil? && binding_head?(name)
206
+ end
207
+
208
+ def special_form?(name) = SPECIAL_FORMS.include?(name)
209
+
210
+ def function_head?(name) = FUNCTION_HEADS.include?(name)
211
+
212
+ def function_definition_head?(name) = FUNCTION_DEFINITION_HEADS.include?(name)
213
+
214
+ def macro_function_head?(name) = MACRO_FUNCTION_HEADS.include?(name)
215
+
216
+ def binding_head?(name) = BINDING_HEADS.include?(name)
217
+
218
+ def header_head?(name) = HEADER_HEADS.include?(name)
219
+
220
+ def header_scope?(scope) = HEADER_SCOPES.include?(scope)
221
+
222
+ def definition_scope?(scope) = DEFINITION_SCOPES.include?(scope)
223
+
224
+ def pipeline_head?(name) = PIPELINE_HEADS.include?(name)
225
+
226
+ def short_pipeline_head?(name) = SHORT_PIPELINE_HEADS.include?(name)
227
+
228
+ def thread_first_head?(name) = THREAD_FIRST_HEADS.include?(name)
229
+
230
+ def sequence_statement_head?(name) = SEQUENCE_STATEMENT_HEADS.include?(name)
231
+
232
+ def multiline_body_head?(name) = MULTILINE_BODY_HEADS.include?(name)
233
+
234
+ def flat_body_head?(name) = FLAT_BODY_HEADS.include?(name)
235
+
236
+ def never_flat_head?(name) = NEVER_FLAT_HEADS.include?(name)
237
+
238
+ def quasi_head?(name) = QUASI_HEADS.include?(name)
239
+
240
+ def bodyless_header?(form)
241
+ return false unless header_form?(form)
242
+
243
+ case form.head.name
244
+ when 'module'
245
+ parsed = parse_module_form(form)
246
+ parsed.body.empty? || (parsed.body.length == 1 && bodyless_header?(parsed.body[0]))
247
+ when 'class'
248
+ parse_class_form(form).body.empty?
249
+ else
250
+ false
251
+ end
252
+ end
253
+
254
+ def function_form?(form, heads: FUNCTION_HEADS)
255
+ !parse_function_form(form, heads:).nil?
256
+ end
257
+
258
+ def parse_function_form(form, heads: FUNCTION_HEADS)
259
+ return unless list_head?(form, heads)
260
+
261
+ parse_function_args(form.rest, head: form.head)
262
+ end
263
+
264
+ def parse_function_args(args, head: nil)
265
+ if args[0].is_a?(Vec)
266
+ FunctionForm.new(head:, name: nil, params: args[0], body: args[1..] || [], prefix_length: 1)
267
+ elsif args[0].is_a?(Sym) && args[1].is_a?(Vec)
268
+ FunctionForm.new(head:, name: args[0], params: args[1], body: args[2..] || [], prefix_length: 2)
269
+ end
270
+ end
271
+
272
+ def parse_class_form(form)
273
+ return unless list_head?(form, 'class')
274
+
275
+ parse_class_args(form.rest)
276
+ end
277
+
278
+ def parse_module_form(form)
279
+ return unless list_head?(form, 'module')
280
+
281
+ parse_module_args(form.rest)
282
+ end
283
+
284
+ def parse_module_args(args)
285
+ ModuleForm.new(name: args[0], body: args[1..] || [], prefix_length: 1)
286
+ end
287
+
288
+ def parse_class_args(args)
289
+ if args[1].is_a?(Vec)
290
+ ClassForm.new(name: args[0], supers: args[1], body: args[2..] || [], prefix_length: 2)
291
+ else
292
+ ClassForm.new(name: args[0], supers: nil, body: args[1..] || [], prefix_length: 1)
293
+ end
294
+ end
295
+
296
+ def parse_try_args(args)
297
+ TryForm.new(body: args[0], clauses: (args[1..] || []).filter_map { |clause| parse_try_clause(clause) })
298
+ end
299
+
300
+ def parse_try_clause(clause)
301
+ return unless list_head_name(clause)
302
+
303
+ case clause.head.name
304
+ when 'catch' then parse_catch_clause(clause.rest)
305
+ when 'finally' then FinallyClause.new(body: clause.rest)
306
+ end
307
+ end
308
+
309
+ def parse_catch_clause(args)
310
+ if exception_class_form?(args[0])
311
+ CatchClause.new(klass: args[0], bind_sym: args[1], body: args[2..] || [])
312
+ else
313
+ CatchClause.new(klass: nil, bind_sym: args[0], body: args[1..] || [])
314
+ end
315
+ end
316
+
317
+ def exception_class_form?(form)
318
+ form.is_a?(Sym) && (form.name.match?(/\A[A-Z]/) || form.dotted?)
319
+ end
320
+
321
+ def parse_binding_form(form)
322
+ return unless binding_form?(form)
323
+
324
+ parse_binding_args(form.head.name, form.rest)
325
+ end
326
+
327
+ def parse_binding_args(head, args)
328
+ return unless args.length == 2
329
+
330
+ BindingForm.new(head:, target: args[0], value: args[1])
331
+ end
332
+
333
+ def parse_global_args(args)
334
+ return unless args.length == 2
335
+
336
+ GlobalForm.new(name: args[0], value: args[1])
337
+ end
338
+
339
+ def parse_global_form(form)
340
+ return unless list_head?(form, 'global')
341
+
342
+ parse_global_args(form.rest)
343
+ end
344
+
345
+ def parse_set_form(form)
346
+ return unless list_head?(form, 'set')
347
+
348
+ parse_set_args(form.rest)
349
+ end
350
+
351
+ def parse_set_args(args)
352
+ SetForm.new(target: args[0], value: args[1])
353
+ end
354
+
355
+ def parse_dot_target(form)
356
+ return unless list_head?(form, ':')
357
+
358
+ DotTarget.new(object: form.items[1], keys: form.items[2..] || [])
359
+ end
360
+
361
+ def parse_tset_args(args)
362
+ return unless args.length >= 3
363
+
364
+ TsetForm.new(table: args[0], key: args[1], value: args[2])
365
+ end
366
+
367
+ def parse_conditional_body_args(args)
368
+ ConditionalBodyForm.new(condition: args[0], body: args[1..] || [])
369
+ end
370
+
371
+ def parse_values_form(form)
372
+ return unless list_head?(form, 'values') && form.items.length == 3
373
+
374
+ ValuesForm.new(key: form.items[1], value: form.items[2])
375
+ end
376
+
377
+ def parse_lua_iterator_form(form)
378
+ head = list_head(form)
379
+ return unless head.is_a?(Sym) && LuaCompat.iterator_form?(head.name)
380
+
381
+ LuaIteratorForm.new(head:, collection: form.items[1])
382
+ end
383
+
384
+ def parse_lua_pcall_args(args)
385
+ LuaPcallForm.new(callable: args[0], args: args[1..] || [])
386
+ end
387
+
388
+ def parse_lua_xpcall_args(args)
389
+ LuaXpcallForm.new(callable: args[0], handler: args[1], args: args[2..] || [])
390
+ end
391
+
392
+ def parse_sigil_form(form)
393
+ head = list_head_name(form)
394
+ return unless head
395
+
396
+ parse_sigil_args(head, form.rest)
397
+ end
398
+
399
+ def parse_sigil_args(head, args)
400
+ return unless %w[ivar cvar gvar].include?(head)
401
+
402
+ SigilForm.new(head:, name: args[0])
403
+ end
404
+
405
+ def parse_hashfn_form(form)
406
+ return unless list_head?(form, 'hashfn')
407
+
408
+ HashFnForm.new(body: form.rest)
409
+ end
410
+
411
+ def parse_macro_definition_args(args)
412
+ MacroDefinitionForm.new(name: args[0], params: args[1], body: args[2..] || [])
413
+ end
414
+
415
+ def parse_macro_definition_form(form)
416
+ return unless list_head?(form, 'macro')
417
+
418
+ parse_macro_definition_args(form.rest)
419
+ end
420
+
421
+ def parse_import_macros_args(args)
422
+ ImportMacrosForm.new(destructure: args[0], module_arg: args[1])
423
+ end
424
+
425
+ def parse_import_macros_form(form)
426
+ return unless list_head?(form, 'import-macros')
427
+
428
+ parse_import_macros_args(form.rest)
429
+ end
430
+
431
+ def parse_let_args(args)
432
+ LetForm.new(bindings: args[0], body: args[1..] || [])
433
+ end
434
+
435
+ def parse_let_form(form)
436
+ return unless list_head?(form, 'let')
437
+
438
+ parse_let_args(form.rest)
439
+ end
440
+
441
+ def parse_case_args(args)
442
+ CaseForm.new(subject: args[0], clauses: args[1..] || [])
443
+ end
444
+
445
+ def parse_case_form(form)
446
+ return unless list_head?(form, %w[case match])
447
+
448
+ parse_case_args(form.rest)
449
+ end
450
+
451
+ def parse_try_form(form)
452
+ return unless list_head?(form, 'try')
453
+
454
+ parse_try_args(form.rest)
455
+ end
456
+
457
+ def parse_where_pattern(pattern)
458
+ return unless list_head?(pattern, 'where')
459
+
460
+ WherePattern.new(inner: pattern.items[1], guards: pattern.items[2..] || [])
461
+ end
462
+
463
+ def parse_or_pattern(pattern)
464
+ return unless list_head?(pattern, 'or')
465
+
466
+ OrPattern.new(alternatives: pattern.items[1..] || [])
467
+ end
468
+
469
+ def parse_pin_pattern(pattern)
470
+ return unless list_head?(pattern, '=') && pattern.items.length == 2
471
+
472
+ PinPattern.new(name: pattern.items[1])
473
+ end
474
+
475
+ def parse_unpack_call(form)
476
+ return unless list_head?(form, 'unpack')
477
+
478
+ UnpackCall.new(value: form.items[1])
479
+ end
480
+
481
+ def parse_iteration_args(args)
482
+ IterationForm.new(bindings: args[0], body: args[1..] || [])
483
+ end
484
+
485
+ def parse_iteration_form(form)
486
+ return unless list_head?(form, %w[each collect icollect])
487
+
488
+ parse_iteration_args(form.rest)
489
+ end
490
+
491
+ def parse_accumulate_args(args)
492
+ AccumulateForm.new(bindings: args[0], body: args[1..] || [])
493
+ end
494
+
495
+ def parse_accumulate_form(form)
496
+ return unless list_head?(form, 'accumulate')
497
+
498
+ parse_accumulate_args(form.rest)
499
+ end
500
+
501
+ def parse_faccumulate_args(args)
502
+ FaccumulateForm.new(bindings: args[0], body: args[1..] || [])
503
+ end
504
+
505
+ def parse_faccumulate_form(form)
506
+ return unless list_head?(form, 'faccumulate')
507
+
508
+ parse_faccumulate_args(form.rest)
509
+ end
510
+
511
+ def parse_counted_for_args(args)
512
+ CountedForForm.new(bindings: args[0], body: args[1..] || [])
513
+ end
514
+
515
+ def parse_counted_for_form(form)
516
+ return unless list_head?(form, %w[for fcollect])
517
+
518
+ parse_counted_for_args(form.rest)
519
+ end
520
+ end
521
+ end
522
+ end
@@ -25,25 +25,22 @@ module Kapusta
25
25
  end
26
26
 
27
27
  def normalize_lua_pcall(items)
28
- fn = items[1]
29
- args = items[2..]
28
+ parsed = Language.parse_lua_pcall_args(items[1..] || [])
30
29
  List.new([
31
30
  Sym.new('try'),
32
- List.new([Sym.new('values'), true, List.new([fn, *args])]),
31
+ List.new([Sym.new('values'), true, List.new([parsed.callable, *parsed.args])]),
33
32
  List.new([Sym.new('catch'), Sym.new('StandardError'), Sym.new('e'),
34
33
  List.new([Sym.new('values'), false, Sym.new('e')])])
35
34
  ])
36
35
  end
37
36
 
38
37
  def normalize_lua_xpcall(items)
39
- fn = items[1]
40
- handler = items[2]
41
- args = items[3..]
38
+ parsed = Language.parse_lua_xpcall_args(items[1..] || [])
42
39
  List.new([
43
40
  Sym.new('try'),
44
- List.new([Sym.new('values'), true, List.new([fn, *args])]),
41
+ List.new([Sym.new('values'), true, List.new([parsed.callable, *parsed.args])]),
45
42
  List.new([Sym.new('catch'), Sym.new('StandardError'), Sym.new('e'),
46
- List.new([Sym.new('values'), false, List.new([handler, Sym.new('e')])])])
43
+ List.new([Sym.new('values'), false, List.new([parsed.handler, Sym.new('e')])])])
47
44
  ])
48
45
  end
49
46
  end
@@ -53,39 +50,37 @@ module Kapusta
53
50
 
54
51
  def emit_lua_compat_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var,
55
52
  init_code, body_forms)
56
- return unless lua_iterator_expr?(iter_expr)
53
+ parsed = Language.parse_lua_iterator_form(iter_expr)
54
+ return unless parsed
57
55
 
58
- case iter_expr.head.name
56
+ case parsed.name
59
57
  when 'ipairs'
60
- emit_lua_ipairs_inject(iter_expr, binding_pats, body_env, env, current_scope,
58
+ emit_lua_ipairs_inject(parsed, binding_pats, body_env, env, current_scope,
61
59
  acc_var, init_code, body_forms)
62
60
  when 'pairs'
63
- emit_lua_pairs_inject(iter_expr, binding_pats, body_env, env, current_scope,
61
+ emit_lua_pairs_inject(parsed, binding_pats, body_env, env, current_scope,
64
62
  acc_var, init_code, body_forms)
65
63
  end
66
64
  end
67
65
 
68
66
  def emit_lua_compat_iteration(iter_expr, binding_pats, env, current_scope, method:,
69
67
  extra_block_param: nil, &block)
70
- return unless lua_iterator_expr?(iter_expr)
68
+ parsed = Language.parse_lua_iterator_form(iter_expr)
69
+ return unless parsed
71
70
 
72
- case iter_expr.head.name
71
+ case parsed.name
73
72
  when 'ipairs'
74
- emit_lua_ipairs_iteration(iter_expr, binding_pats, env, current_scope,
73
+ emit_lua_ipairs_iteration(parsed, binding_pats, env, current_scope,
75
74
  method:, extra_block_param:, &block)
76
75
  when 'pairs'
77
- emit_lua_pairs_iteration(iter_expr, binding_pats, env, current_scope,
76
+ emit_lua_pairs_iteration(parsed, binding_pats, env, current_scope,
78
77
  method:, extra_block_param:, &block)
79
78
  end
80
79
  end
81
80
 
82
- def lua_iterator_expr?(expr)
83
- expr.is_a?(List) && expr.head.is_a?(Sym) && LuaCompat.iterator_form?(expr.head.name)
84
- end
85
-
86
- def emit_lua_ipairs_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var,
81
+ def emit_lua_ipairs_inject(iter_form, binding_pats, body_env, env, current_scope, acc_var,
87
82
  init_code, body_forms)
88
- coll_code = emit_expr(iter_expr.items[1], env, current_scope)
83
+ coll_code = emit_expr(iter_form.collection, env, current_scope)
89
84
  value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
90
85
  if ignored_pattern?(binding_pats[0])
91
86
  body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
@@ -99,22 +94,22 @@ module Kapusta
99
94
  init_code, bind_code, body_code)
100
95
  end
101
96
 
102
- def emit_lua_pairs_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var,
97
+ def emit_lua_pairs_inject(iter_form, binding_pats, body_env, env, current_scope, acc_var,
103
98
  init_code, body_forms)
104
99
  key_var, key_bind = bind_iteration_param(binding_pats[0], 'key', body_env)
105
100
  value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
106
101
  bind_code = [key_bind, value_bind].compact.join("\n")
107
102
  body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
108
- coll_code = emit_expr(iter_expr.items[1], env, current_scope)
103
+ coll_code = emit_expr(iter_form.collection, env, current_scope)
109
104
  inject_block(coll_code, "#{acc_var}, (#{key_var}, #{value_var})",
110
105
  init_code, bind_code, body_code)
111
106
  end
112
107
 
113
- def emit_lua_ipairs_iteration(iter_expr, binding_pats, env, current_scope, method:,
108
+ def emit_lua_ipairs_iteration(iter_form, binding_pats, env, current_scope, method:,
114
109
  extra_block_param: nil, &block)
115
110
  body_env = env.child
116
111
  value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
117
- coll_code = emit_expr(iter_expr.items[1], env, current_scope)
112
+ coll_code = emit_expr(iter_form.collection, env, current_scope)
118
113
  if ignored_pattern?(binding_pats[0])
119
114
  bind_code = value_bind || ''
120
115
  body_code = block.call(body_env)
@@ -131,14 +126,14 @@ module Kapusta
131
126
  iteration_block("#{receiver} do |#{params}|", bind_code, body_code)
132
127
  end
133
128
 
134
- def emit_lua_pairs_iteration(iter_expr, binding_pats, env, current_scope, method:,
129
+ def emit_lua_pairs_iteration(iter_form, binding_pats, env, current_scope, method:,
135
130
  extra_block_param: nil, &block)
136
131
  body_env = env.child
137
132
  key_var, key_bind = bind_iteration_param(binding_pats[0], 'key', body_env)
138
133
  value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
139
134
  bind_code = [key_bind, value_bind].compact.join("\n")
140
135
  body_code = block.call(body_env)
141
- coll_code = emit_expr(iter_expr.items[1], env, current_scope)
136
+ coll_code = emit_expr(iter_form.collection, env, current_scope)
142
137
  inner_params = "#{key_var}, #{value_var}"
143
138
  params = extra_block_param ? "(#{inner_params}), #{extra_block_param}" : inner_params
144
139
  iteration_block("#{coll_code}.#{method} do |#{params}|", bind_code, body_code)