kapusta 0.13.2 → 0.15.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 (100) 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/digit-tools.kap +24 -0
  16. data/examples/doto-hygiene.kap +2 -2
  17. data/examples/doto.kap +2 -2
  18. data/examples/egg-count.kap +1 -1
  19. data/examples/excel-column.kap +26 -0
  20. data/examples/exceptions.kap +1 -1
  21. data/examples/falling-drops.kap +7 -7
  22. data/examples/fennel-parity-examples.txt +3 -6
  23. data/examples/good-pairs.kap +18 -0
  24. data/examples/greet.kap +1 -1
  25. data/examples/happy-number.kap +2 -2
  26. data/examples/left-right-difference.kap +60 -0
  27. data/examples/length-of-last-word.kap +4 -4
  28. data/examples/logger-rate-limiter.kap +26 -0
  29. data/examples/manhattan-distance.kap +1 -1
  30. data/examples/maximum-subarray.kap +23 -7
  31. data/examples/minimum-start-value.kap +19 -0
  32. data/examples/move-zeroes.kap +2 -2
  33. data/examples/mruby-runtime-examples.txt +10 -2
  34. data/examples/non-constant-local.kap +1 -1
  35. data/examples/number-of-1-bits.kap +1 -1
  36. data/examples/number-of-steps.kap +1 -1
  37. data/examples/palindrome.kap +2 -2
  38. data/examples/pangram.kap +4 -4
  39. data/examples/pcall.kap +3 -3
  40. data/examples/pipeline.kap +4 -4
  41. data/examples/plus-one.kap +3 -3
  42. data/examples/raindrops.kap +1 -1
  43. data/examples/range-width.kap +14 -0
  44. data/examples/recent-counter.kap +6 -6
  45. data/examples/require-local-args.kap +9 -0
  46. data/examples/require-local.kap +7 -0
  47. data/examples/require-module-local.kap +4 -0
  48. data/examples/require-module.kap +4 -0
  49. data/examples/reverse-integer.kap +1 -1
  50. data/examples/roman-to-integer.kap +3 -3
  51. data/examples/running-sum.kap +20 -0
  52. data/examples/safe-lookup.kap +2 -2
  53. data/examples/single-number.kap +1 -1
  54. data/examples/stack.kap +14 -14
  55. data/examples/subtract-product-sum.kap +1 -1
  56. data/examples/summary-ranges.kap +45 -0
  57. data/examples/threading.kap +5 -5
  58. data/examples/tset.kap +1 -1
  59. data/examples/two-sum-hash.kap +3 -3
  60. data/examples/two-sum.kap +1 -1
  61. data/examples/ugly-number.kap +1 -1
  62. data/examples/underground-system.kap +39 -0
  63. data/examples/valid-parentheses-1.kap +6 -6
  64. data/exe/kapusta-ls +49 -2
  65. data/lib/kapusta/ast.rb +8 -0
  66. data/lib/kapusta/compiler/emitter/bindings.rb +111 -89
  67. data/lib/kapusta/compiler/emitter/collections.rb +32 -40
  68. data/lib/kapusta/compiler/emitter/control_flow.rb +33 -31
  69. data/lib/kapusta/compiler/emitter/expressions.rb +21 -5
  70. data/lib/kapusta/compiler/emitter/interop.rb +172 -48
  71. data/lib/kapusta/compiler/emitter/patterns.rb +12 -14
  72. data/lib/kapusta/compiler/emitter/support.rb +70 -81
  73. data/lib/kapusta/compiler/language.rb +533 -0
  74. data/lib/kapusta/compiler/lua_compat.rb +23 -28
  75. data/lib/kapusta/compiler/macro_expander.rb +30 -30
  76. data/lib/kapusta/compiler/macro_lowerer.rb +12 -24
  77. data/lib/kapusta/compiler/normalizer.rb +25 -17
  78. data/lib/kapusta/compiler.rb +3 -24
  79. data/lib/kapusta/env.rb +2 -2
  80. data/lib/kapusta/errors.rb +2 -1
  81. data/lib/kapusta/formatter/ast_helpers.rb +78 -0
  82. data/lib/kapusta/formatter/cli.rb +125 -0
  83. data/lib/kapusta/formatter/line_helpers.rb +44 -0
  84. data/lib/kapusta/formatter/validator.rb +32 -0
  85. data/lib/kapusta/formatter.rb +363 -325
  86. data/lib/kapusta/lsp/identifier.rb +1 -1
  87. data/lib/kapusta/lsp/rename.rb +21 -11
  88. data/lib/kapusta/lsp/scope_walker.rb +122 -212
  89. data/lib/kapusta/lsp/workspace_index.rb +17 -5
  90. data/lib/kapusta/reader.rb +4 -2
  91. data/lib/kapusta/version.rb +1 -1
  92. data/lib/kapusta.rb +39 -6
  93. data/spec/cli_spec.rb +13 -0
  94. data/spec/examples_errors_spec.rb +3 -1
  95. data/spec/examples_spec.rb +99 -15
  96. data/spec/formatter_spec.rb +246 -0
  97. data/spec/lsp_spec.rb +69 -0
  98. data/spec/require_spec.rb +294 -0
  99. metadata +23 -2
  100. data/examples/describe.kap +0 -9
