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.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/Gemfile +10 -0
- data/README.md +58 -0
- data/Rakefile +10 -0
- data/bin/console +8 -0
- data/bin/setup +4 -0
- data/examples/accumulator.kap +16 -0
- data/examples/ackermann.kap +7 -0
- data/examples/anagram.kap +13 -0
- data/examples/binary-search.kap +16 -0
- data/examples/block-sort.kap +3 -0
- data/examples/calc.kap +10 -0
- data/examples/counter.kap +19 -0
- data/examples/describe.kap +9 -0
- data/examples/destructure.kap +4 -0
- data/examples/doto.kap +2 -0
- data/examples/egg-count.kap +10 -0
- data/examples/even-squares.kap +7 -0
- data/examples/exceptions.kap +14 -0
- data/examples/factorial.kap +8 -0
- data/examples/fib.kap +4 -0
- data/examples/fizzbuzz.kap +7 -0
- data/examples/gcd.kap +5 -0
- data/examples/greet.kap +2 -0
- data/examples/hashfn.kap +4 -0
- data/examples/kwargs.kap +1 -0
- data/examples/leap-year.kap +5 -0
- data/examples/match.kap +9 -0
- data/examples/min-max.kap +11 -0
- data/examples/module-header.kap +6 -0
- data/examples/palindrome.kap +8 -0
- data/examples/pangram.kap +9 -0
- data/examples/pcall.kap +9 -0
- data/examples/pipeline.kap +6 -0
- data/examples/points.kap +9 -0
- data/examples/primes.kap +8 -0
- data/examples/raindrops.kap +13 -0
- data/examples/record.kap +6 -0
- data/examples/regex.kap +9 -0
- data/examples/ruby-eval.kap +1 -0
- data/examples/safe-lookup.kap +6 -0
- data/examples/scopes.kap +18 -0
- data/examples/shapes.kap +9 -0
- data/examples/squares.kap +3 -0
- data/examples/stack.kap +19 -0
- data/examples/sum.kap +3 -0
- data/examples/tset.kap +4 -0
- data/examples/two-sum.kap +17 -0
- data/exe/kapfmt +6 -0
- data/exe/kapusta +6 -0
- data/kapfmt +4 -0
- data/kapusta.gemspec +25 -0
- data/lib/kapusta/ast.rb +76 -0
- data/lib/kapusta/cli.rb +61 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +178 -0
- data/lib/kapusta/compiler/emitter/collections.rb +245 -0
- data/lib/kapusta/compiler/emitter/control_flow.rb +168 -0
- data/lib/kapusta/compiler/emitter/expressions.rb +107 -0
- data/lib/kapusta/compiler/emitter/interop.rb +277 -0
- data/lib/kapusta/compiler/emitter/patterns.rb +105 -0
- data/lib/kapusta/compiler/emitter/support.rb +169 -0
- data/lib/kapusta/compiler/emitter.rb +45 -0
- data/lib/kapusta/compiler/normalizer.rb +122 -0
- data/lib/kapusta/compiler/runtime.rb +583 -0
- data/lib/kapusta/compiler.rb +47 -0
- data/lib/kapusta/env.rb +42 -0
- data/lib/kapusta/formatter.rb +685 -0
- data/lib/kapusta/reader.rb +215 -0
- data/lib/kapusta/support.rb +7 -0
- data/lib/kapusta/version.rb +5 -0
- data/lib/kapusta.rb +30 -0
- data/spec/cli_spec.rb +77 -0
- data/spec/examples_spec.rb +258 -0
- data/spec/formatter_spec.rb +176 -0
- data/spec/spec_helper.rb +12 -0
- 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
|
data/lib/kapusta/env.rb
ADDED
|
@@ -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
|