kapusta 0.1.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 (77) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +10 -0
  4. data/README.md +58 -0
  5. data/Rakefile +10 -0
  6. data/bin/console +8 -0
  7. data/bin/setup +4 -0
  8. data/examples/accumulator.kap +16 -0
  9. data/examples/ackermann.kap +7 -0
  10. data/examples/anagram.kap +13 -0
  11. data/examples/binary-search.kap +16 -0
  12. data/examples/block-sort.kap +3 -0
  13. data/examples/calc.kap +10 -0
  14. data/examples/counter.kap +19 -0
  15. data/examples/describe.kap +9 -0
  16. data/examples/destructure.kap +4 -0
  17. data/examples/doto.kap +2 -0
  18. data/examples/egg-count.kap +10 -0
  19. data/examples/even-squares.kap +7 -0
  20. data/examples/exceptions.kap +14 -0
  21. data/examples/factorial.kap +8 -0
  22. data/examples/fib.kap +4 -0
  23. data/examples/fizzbuzz.kap +7 -0
  24. data/examples/gcd.kap +5 -0
  25. data/examples/greet.kap +2 -0
  26. data/examples/hashfn.kap +4 -0
  27. data/examples/kwargs.kap +1 -0
  28. data/examples/leap-year.kap +5 -0
  29. data/examples/match.kap +9 -0
  30. data/examples/min-max.kap +11 -0
  31. data/examples/module-header.kap +6 -0
  32. data/examples/palindrome.kap +8 -0
  33. data/examples/pangram.kap +9 -0
  34. data/examples/pcall.kap +9 -0
  35. data/examples/pipeline.kap +6 -0
  36. data/examples/points.kap +9 -0
  37. data/examples/primes.kap +8 -0
  38. data/examples/raindrops.kap +13 -0
  39. data/examples/record.kap +6 -0
  40. data/examples/regex.kap +9 -0
  41. data/examples/ruby-eval.kap +1 -0
  42. data/examples/safe-lookup.kap +6 -0
  43. data/examples/scopes.kap +18 -0
  44. data/examples/shapes.kap +9 -0
  45. data/examples/squares.kap +3 -0
  46. data/examples/stack.kap +19 -0
  47. data/examples/sum.kap +3 -0
  48. data/examples/tset.kap +4 -0
  49. data/examples/two-sum.kap +17 -0
  50. data/exe/kapfmt +6 -0
  51. data/exe/kapusta +6 -0
  52. data/kapfmt +4 -0
  53. data/kapusta.gemspec +25 -0
  54. data/lib/kapusta/ast.rb +76 -0
  55. data/lib/kapusta/cli.rb +61 -0
  56. data/lib/kapusta/compiler/emitter/bindings.rb +178 -0
  57. data/lib/kapusta/compiler/emitter/collections.rb +245 -0
  58. data/lib/kapusta/compiler/emitter/control_flow.rb +168 -0
  59. data/lib/kapusta/compiler/emitter/expressions.rb +107 -0
  60. data/lib/kapusta/compiler/emitter/interop.rb +277 -0
  61. data/lib/kapusta/compiler/emitter/patterns.rb +105 -0
  62. data/lib/kapusta/compiler/emitter/support.rb +169 -0
  63. data/lib/kapusta/compiler/emitter.rb +45 -0
  64. data/lib/kapusta/compiler/normalizer.rb +122 -0
  65. data/lib/kapusta/compiler/runtime.rb +583 -0
  66. data/lib/kapusta/compiler.rb +47 -0
  67. data/lib/kapusta/env.rb +42 -0
  68. data/lib/kapusta/formatter.rb +685 -0
  69. data/lib/kapusta/reader.rb +215 -0
  70. data/lib/kapusta/support.rb +7 -0
  71. data/lib/kapusta/version.rb +5 -0
  72. data/lib/kapusta.rb +30 -0
  73. data/spec/cli_spec.rb +77 -0
  74. data/spec/examples_spec.rb +258 -0
  75. data/spec/formatter_spec.rb +176 -0
  76. data/spec/spec_helper.rb +12 -0
  77. metadata +119 -0