@@ -50,7 +50,7 @@ module Kapusta
50
50
  return [join_definition_aware(entries, current_scope), i + 1]
51
51
  end
52
52
 
53
- if bodyless_header?(form)
53
+ if Language.bodyless_header?(form)
54
54
  header_code, i = emit_bodyless_header(form, forms, i + 1, env)
55
55
  entries << [form, header_code]
56
56
  next
@@ -67,7 +67,7 @@ module Kapusta
67
67
  end
68
68
 
69
69
  def end_form?(form)
70
- form.is_a?(List) && !form.empty? && form.head.is_a?(Sym) && form.head.name == 'end'
70
+ Language.end_form?(form)
71
71
  end
72
72
 
73
73
  def validate_end_form!(form)
@@ -75,52 +75,36 @@ module Kapusta
75
75
  end
76
76
 
77
77
  def validate_header_name!(form, head)
78
- name_sym = head == 'module' ? form.items[1] : split_class_args(form.items[1..])[0]
78
+ name_sym = head == 'module' ? Language.parse_module_form(form).name : Language.parse_class_form(form).name
79
79
  return if constant_segments(name_sym)
80
80
 
81
81
  code = head == 'module' ? :invalid_module_name : :invalid_class_name
82
82
  emit_error!(code, name: name_sym.respond_to?(:name) ? name_sym.name : name_sym.inspect)
83
83
  end
84
84
 
85
- def bodyless_header?(form)
86
- return false unless form.is_a?(List) && !form.empty? && form.head.is_a?(Sym)
87
-
88
- case form.head.name
89
- when 'module'
90
- body = form.items[2..] || []
91
- return true if body.empty?
92
-
93
- body.length == 1 && bodyless_header?(body[0])
94
- when 'class'
95
- _, _, body = split_class_args(form.items[1..])
96
- body.empty?
97
- else
98
- false
99
- end
100
- end
101
-
102
85
  def emit_bodyless_header(form, forms, body_start, env)
103
86
  head = form.head.name
104
87
  validate_header_name!(form, head)
105
88
  if head == 'module'
106
- name_sym = form.items[1]
107
- inner = form.items[2..] || []
108
- if inner.length == 1 && bodyless_header?(inner[0])
109
- inner_code, next_i = emit_bodyless_header(inner[0], forms, body_start, env)
110
- [emit_direct_module_header(name_sym, inner_code) || emit_module_wrapper(name_sym, inner_code), next_i]
89
+ parsed = Language.parse_module_form(form)
90
+ if parsed.body.length == 1 && Language.bodyless_header?(parsed.body[0])
91
+ inner_code, next_i = emit_bodyless_header(parsed.body[0], forms, body_start, env)
92
+ [emit_direct_module_header(parsed.name, inner_code) || emit_module_wrapper(parsed.name, inner_code),
93
+ next_i]
111
94
  else
112
95
  body, next_i = with_class_body do
113
96
  emit_form_run(forms, body_start, env.child, :module, header_form: form)
114
97
  end
115
- [emit_direct_module_header(name_sym, body) || emit_module_wrapper(name_sym, body), next_i]
98
+ body = wrap_singleton(body) if parsed.singleton
99
+ [emit_direct_module_header(parsed.name, body) || emit_module_wrapper(parsed.name, body), next_i]
116
100
  end
117
101
  else
118
- name_sym, supers, = split_class_args(form.items[1..])
102
+ parsed = Language.parse_class_form(form)
119
103
  body, next_i = with_class_body do
120
104
  emit_form_run(forms, body_start, env.child, :class, header_form: form)
121
105
  end
122
- code = emit_direct_class_header(name_sym, supers, body, env) ||
123
- emit_class_wrapper(name_sym, supers, env, body)
106
+ code = emit_direct_class_header(parsed.name, parsed.supers, body, env) ||
107
+ emit_class_wrapper(parsed.name, parsed.supers, env, body)
124
108
  [code, next_i]
125
109
  end
126
110
  end
@@ -133,18 +117,20 @@ module Kapusta
133
117
  end
134
118
 
135
119
  def lower_defn_in_sequence(form, current_scope)
136
- emit_error!(:defn_outside_header) unless %i[module class].include?(current_scope)
120
+ emit_error!(:defn_outside_header) unless Language.header_scope?(current_scope)
137
121
  lower_defn_to_fn(form)
138
122
  end
139
123
 
140
124
  def emit_form_body(form, env, current_scope, allow_method_definitions:, result_needed:)
141
125
  if allow_method_definitions &&
142
126
  method_definition_form?(form) &&
143
- %i[toplevel module class].include?(current_scope)
127
+ Language.definition_scope?(current_scope)
144
128
  code, env = emit_definition_form(form, env, current_scope)
145
129
  return [code, env] if code
146
130
  end
147
131
 
132
+ validate_header_body_form!(form, env, current_scope, allow_method_definitions:)
133
+
148
134
  if named_function_form?(form)
149
135
  emit_named_fn_assignment(form, env, current_scope)
150
136
  elsif local_form?(form)
@@ -165,28 +151,42 @@ module Kapusta
165
151
  end
166
152
  end
167
153
 
154
+ def validate_header_body_form!(form, env, current_scope, allow_method_definitions:)
155
+ return unless allow_method_definitions
156
+ return unless Language.header_scope?(current_scope)
157
+ return unless form.is_a?(List) && form.head.is_a?(Sym)
158
+
159
+ name = form.head.name
160
+ return if special_form?(name)
161
+ return if Language.class_body_declaration?(Kapusta.kebab_to_snake(name))
162
+ return if form.head.dotted?
163
+ return if env.lookup_if_defined(name)
164
+
165
+ emit_error!(:invalid_header_body_form, scope: current_scope, name:)
166
+ end
167
+
168
168
  def class_or_module_form?(form)
169
- form.is_a?(List) && form.head.is_a?(Sym) &&
170
- %w[class module].include?(form.head.name)
169
+ Language.header_form?(form)
171
170
  end
172
171
 
173
172
  def emit_class_or_module_statement(form, env)
174
173
  args = form.rest
175
174
  if form.head.name == 'module'
176
- name_sym = args[0]
175
+ parsed = Language.parse_module_args(args)
177
176
  body = with_class_body do
178
- emit_sequence(args[1..], env.child, :module, allow_method_definitions: true,
179
- result: false).first
177
+ emit_sequence(parsed.body, env.child, :module, allow_method_definitions: true,
178
+ result: false).first
180
179
  end
181
- emit_direct_module_header(name_sym, body) || emit_module_wrapper(name_sym, body)
180
+ body = wrap_singleton(body) if parsed.singleton
181
+ emit_direct_module_header(parsed.name, body) || emit_module_wrapper(parsed.name, body)
182
182
  else
183
- name_sym, supers, body_forms = split_class_args(args)
183
+ parsed = Language.parse_class_args(args)
184
184
  body = with_class_body do
185
- emit_sequence(body_forms, env.child, :class, allow_method_definitions: true,
186
- result: false).first
185
+ emit_sequence(parsed.body, env.child, :class, allow_method_definitions: true,
186
+ result: false).first
187
187
  end
188
- emit_direct_class_header(name_sym, supers, body, env) ||
189
- emit_class_wrapper(name_sym, supers, env, body)
188
+ emit_direct_class_header(parsed.name, parsed.supers, body, env) ||
189
+ emit_class_wrapper(parsed.name, parsed.supers, env, body)
190
190
  end
191
191
  end
192
192
 
@@ -223,25 +223,21 @@ module Kapusta
223
223
  end
224
224
 
225
225
  def blank_between_definitions?(prev_form, curr_form, current_scope)
226
- return false unless %i[toplevel module class].include?(current_scope)
226
+ return false unless Language.definition_scope?(current_scope)
227
227
 
228
228
  definition_form?(prev_form) || definition_form?(curr_form)
229
229
  end
230
230
 
231
231
  def definition_form?(form)
232
- return false unless form.is_a?(List) && !form.empty? && form.head.is_a?(Sym)
233
-
234
- case form.head.name
232
+ case Language.list_head_name(form)
235
233
  when 'defn', 'class', 'module' then true
236
- when 'fn', 'lambda', 'λ' then form.items[1].is_a?(Sym)
234
+ when *Language::FUNCTION_HEADS then Language.parse_function_form(form)&.named?
237
235
  else false
238
236
  end
239
237
  end
240
238
 
241
239
  def sequence_statement_form?(form)
242
- return false unless form.is_a?(List) && form.head.is_a?(Sym)
243
-
244
- %w[let while for each case match].include?(form.head.name)
240
+ Language.sequence_statement_form?(form)
245
241
  end
246
242
 
247
243
  def emit_sequence_statement_form(form, env, current_scope, result_needed:)