@@ -0,0 +1,583 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kapusta
4
+ module Compiler
5
+ module Runtime
6
+ HELPER_DEPENDENCIES = {
7
+ print_values: %i[stringify],
8
+ concat: %i[stringify],
9
+ method_path_value: %i[kebab_to_snake],
10
+ set_method_path: %i[kebab_to_snake],
11
+ get_ivar: %i[kebab_to_snake],
12
+ set_ivar: %i[kebab_to_snake],
13
+ get_cvar: %i[current_class_scope kebab_to_snake],
14
+ set_cvar: %i[current_class_scope kebab_to_snake],
15
+ get_gvar: %i[kebab_to_snake],
16
+ set_gvar: %i[kebab_to_snake],
17
+ destructure: %i[destructure_into],
18
+ match_pattern: %i[match_pattern_into]
19
+ }.freeze
20
+
21
+ HELPER_SOURCES = {
22
+ kebab_to_snake: <<~RUBY.chomp,
23
+ def __kap_kebab_to_snake(name)
24
+ name.tr('-', '_')
25
+ end
26
+ RUBY
27
+ call: <<~'RUBY'.chomp,
28
+ def __kap_call(callee, positional, kwargs = nil, block = nil)
29
+ raise "not callable: #{callee.inspect}" unless callee.respond_to?(:call)
30
+
31
+ if block
32
+ kwargs ? callee.call(*positional, **kwargs, &block) : callee.call(*positional, &block)
33
+ else
34
+ kwargs ? callee.call(*positional, **kwargs) : callee.call(*positional)
35
+ end
36
+ end
37
+ RUBY
38
+ send_call: <<~RUBY.chomp,
39
+ def __kap_send_call(receiver, method_name, positional, kwargs = nil, block = nil)
40
+ if block
41
+ if kwargs
42
+ receiver.public_send(method_name, *positional, **kwargs, &block)
43
+ else
44
+ receiver.public_send(method_name, *positional, &block)
45
+ end
46
+ elsif kwargs
47
+ receiver.public_send(method_name, *positional, **kwargs)
48
+ else
49
+ receiver.public_send(method_name, *positional)
50
+ end
51
+ end
52
+ RUBY
53
+ invoke_self: <<~RUBY.chomp,
54
+ def __kap_invoke_self(receiver, method_name, positional, kwargs = nil, block = nil)
55
+ if block
56
+ if kwargs
57
+ receiver.send(method_name, *positional, **kwargs, &block)
58
+ else
59
+ receiver.send(method_name, *positional, &block)
60
+ end
61
+ else
62
+ kwargs ? receiver.send(method_name, *positional, **kwargs) : receiver.send(method_name, *positional)
63
+ end
64
+ end
65
+ RUBY
66
+ stringify: <<~RUBY.chomp,
67
+ def __kap_stringify(value)
68
+ case value
69
+ when nil then 'nil'
70
+ when true then 'true'
71
+ when false then 'false'
72
+ else value.to_s
73
+ end
74
+ end
75
+ RUBY
76
+ print_values: <<~'RUBY'.chomp,
77
+ def __kap_print_values(*values)
78
+ $stdout.puts(values.map { |value| __kap_stringify(value) }.join("\t"))
79
+ nil
80
+ end
81
+ RUBY
82
+ concat: <<~RUBY.chomp,
83
+ def __kap_concat(values)
84
+ values.map { |value| __kap_stringify(value) }.join
85
+ end
86
+ RUBY
87
+ get_path: <<~RUBY.chomp,
88
+ def __kap_get_path(obj, keys)
89
+ keys.reduce(obj) { |acc, key| acc[key] }
90
+ end
91
+ RUBY
92
+ qget_path: <<~RUBY.chomp,
93
+ def __kap_qget_path(obj, keys)
94
+ keys.each do |key|
95
+ return nil if obj.nil?
96
+
97
+ obj = obj[key]
98
+ end
99
+ obj
100
+ end
101
+ RUBY
102
+ set_path: <<~RUBY.chomp,
103
+ def __kap_set_path(obj, keys, value)
104
+ target = obj
105
+ keys[0...-1].each { |key| target = target[key] }
106
+ target[keys.last] = value
107
+ end
108
+ RUBY
109
+ method_path_value: <<~RUBY.chomp,
110
+ def __kap_method_path_value(base, segments)
111
+ segments.reduce(base) { |obj, segment| obj.public_send(__kap_kebab_to_snake(segment).to_sym) }
112
+ end
113
+ RUBY
114
+ set_method_path: <<~'RUBY'.chomp,
115
+ def __kap_set_method_path(base, segments, value)
116
+ target = base
117
+ segments[0...-1].each do |segment|
118
+ target = target.public_send(__kap_kebab_to_snake(segment).to_sym)
119
+ end
120
+ setter = "#{__kap_kebab_to_snake(segments.last)}="
121
+ target.public_send(setter.to_sym, value)
122
+ end
123
+ RUBY
124
+ current_class_scope: <<~RUBY.chomp,
125
+ def __kap_current_class_scope(receiver)
126
+ receiver.is_a?(Module) ? receiver : receiver.class
127
+ end
128
+ RUBY
129
+ get_ivar: <<~'RUBY'.chomp,
130
+ def __kap_get_ivar(receiver, name)
131
+ receiver.instance_variable_get("@#{__kap_kebab_to_snake(name)}")
132
+ end
133
+ RUBY
134
+ set_ivar: <<~'RUBY'.chomp,
135
+ def __kap_set_ivar(receiver, name, value)
136
+ receiver.instance_variable_set("@#{__kap_kebab_to_snake(name)}", value)
137
+ end
138
+ RUBY
139
+ get_cvar: <<~'RUBY'.chomp,
140
+ def __kap_get_cvar(receiver, name)
141
+ __kap_current_class_scope(receiver).class_variable_get("@@#{__kap_kebab_to_snake(name)}")
142
+ end
143
+ RUBY
144
+ set_cvar: <<~'RUBY'.chomp,
145
+ def __kap_set_cvar(receiver, name, value)
146
+ __kap_current_class_scope(receiver).class_variable_set("@@#{__kap_kebab_to_snake(name)}", value)
147
+ end
148
+ RUBY
149
+ get_gvar: <<~'RUBY'.chomp,
150
+ def __kap_get_gvar(name)
151
+ Kernel.eval("$#{__kap_kebab_to_snake(name)}", binding, __FILE__, __LINE__)
152
+ end
153
+ RUBY
154
+ set_gvar: <<~'RUBY'.chomp,
155
+ def __kap_set_gvar(name, value)
156
+ Kernel.eval("$#{__kap_kebab_to_snake(name)} = value", binding, __FILE__, __LINE__)
157
+ end
158
+ RUBY
159
+ ensure_module: <<~RUBY.chomp,
160
+ def __kap_ensure_module(holder, path)
161
+ segments = path.split('.')
162
+ last = segments.pop
163
+ scope = holder.is_a?(Module) ? holder : Object
164
+ segments.each do |segment|
165
+ scope =
166
+ if scope.const_defined?(segment, false)
167
+ scope.const_get(segment, false)
168
+ else
169
+ mod = Module.new
170
+ scope.const_set(segment, mod)
171
+ mod
172
+ end
173
+ end
174
+ if scope.const_defined?(last, false)
175
+ scope.const_get(last, false)
176
+ else
177
+ mod = Module.new
178
+ scope.const_set(last, mod)
179
+ mod
180
+ end
181
+ end
182
+ RUBY
183
+ ensure_class: <<~RUBY.chomp,
184
+ def __kap_ensure_class(holder, path, super_class)
185
+ segments = path.split('.')
186
+ last = segments.pop
187
+ scope = holder.is_a?(Module) ? holder : Object
188
+ segments.each do |segment|
189
+ scope =
190
+ if scope.const_defined?(segment, false)
191
+ scope.const_get(segment, false)
192
+ else
193
+ mod = Module.new
194
+ scope.const_set(segment, mod)
195
+ mod
196
+ end
197
+ end
198
+ if scope.const_defined?(last, false)
199
+ scope.const_get(last, false)
200
+ else
201
+ klass = Class.new(super_class)
202
+ scope.const_set(last, klass)
203
+ klass
204
+ end
205
+ end
206
+ RUBY
207
+ destructure: <<~RUBY.chomp,
208
+ def __kap_destructure(pattern, value)
209
+ bindings = {}
210
+ __kap_destructure_into(pattern, value, bindings)
211
+ bindings
212
+ end
213
+ RUBY
214
+ destructure_into: <<~'RUBY'.chomp,
215
+ def __kap_destructure_into(pattern, value, bindings)
216
+ case pattern[0]
217
+ when :sym
218
+ name = pattern[1]
219
+ bindings[name] = value unless name == '_'
220
+ when :vec
221
+ items = pattern[1]
222
+ rest_idx = items.index { |item| item.is_a?(Array) && item[0] == :rest }
223
+ if rest_idx
224
+ before = items[0...rest_idx]
225
+ rest_pattern = items[rest_idx][1]
226
+ before.each_with_index do |item, i|
227
+ __kap_destructure_into(item, value ? value[i] : nil, bindings)
228
+ end
229
+ rest_value = value ? (value[rest_idx..] || []) : []
230
+ __kap_destructure_into(rest_pattern, rest_value, bindings)
231
+ else
232
+ items.each_with_index do |item, i|
233
+ __kap_destructure_into(item, value ? value[i] : nil, bindings)
234
+ end
235
+ end
236
+ when :hash
237
+ pattern[1].each do |key, subpattern|
238
+ __kap_destructure_into(subpattern, value ? value[key] : nil, bindings)
239
+ end
240
+ when :ignore
241
+ nil
242
+ else
243
+ raise "unknown destructure pattern: #{pattern.inspect}"
244
+ end
245
+ end
246
+ RUBY
247
+ match_pattern: <<~RUBY.chomp,
248
+ def __kap_match_pattern(pattern, value)
249
+ bindings = {}
250
+ [__kap_match_pattern_into(pattern, value, bindings), bindings]
251
+ end
252
+ RUBY
253
+ match_pattern_into: <<~'RUBY'.chomp
254
+ def __kap_match_pattern_into(pattern, value, bindings)
255
+ case pattern[0]
256
+ when :sym
257
+ name = pattern[1]
258
+ bindings[name] = value unless name == '_'
259
+ true
260
+ when :vec
261
+ return false unless value.is_a?(Array) || value.respond_to?(:to_ary)
262
+
263
+ array = value.is_a?(Array) ? value : value.to_ary
264
+ items = pattern[1]
265
+ rest_idx = items.index { |item| item.is_a?(Array) && item[0] == :rest }
266
+ if rest_idx
267
+ before = items[0...rest_idx]
268
+ rest_pattern = items[rest_idx][1]
269
+ return false if array.length < before.length
270
+
271
+ before.each_with_index do |item, i|
272
+ return false unless __kap_match_pattern_into(item, array[i], bindings)
273
+ end
274
+ __kap_match_pattern_into(rest_pattern, array[rest_idx..], bindings)
275
+ else
276
+ return false unless array.length == items.length
277
+
278
+ items.each_with_index do |item, i|
279
+ return false unless __kap_match_pattern_into(item, array[i], bindings)
280
+ end
281
+ true
282
+ end
283
+ when :hash
284
+ return false unless value.is_a?(Hash)
285
+
286
+ pattern[1].each do |key, subpattern|
287
+ return false unless value.key?(key)
288
+ return false unless __kap_match_pattern_into(subpattern, value[key], bindings)
289
+ end
290
+ true
291
+ when :lit
292
+ value == pattern[1]
293
+ when :nil
294
+ value.nil?
295
+ else
296
+ raise "bad pattern: #{pattern.inspect}"
297
+ end
298
+ end
299
+ RUBY
300
+ }.transform_values(&:freeze).freeze
301
+
302
+ module_function
303
+
304
+ def helper_name(name)
305
+ "__kap_#{name}"
306
+ end
307
+
308
+ def helper_source(helpers)
309
+ ordered = []
310
+ seen = {}
311
+ helpers.each { |name| append_helper_source(name.to_sym, ordered, seen) }
312
+ return '' if ordered.empty?
313
+
314
+ [
315
+ ordered.map { |name| HELPER_SOURCES.fetch(name) }.join("\n\n"),
316
+ "private #{ordered.map { |name| ":#{helper_name(name)}" }.join(', ')}"
317
+ ].join("\n\n")
318
+ end
319
+
320
+ def append_helper_source(name, ordered, seen)
321
+ return if seen[name]
322
+
323
+ HELPER_DEPENDENCIES.fetch(name, []).each do |dependency|
324
+ append_helper_source(dependency, ordered, seen)
325
+ end
326
+ ordered << name
327
+ seen[name] = true
328
+ end
329
+
330
+ def call(callee, positional, kwargs = nil, block = nil)
331
+ raise "not callable: #{callee.inspect}" unless callee.respond_to?(:call)
332
+
333
+ if block
334
+ kwargs ? callee.call(*positional, **kwargs, &block) : callee.call(*positional, &block)
335
+ else
336
+ kwargs ? callee.call(*positional, **kwargs) : callee.call(*positional)
337
+ end
338
+ end
339
+
340
+ def send_call(receiver, method_name, positional, kwargs = nil, block = nil)
341
+ if block
342
+ if kwargs
343
+ receiver.public_send(method_name, *positional, **kwargs,
344
+ &block)
345
+ else
346
+ receiver.public_send(method_name, *positional, &block)
347
+ end
348
+ elsif kwargs
349
+ receiver.public_send(method_name, *positional,
350
+ **kwargs)
351
+ else
352
+ receiver.public_send(method_name, *positional)
353
+ end
354
+ end
355
+
356
+ def invoke_self(receiver, method_name, positional, kwargs = nil, block = nil)
357
+ if block
358
+ if kwargs
359
+ receiver.send(method_name, *positional, **kwargs,
360
+ &block)
361
+ else
362
+ receiver.send(method_name, *positional, &block)
363
+ end
364
+ else
365
+ kwargs ? receiver.send(method_name, *positional, **kwargs) : receiver.send(method_name, *positional)
366
+ end
367
+ end
368
+
369
+ def stringify(value)
370
+ case value
371
+ when nil then 'nil'
372
+ when true then 'true'
373
+ when false then 'false'
374
+ else value.to_s
375
+ end
376
+ end
377
+
378
+ def print_values(*values)
379
+ $stdout.puts(values.map { |value| stringify(value) }.join("\t"))
380
+ nil
381
+ end
382
+
383
+ def concat(values)
384
+ values.map { |value| stringify(value) }.join
385
+ end
386
+
387
+ def get_path(obj, keys)
388
+ keys.reduce(obj) { |acc, key| acc[key] }
389
+ end
390
+
391
+ def qget_path(obj, keys)
392
+ keys.each do |key|
393
+ return nil if obj.nil?
394
+
395
+ obj = obj[key]
396
+ end
397
+ obj
398
+ end
399
+
400
+ def set_path(obj, keys, value)
401
+ target = obj
402
+ keys[0...-1].each { |key| target = target[key] }
403
+ target[keys.last] = value
404
+ end
405
+
406
+ def method_path_value(base, segments)
407
+ segments.reduce(base) { |obj, segment| obj.public_send(Kapusta.kebab_to_snake(segment).to_sym) }
408
+ end
409
+
410
+ def set_method_path(base, segments, value)
411
+ target = base
412
+ segments[0...-1].each do |segment|
413
+ target = target.public_send(Kapusta.kebab_to_snake(segment).to_sym)
414
+ end
415
+ setter = "#{Kapusta.kebab_to_snake(segments.last)}="
416
+ target.public_send(setter.to_sym, value)
417
+ end
418
+
419
+ def current_class_scope(receiver)
420
+ receiver.is_a?(Module) ? receiver : receiver.class
421
+ end
422
+
423
+ def get_ivar(receiver, name)
424
+ receiver.instance_variable_get("@#{Kapusta.kebab_to_snake(name)}")
425
+ end
426
+
427
+ def set_ivar(receiver, name, value)
428
+ receiver.instance_variable_set("@#{Kapusta.kebab_to_snake(name)}", value)
429
+ end
430
+
431
+ def get_cvar(receiver, name)
432
+ current_class_scope(receiver).class_variable_get("@@#{Kapusta.kebab_to_snake(name)}")
433
+ end
434
+
435
+ def set_cvar(receiver, name, value)
436
+ current_class_scope(receiver).class_variable_set("@@#{Kapusta.kebab_to_snake(name)}", value)
437
+ end
438
+
439
+ def get_gvar(name)
440
+ Kernel.eval("$#{Kapusta.kebab_to_snake(name)}", binding, __FILE__, __LINE__) # $stderr
441
+ end
442
+
443
+ def set_gvar(name, value)
444
+ Kernel.eval("$#{Kapusta.kebab_to_snake(name)} = value", binding, __FILE__, __LINE__) # $stderr = value
445
+ end
446
+
447
+ def ensure_module(holder, path)
448
+ segments = path.split('.')
449
+ last = segments.pop
450
+ scope = holder.is_a?(Module) ? holder : Object
451
+ segments.each do |segment|
452
+ scope =
453
+ if scope.const_defined?(segment, false)
454
+ scope.const_get(segment, false)
455
+ else
456
+ mod = Module.new
457
+ scope.const_set(segment, mod)
458
+ mod
459
+ end
460
+ end
461
+ if scope.const_defined?(last, false)
462
+ scope.const_get(last, false)
463
+ else
464
+ mod = Module.new
465
+ scope.const_set(last, mod)
466
+ mod
467
+ end
468
+ end
469
+
470
+ def ensure_class(holder, path, super_class)
471
+ segments = path.split('.')
472
+ last = segments.pop
473
+ scope = holder.is_a?(Module) ? holder : Object
474
+ segments.each do |segment|
475
+ scope =
476
+ if scope.const_defined?(segment, false)
477
+ scope.const_get(segment, false)
478
+ else
479
+ mod = Module.new
480
+ scope.const_set(segment, mod)
481
+ mod
482
+ end
483
+ end
484
+ if scope.const_defined?(last, false)
485
+ scope.const_get(last, false)
486
+ else
487
+ klass = Class.new(super_class)
488
+ scope.const_set(last, klass)
489
+ klass
490
+ end
491
+ end
492
+
493
+ def destructure(pattern, value)
494
+ bindings = {}
495
+ destructure_into(pattern, value, bindings)
496
+ bindings
497
+ end
498
+
499
+ def destructure_into(pattern, value, bindings)
500
+ case pattern[0]
501
+ when :sym
502
+ name = pattern[1]
503
+ bindings[name] = value unless name == '_'
504
+ when :vec
505
+ items = pattern[1]
506
+ rest_idx = items.index { |item| item.is_a?(Array) && item[0] == :rest }
507
+ if rest_idx
508
+ before = items[0...rest_idx]
509
+ rest_pattern = items[rest_idx][1]
510
+ before.each_with_index do |item, i|
511
+ destructure_into(item, value ? value[i] : nil, bindings)
512
+ end
513
+ rest_value = value ? (value[rest_idx..] || []) : []
514
+ destructure_into(rest_pattern, rest_value, bindings)
515
+ else
516
+ items.each_with_index do |item, i|
517
+ destructure_into(item, value ? value[i] : nil, bindings)
518
+ end
519
+ end
520
+ when :hash
521
+ pattern[1].each do |key, subpattern|
522
+ destructure_into(subpattern, value ? value[key] : nil, bindings)
523
+ end
524
+ when :ignore
525
+ nil
526
+ else
527
+ raise "unknown destructure pattern: #{pattern.inspect}"
528
+ end
529
+ end
530
+
531
+ def match_pattern(pattern, value)
532
+ bindings = {}
533
+ [match_pattern_into(pattern, value, bindings), bindings]
534
+ end
535
+
536
+ def match_pattern_into(pattern, value, bindings)
537
+ case pattern[0]
538
+ when :sym
539
+ name = pattern[1]
540
+ bindings[name] = value unless name == '_'
541
+ true
542
+ when :vec
543
+ return false unless value.is_a?(Array) || value.respond_to?(:to_ary)
544
+
545
+ array = value.is_a?(Array) ? value : value.to_ary
546
+ items = pattern[1]
547
+ rest_idx = items.index { |item| item.is_a?(Array) && item[0] == :rest }
548
+ if rest_idx
549
+ before = items[0...rest_idx]
550
+ rest_pattern = items[rest_idx][1]
551
+ return false if array.length < before.length
552
+
553
+ before.each_with_index do |item, i|
554
+ return false unless match_pattern_into(item, array[i], bindings)
555
+ end
556
+ match_pattern_into(rest_pattern, array[rest_idx..], bindings)
557
+ else
558
+ return false unless array.length == items.length
559
+
560
+ items.each_with_index do |item, i|
561
+ return false unless match_pattern_into(item, array[i], bindings)
562
+ end
563
+ true
564
+ end
565
+ when :hash
566
+ return false unless value.is_a?(Hash)
567
+
568
+ pattern[1].each do |key, subpattern|
569
+ return false unless value.key?(key)
570
+ return false unless match_pattern_into(subpattern, value[key], bindings)
571
+ end
572
+ true
573
+ when :lit
574
+ value == pattern[1]
575
+ when :nil
576
+ value.nil?
577
+ else
578
+ raise "bad pattern: #{pattern.inspect}"
579
+ end
580
+ end
581
+ end
582
+ end
583
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'compiler/runtime'
4
+ require_relative 'compiler/normalizer'
5
+ require_relative 'compiler/emitter'
6
+
7
+ module Kapusta
8
+ module Compiler
9
+ class Error < StandardError; end
10
+ SPECIAL_FORMS = %w[
11
+ fn lambda λ let local var set if when unless case match
12
+ while for each do values
13
+ -> ->> -?> -?>> doto
14
+ icollect collect fcollect accumulate faccumulate
15
+ hashfn
16
+ . ?. :
17
+ ..
18
+ length
19
+ require
20
+ module class
21
+ try catch finally
22
+ raise
23
+ ivar cvar gvar
24
+ ruby
25
+ tset pcall xpcall
26
+ and or not
27
+ = not= < <= > >=
28
+ + - * / %
29
+ print
30
+ ].freeze
31
+
32
+ def self.compile(source, path: '(kapusta)')
33
+ forms = Reader.read_all(source)
34
+ normalized = Normalizer.new.normalize_all(forms)
35
+ Emitter.new(path:).emit_file(normalized)
36
+ end
37
+
38
+ def self.run(source, path: '(kapusta)')
39
+ ruby = compile(source, path:)
40
+ TOPLEVEL_BINDING.eval(ruby, path, 1)
41
+ end
42
+
43
+ def self.run_file(path)
44
+ run(File.read(path), path:)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kapusta
4
+ class Env
5
+ def initialize(parent = nil)
6
+ @parent = parent
7
+ @vars = {}
8
+ end
9
+
10
+ def define(name, value)
11
+ @vars[name] = value
12
+ end
13
+
14
+ def lookup(name)
15
+ if @vars.key?(name)
16
+ @vars[name]
17
+ elsif @parent
18
+ @parent.lookup(name)
19
+ else
20
+ raise NameError, "undefined: #{name}"
21
+ end
22
+ end
23
+
24
+ def defined?(name)
25
+ @vars.key?(name) || @parent&.defined?(name)
26
+ end
27
+
28
+ def set_existing!(name, value)
29
+ if @vars.key?(name)
30
+ @vars[name] = value
31
+ elsif @parent
32
+ @parent.set_existing!(name, value)
33
+ else
34
+ raise NameError, "undefined: #{name}"
35
+ end
36
+ end
37
+
38
+ def child
39
+ Env.new(self)
40
+ end
41
+ end
42
+ end