@@ -265,29 +261,19 @@ module Kapusta
265
261
  end
266
262
 
267
263
  def special_form?(name)
268
- Compiler::SPECIAL_FORMS.include?(name)
269
- end
270
-
271
- def split_class_args(args)
272
- name_sym = args[0]
273
- if args[1].is_a?(Vec)
274
- [name_sym, args[1], args[2..] || []]
275
- else
276
- [name_sym, nil, args[1..] || []]
277
- end
264
+ Language.special_form?(name)
278
265
  end
279
266
 
280
267
  def named_function_form?(form)
281
- form.is_a?(List) && !form.empty? && form.head.is_a?(Sym) &&
282
- %w[fn lambda λ].include?(form.head.name) && form.items[1].is_a?(Sym)
268
+ Language.parse_function_form(form)&.named?
283
269
  end
284
270
 
285
271
  def defn_form?(form)
286
- form.is_a?(List) && !form.empty? && form.head.is_a?(Sym) && form.head.name == 'defn'
272
+ Language.defn_form?(form)
287
273
  end
288
274
 
289
275
  def lower_defn_to_fn(form)
290
- name_sym = form.items[1]
276
+ name_sym = Language.parse_function_form(form, heads: Language::FUNCTION_DEFINITION_HEADS)&.name
291
277
  emit_error!(:fn_no_params) unless name_sym.is_a?(Sym)
292
278
 
293
279
  fn_sym = Sym.new('fn')
@@ -307,21 +293,23 @@ module Kapusta
307
293
  end
308
294
 
309
295
  def block_form?(form)
310
- form.is_a?(List) && form.head.is_a?(Sym) && %w[fn lambda λ hashfn].include?(form.head.name)
296
+ name = Language.list_head_name(form)
297
+ !name.nil? && (Language.function_head?(name) || name == 'hashfn')
311
298
  end
312
299
 
313
300
  def local_form?(form)
314
- form.is_a?(List) && form.head.is_a?(Sym) && %w[local var].include?(form.head.name)
301
+ Language.binding_form?(form)
315
302
  end
316
303
 
317
304
  def do_form?(form)
318
- form.is_a?(List) && form.head.is_a?(Sym) && form.head.name == 'do'
305
+ Language.do_form?(form)
319
306
  end
320
307
 
321
308
  def set_new_local_form?(form, env)
322
- return false unless form.is_a?(List) && form.head.is_a?(Sym) && form.head.name == 'set'
309
+ parsed = Language.parse_set_form(form)
310
+ return false unless parsed
323
311
 
324
- target = form.items[1]
312
+ target = parsed.target
325
313
  target.is_a?(Sym) && !target.dotted? && !env.defined?(target.name)
326
314
  end
327
315
 
@@ -330,6 +318,10 @@ module Kapusta
330
318
  text.lines.map { |line| line.strip.empty? ? line : "#{prefix}#{line}" }.join
331
319
  end
332
320
 
321
+ def wrap_singleton(body)
322
+ ['class << self', indent(body), 'end'].join("\n")
323
+ end
324
+
333
325
  def temp(prefix)
334
326
  @temp_index += 1
335
327
  "kap_#{prefix}_#{@temp_index}"
@@ -397,23 +389,20 @@ module Kapusta
397
389
  binding
398
390
  end
399
391
 
400
- def parse_counted_for_bindings(bindings, env, current_scope)
401
- emit_error!(:counted_no_range) if bindings.length < 3
402
- name_sym = bindings[0]
392
+ def parse_counted_for_bindings(parsed, env, current_scope)
393
+ emit_error!(:counted_no_range) if parsed.items.length < 3
394
+ name_sym = parsed.counter
403
395
  loop_env = env.child
404
396
  ruby_name = define_local(loop_env, name_sym.name)
405
- start_code = emit_expr(bindings[1], env, current_scope)
406
- finish_code = emit_expr(bindings[2], env, current_scope)
397
+ start_code = emit_expr(parsed.start, env, current_scope)
398
+ finish_code = emit_expr(parsed.finish, env, current_scope)
407
399
  step_code = '1'
408
400
  until_form = nil
409
- i = 3
410
- while i < bindings.length
411
- if bindings[i].is_a?(Sym) && bindings[i].name == '&until'
412
- until_form = bindings[i + 1]
413
- i += 2
401
+ parsed.each_extra do |kind, form|
402
+ if kind == :until
403
+ until_form = form
414
404
  else
415
- step_code = emit_expr(bindings[i], env, current_scope)
416
- i += 1
405
+ step_code = emit_expr(form, env, current_scope)
417
406
  end
418
407
  end
419
408
  { ruby_name:, loop_env:, start_code:, finish_code:, step_code:, until_form: }