kapusta 0.8.0 → 0.10.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 +4 -4
- data/README.md +8 -2
- data/bin/check-all +17 -0
- data/bin/compile-examples +70 -0
- data/bin/fennel-parity +4 -37
- data/examples/account-lockout.kap +11 -0
- data/examples/circle.kap +16 -0
- data/examples/convert-temperature.kap +14 -0
- data/examples/count-effects.kap +13 -0
- data/examples/falling-drops.kap +12 -0
- data/examples/fennel-parity-examples.txt +40 -0
- data/examples/hit-counter.kap +17 -0
- data/examples/max-achievable.kap +8 -0
- data/examples/mruby-runtime-examples.txt +89 -0
- data/examples/number-of-1-bits.kap +13 -0
- data/examples/number-of-steps.kap +15 -0
- data/examples/parking-system.kap +18 -0
- data/examples/thread-styles.kap +41 -0
- data/examples/two-sum-hash.kap +11 -14
- data/examples/underscore-patterns.kap +1 -1
- data/lib/kapusta/ast.rb +1 -1
- data/lib/kapusta/cli.rb +11 -6
- data/lib/kapusta/compiler/emitter/bindings.rb +27 -2
- data/lib/kapusta/compiler/emitter/control_flow.rb +97 -14
- data/lib/kapusta/compiler/emitter/interop.rb +2 -0
- data/lib/kapusta/compiler/emitter/patterns.rb +125 -0
- data/lib/kapusta/compiler/emitter/support.rb +9 -2
- data/lib/kapusta/compiler/emitter.rb +2 -1
- data/lib/kapusta/compiler/normalizer.rb +22 -12
- data/lib/kapusta/compiler.rb +13 -4
- data/lib/kapusta/errors.rb +3 -0
- data/lib/kapusta/formatter.rb +9 -2
- data/lib/kapusta/lsp/scope_walker.rb +55 -5
- data/lib/kapusta/reader.rb +28 -0
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +2 -2
- data/spec/cli_spec.rb +35 -0
- data/spec/examples_spec.rb +128 -0
- data/spec/lsp_spec.rb +86 -0
- data/spec/spec_helper.rb +9 -0
- metadata +14 -1
|
@@ -181,6 +181,14 @@ module Kapusta
|
|
|
181
181
|
|
|
182
182
|
def emit_toplevel_method_bridge(ruby_name)
|
|
183
183
|
method_name = ruby_name.to_sym.inspect
|
|
184
|
+
if mruby_target?
|
|
185
|
+
return [
|
|
186
|
+
"define_singleton_method(#{method_name}) do |*args|",
|
|
187
|
+
indent("Object.instance_method(#{method_name}).bind(self).call(*args)"),
|
|
188
|
+
'end'
|
|
189
|
+
].join("\n")
|
|
190
|
+
end
|
|
191
|
+
|
|
184
192
|
"define_singleton_method(#{method_name}, Object.instance_method(#{method_name}).bind(self))"
|
|
185
193
|
end
|
|
186
194
|
|
|
@@ -229,8 +237,14 @@ module Kapusta
|
|
|
229
237
|
|
|
230
238
|
binding = env.lookup_if_defined(name)
|
|
231
239
|
return false if binding.nil?
|
|
240
|
+
return false if method_binding?(binding)
|
|
241
|
+
return false if constant_binding?(binding)
|
|
242
|
+
|
|
243
|
+
true
|
|
244
|
+
end
|
|
232
245
|
|
|
233
|
-
|
|
246
|
+
def constant_binding?(binding)
|
|
247
|
+
binding.is_a?(String) && binding.match?(/\A[A-Z][A-Z0-9_]*\z/)
|
|
234
248
|
end
|
|
235
249
|
|
|
236
250
|
def emit_let(args, env, current_scope)
|
|
@@ -277,7 +291,7 @@ module Kapusta
|
|
|
277
291
|
chunks.reject(&:empty?).join("\n")
|
|
278
292
|
end
|
|
279
293
|
|
|
280
|
-
def emit_local_form(form, env, current_scope)
|
|
294
|
+
def emit_local_form(form, env, current_scope, allow_constant: false)
|
|
281
295
|
emit_error!(:local_arity, form: form.head.name) unless form.items.length == 3
|
|
282
296
|
|
|
283
297
|
target = form.items[1]
|
|
@@ -285,6 +299,12 @@ module Kapusta
|
|
|
285
299
|
|
|
286
300
|
if target.is_a?(Sym)
|
|
287
301
|
validate_binding_symbol!(target)
|
|
302
|
+
if allow_constant && form.head.name == 'local' && (constant_name = constant_name_for(target.name))
|
|
303
|
+
env.define(target.name, constant_name)
|
|
304
|
+
mark_mutability(env, target.name, mutable: false)
|
|
305
|
+
return ["#{constant_name} = #{value_code}\nnil", env]
|
|
306
|
+
end
|
|
307
|
+
|
|
288
308
|
ruby_name = define_local(env, target.name)
|
|
289
309
|
mark_mutability(env, target.name, mutable: form.head.name == 'var')
|
|
290
310
|
["#{ruby_name} = #{value_code}\nnil", env]
|
|
@@ -294,6 +314,11 @@ module Kapusta
|
|
|
294
314
|
end
|
|
295
315
|
end
|
|
296
316
|
|
|
317
|
+
def constant_name_for(source_name)
|
|
318
|
+
candidate = source_name.tr('-', '_').upcase
|
|
319
|
+
candidate if candidate.match?(/\A[A-Z][A-Z0-9_]*\z/)
|
|
320
|
+
end
|
|
321
|
+
|
|
297
322
|
def check_destructure_value!(pattern, value_form)
|
|
298
323
|
return unless pattern.is_a?(Vec) || pattern.is_a?(HashLit)
|
|
299
324
|
|
|
@@ -57,6 +57,25 @@ module Kapusta
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def emit_case(args, env, current_scope, mode)
|
|
60
|
+
value_code, value_var, body = build_case_parts(args, env, current_scope, mode)
|
|
61
|
+
return body unless value_var
|
|
62
|
+
|
|
63
|
+
[
|
|
64
|
+
'(-> do',
|
|
65
|
+
indent("#{value_var} = #{value_code}"),
|
|
66
|
+
indent(body),
|
|
67
|
+
'end).call'
|
|
68
|
+
].join("\n")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def emit_case_statement(args, env, current_scope, mode)
|
|
72
|
+
value_code, value_var, body = build_case_parts(args, env, current_scope, mode)
|
|
73
|
+
return body unless value_var
|
|
74
|
+
|
|
75
|
+
"#{value_var} = #{value_code}\n#{body}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def build_case_parts(args, env, current_scope, mode)
|
|
60
79
|
emit_error!(:case_no_subject) if args.empty?
|
|
61
80
|
|
|
62
81
|
clauses = args[1..]
|
|
@@ -65,20 +84,22 @@ module Kapusta
|
|
|
65
84
|
|
|
66
85
|
value_code = emit_expr(args[0], env, current_scope)
|
|
67
86
|
if simple_case_subject?(args[0]) && simple_expression?(value_code)
|
|
68
|
-
body =
|
|
87
|
+
body = emit_case_body(value_code, clauses, env, current_scope, mode)
|
|
69
88
|
emit_error!(:case_unsupported) unless body
|
|
70
|
-
return body
|
|
89
|
+
return [value_code, nil, body]
|
|
71
90
|
end
|
|
72
91
|
|
|
73
92
|
value_var = temp('case_value')
|
|
74
|
-
body =
|
|
93
|
+
body = emit_case_body(value_var, clauses, env, current_scope, mode)
|
|
75
94
|
emit_error!(:case_unsupported) unless body
|
|
76
|
-
[
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
95
|
+
[value_code, value_var, body]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def emit_case_body(value_var, clauses, env, current_scope, mode)
|
|
99
|
+
return try_emit_compat_case(value_var, clauses, env, current_scope, mode) if mruby_target?
|
|
100
|
+
|
|
101
|
+
try_emit_native_case(value_var, clauses, env, current_scope, mode) ||
|
|
102
|
+
try_emit_compat_case(value_var, clauses, env, current_scope, mode)
|
|
82
103
|
end
|
|
83
104
|
|
|
84
105
|
def simple_case_subject?(form)
|
|
@@ -113,11 +134,6 @@ module Kapusta
|
|
|
113
134
|
["case #{value_var}", *arms, 'end'].join("\n")
|
|
114
135
|
end
|
|
115
136
|
|
|
116
|
-
def wildcard_last?(clauses)
|
|
117
|
-
last_pattern = clauses[-2]
|
|
118
|
-
last_pattern.is_a?(Sym) && last_pattern.name == '_'
|
|
119
|
-
end
|
|
120
|
-
|
|
121
137
|
def try_native_arm(pattern, body, where_guards, env, current_scope, mode)
|
|
122
138
|
allow_pins = !where_guards.empty? && mode == :case
|
|
123
139
|
plan = native_pattern_plan(pattern, env, mode:, allow_pins:)
|
|
@@ -132,6 +148,73 @@ module Kapusta
|
|
|
132
148
|
["in #{plan[:pattern]}#{guard_clause}", indent(body_code)].join("\n")
|
|
133
149
|
end
|
|
134
150
|
|
|
151
|
+
def try_emit_compat_case(value_var, clauses, env, current_scope, mode)
|
|
152
|
+
arms = []
|
|
153
|
+
i = 0
|
|
154
|
+
while i < clauses.length
|
|
155
|
+
pattern = clauses[i]
|
|
156
|
+
body = clauses[i + 1]
|
|
157
|
+
inner, where_guards = if where_pattern?(pattern)
|
|
158
|
+
[pattern.items[1], pattern.items[2..]]
|
|
159
|
+
else
|
|
160
|
+
[pattern, []]
|
|
161
|
+
end
|
|
162
|
+
sub_patterns = or_pattern?(inner) ? inner.items[1..] : [inner]
|
|
163
|
+
sub_arms = sub_patterns.map do |sub|
|
|
164
|
+
try_compat_arm(sub, body, where_guards, value_var, env, current_scope, mode)
|
|
165
|
+
end
|
|
166
|
+
return if sub_arms.any?(&:nil?)
|
|
167
|
+
|
|
168
|
+
arms.concat(sub_arms)
|
|
169
|
+
i += 2
|
|
170
|
+
end
|
|
171
|
+
emit_compat_case_lines(arms)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def wildcard_last?(clauses)
|
|
175
|
+
last_pattern = clauses[-2]
|
|
176
|
+
last_pattern.is_a?(Sym) && last_pattern.name == '_'
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def try_compat_arm(pattern, body, where_guards, value_var, env, current_scope, mode)
|
|
180
|
+
allow_pins = !where_guards.empty? && mode == :case
|
|
181
|
+
arm_env = env.child
|
|
182
|
+
plan = compat_pattern_plan(pattern, value_var, env, arm_env, mode:, allow_pins:)
|
|
183
|
+
return unless plan
|
|
184
|
+
|
|
185
|
+
where_guard_codes = where_guards.map { |g| emit_expr(g, arm_env, current_scope) }
|
|
186
|
+
if where_guard_codes.empty?
|
|
187
|
+
guard_codes = plan[:conditions]
|
|
188
|
+
prelude = plan[:prelude]
|
|
189
|
+
else
|
|
190
|
+
prelude_guards = plan[:prelude].map { |line| "begin #{line}; true end" }
|
|
191
|
+
guard_codes = plan[:conditions] + prelude_guards + where_guard_codes
|
|
192
|
+
prelude = []
|
|
193
|
+
end
|
|
194
|
+
body_code = emit_expr(body, arm_env, current_scope)
|
|
195
|
+
[guard_codes, prelude, body_code]
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def emit_compat_case_lines(arms)
|
|
199
|
+
last_idx = arms.length - 1
|
|
200
|
+
has_unconditional = arms.any? { |conditions, _prelude, _body_code| conditions.empty? }
|
|
201
|
+
lines = ['case']
|
|
202
|
+
arms.each_with_index do |(conditions, prelude, body_code), idx|
|
|
203
|
+
lines << if conditions.empty?
|
|
204
|
+
idx == last_idx ? 'else' : 'when true'
|
|
205
|
+
else
|
|
206
|
+
"when #{conditions.join(' && ')}"
|
|
207
|
+
end
|
|
208
|
+
lines << indent([*prelude, body_code].join("\n"))
|
|
209
|
+
end
|
|
210
|
+
unless has_unconditional
|
|
211
|
+
lines << 'else'
|
|
212
|
+
lines << indent('nil')
|
|
213
|
+
end
|
|
214
|
+
lines << 'end'
|
|
215
|
+
lines.join("\n")
|
|
216
|
+
end
|
|
217
|
+
|
|
135
218
|
def emit_while(args, env, current_scope)
|
|
136
219
|
<<~RUBY.chomp
|
|
137
220
|
(-> do
|
|
@@ -300,6 +300,8 @@ module Kapusta
|
|
|
300
300
|
def emit_bound_call(binding, args, env, current_scope)
|
|
301
301
|
return emit_self_method_binding_call(binding, args, env, current_scope) if method_binding?(binding)
|
|
302
302
|
|
|
303
|
+
emit_error!(:cannot_call_constant, name: binding) if constant_binding?(binding)
|
|
304
|
+
|
|
303
305
|
emit_callable_call(binding, args, env, current_scope)
|
|
304
306
|
end
|
|
305
307
|
|
|
@@ -161,6 +161,131 @@ module Kapusta
|
|
|
161
161
|
|
|
162
162
|
class PatternNotTranslatable < StandardError; end
|
|
163
163
|
|
|
164
|
+
def compat_pattern_plan(pattern, value_code, env, arm_env, mode:, allow_pins:)
|
|
165
|
+
state = { bound_names: {}, conditions: [], prelude: [] }
|
|
166
|
+
compile_compat_pattern(pattern, value_code, env, arm_env, mode:, allow_pins:, state:)
|
|
167
|
+
{ conditions: state[:conditions], prelude: state[:prelude] }
|
|
168
|
+
rescue PatternNotTranslatable
|
|
169
|
+
nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def compile_compat_pattern(pattern, value_code, env, arm_env, mode:, allow_pins:, state:)
|
|
173
|
+
case pattern
|
|
174
|
+
when Sym
|
|
175
|
+
compile_compat_symbol(pattern, value_code, env, arm_env, mode:, state:)
|
|
176
|
+
when Vec
|
|
177
|
+
compile_compat_sequence(pattern.items, value_code, env, arm_env, mode:, allow_pins:, state:)
|
|
178
|
+
when HashLit
|
|
179
|
+
compile_compat_hash(pattern, value_code, env, arm_env, mode:, allow_pins:, state:)
|
|
180
|
+
when List
|
|
181
|
+
if pin_pattern?(pattern)
|
|
182
|
+
compile_compat_pin(pattern, value_code, env, mode:, allow_pins:, state:)
|
|
183
|
+
elsif or_pattern?(pattern)
|
|
184
|
+
raise PatternNotTranslatable
|
|
185
|
+
else
|
|
186
|
+
compile_compat_sequence(pattern.items, value_code, env, arm_env, mode:, allow_pins:, state:)
|
|
187
|
+
end
|
|
188
|
+
when nil
|
|
189
|
+
state[:conditions] << "#{value_code}.nil?"
|
|
190
|
+
when Symbol, String, Numeric, true, false
|
|
191
|
+
state[:conditions] << "#{value_code} == #{pattern.inspect}"
|
|
192
|
+
else
|
|
193
|
+
raise PatternNotTranslatable
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def compile_compat_symbol(pattern, value_code, env, arm_env, mode:, state:)
|
|
198
|
+
name = pattern.name
|
|
199
|
+
return if name == '_'
|
|
200
|
+
|
|
201
|
+
if nil_allowing_pattern_name?(name)
|
|
202
|
+
raise PatternNotTranslatable if state[:bound_names].key?(name)
|
|
203
|
+
|
|
204
|
+
ruby = define_local(arm_env, name)
|
|
205
|
+
state[:bound_names][name] = true
|
|
206
|
+
state[:prelude] << "#{ruby} = #{value_code}"
|
|
207
|
+
return
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
binding = mode == :match ? env.lookup_if_defined(name) : nil
|
|
211
|
+
if state[:bound_names].key?(name)
|
|
212
|
+
raise PatternNotTranslatable
|
|
213
|
+
elsif binding
|
|
214
|
+
state[:conditions] << "#{value_code} == #{binding_value_code(binding)}"
|
|
215
|
+
else
|
|
216
|
+
ruby = define_local(arm_env, name)
|
|
217
|
+
state[:bound_names][name] = true
|
|
218
|
+
state[:conditions] << "(#{ruby} = #{value_code}) != nil"
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def compile_compat_sequence(items, value_code, env, arm_env, mode:, allow_pins:, state:)
|
|
223
|
+
min_length = compat_sequence_min_length(items)
|
|
224
|
+
state[:conditions] << "#{value_code}.is_a?(Array)"
|
|
225
|
+
state[:conditions] << "#{value_code}.length >= #{min_length}"
|
|
226
|
+
|
|
227
|
+
index = 0
|
|
228
|
+
i = 0
|
|
229
|
+
while i < items.length
|
|
230
|
+
if rest_pattern_marker?(items, i)
|
|
231
|
+
sub = items[i + 1]
|
|
232
|
+
raise PatternNotTranslatable unless sub.is_a?(Sym)
|
|
233
|
+
|
|
234
|
+
unless sub.name == '_'
|
|
235
|
+
ruby = define_local(arm_env, sub.name)
|
|
236
|
+
state[:prelude] << "#{ruby} = #{value_code}[#{index}..]"
|
|
237
|
+
end
|
|
238
|
+
i += 2
|
|
239
|
+
else
|
|
240
|
+
compile_compat_pattern(items[i], "#{value_code}[#{index}]", env, arm_env,
|
|
241
|
+
mode:, allow_pins:, state:)
|
|
242
|
+
index += 1
|
|
243
|
+
i += 1
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def compat_sequence_min_length(items)
|
|
249
|
+
count = 0
|
|
250
|
+
i = 0
|
|
251
|
+
while i < items.length
|
|
252
|
+
if rest_pattern_marker?(items, i)
|
|
253
|
+
i += 2
|
|
254
|
+
else
|
|
255
|
+
count += 1
|
|
256
|
+
i += 1
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
count
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def compile_compat_hash(pattern, value_code, env, arm_env, mode:, allow_pins:, state:)
|
|
263
|
+
state[:conditions] << "#{value_code}.is_a?(Hash)"
|
|
264
|
+
pattern.pairs.each do |key, value|
|
|
265
|
+
lookup = "#{value_code}[#{compile_compat_hash_key(key)}]"
|
|
266
|
+
compile_compat_pattern(value, lookup, env, arm_env, mode:, allow_pins:, state:)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def compile_compat_hash_key(key)
|
|
271
|
+
case key
|
|
272
|
+
when Symbol, String, Numeric, true, false, nil then key.inspect
|
|
273
|
+
else raise PatternNotTranslatable
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def compile_compat_pin(pattern, value_code, env, mode:, allow_pins:, state:)
|
|
278
|
+
raise PatternNotTranslatable unless allow_pins && mode == :case
|
|
279
|
+
|
|
280
|
+
name_sym = pattern.items[1]
|
|
281
|
+
raise PatternNotTranslatable unless name_sym.is_a?(Sym)
|
|
282
|
+
|
|
283
|
+
binding = env.lookup_if_defined(name_sym.name)
|
|
284
|
+
raise PatternNotTranslatable unless binding
|
|
285
|
+
|
|
286
|
+
state[:conditions] << "#{value_code} == #{binding_value_code(binding)}"
|
|
287
|
+
end
|
|
288
|
+
|
|
164
289
|
def native_pattern_plan(pattern, env, mode:, allow_pins:)
|
|
165
290
|
state = { bound_names: {}, binding_names: [], guards: [] }
|
|
166
291
|
ruby_pattern = compile_native_pattern(pattern, env, mode:, allow_pins:, state:)
|
|
@@ -25,6 +25,10 @@ module Kapusta
|
|
|
25
25
|
(@form_stack ||= []).last
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def mruby_target?
|
|
29
|
+
@target == :mruby
|
|
30
|
+
end
|
|
31
|
+
|
|
28
32
|
def positionable?(form)
|
|
29
33
|
form.respond_to?(:line) && form.respond_to?(:column)
|
|
30
34
|
end
|
|
@@ -97,7 +101,8 @@ module Kapusta
|
|
|
97
101
|
if named_function_form?(form)
|
|
98
102
|
emit_named_fn_assignment(form, env, current_scope)
|
|
99
103
|
elsif local_form?(form)
|
|
100
|
-
code, env = emit_local_form(form, env, current_scope
|
|
104
|
+
code, env = emit_local_form(form, env, current_scope,
|
|
105
|
+
allow_constant: allow_method_definitions)
|
|
101
106
|
code = code.delete_suffix("\nnil") unless result_needed
|
|
102
107
|
[code, env]
|
|
103
108
|
elsif do_form?(form)
|
|
@@ -136,7 +141,7 @@ module Kapusta
|
|
|
136
141
|
def sequence_statement_form?(form)
|
|
137
142
|
return false unless form.is_a?(List) && form.head.is_a?(Sym)
|
|
138
143
|
|
|
139
|
-
%w[let while for each].include?(form.head.name)
|
|
144
|
+
%w[let while for each case match].include?(form.head.name)
|
|
140
145
|
end
|
|
141
146
|
|
|
142
147
|
def emit_sequence_statement_form(form, env, current_scope, result_needed:)
|
|
@@ -152,6 +157,8 @@ module Kapusta
|
|
|
152
157
|
return ["#{code}\nnil", env] if result_needed && current_scope != :toplevel
|
|
153
158
|
|
|
154
159
|
return [code, env]
|
|
160
|
+
when 'case', 'match'
|
|
161
|
+
return [emit_case_statement(form.rest, env, current_scope, form.head.name.to_sym), env] unless result_needed
|
|
155
162
|
end
|
|
156
163
|
|
|
157
164
|
[emit_expr(form, env, current_scope), env]
|
|
@@ -104,19 +104,29 @@ module Kapusta
|
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
def thread_short(forms, position)
|
|
107
|
-
forms[1..]
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
107
|
+
steps = forms[1..]
|
|
108
|
+
return forms.first if steps.empty?
|
|
109
|
+
|
|
110
|
+
prev_temp = thread_temp
|
|
111
|
+
binding_items = [prev_temp, forms.first]
|
|
112
|
+
body = nil
|
|
113
|
+
last_index = steps.length - 1
|
|
114
|
+
steps.each_with_index do |form, i|
|
|
115
|
+
guarded = List.new([
|
|
116
|
+
Sym.new('if'),
|
|
117
|
+
List.new([Sym.new('='), prev_temp, nil]),
|
|
118
|
+
nil,
|
|
119
|
+
thread_step(prev_temp, form, position)
|
|
120
|
+
])
|
|
121
|
+
if i == last_index
|
|
122
|
+
body = guarded
|
|
123
|
+
else
|
|
124
|
+
temp = thread_temp
|
|
125
|
+
binding_items.push(temp, guarded)
|
|
126
|
+
prev_temp = temp
|
|
127
|
+
end
|
|
119
128
|
end
|
|
129
|
+
List.new([Sym.new('let'), Vec.new(binding_items), body])
|
|
120
130
|
end
|
|
121
131
|
|
|
122
132
|
def thread_step(memo, form, position)
|
data/lib/kapusta/compiler.rb
CHANGED
|
@@ -34,17 +34,17 @@ module Kapusta
|
|
|
34
34
|
].freeze
|
|
35
35
|
SPECIAL_FORMS = (CORE_SPECIAL_FORMS + LuaCompat::SPECIAL_FORMS).freeze
|
|
36
36
|
|
|
37
|
-
def self.compile(source, path: '(kapusta)')
|
|
37
|
+
def self.compile(source, path: '(kapusta)', target: nil)
|
|
38
38
|
forms = Reader.read_all(source)
|
|
39
39
|
expanded = MacroExpander.new(path:).expand_all(forms)
|
|
40
|
-
compile_forms(expanded, path:)
|
|
40
|
+
compile_forms(expanded, path:, target:)
|
|
41
41
|
rescue Kapusta::Error => e
|
|
42
42
|
raise e.with_defaults(path:)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
def self.compile_forms(forms, path: '(kapusta)')
|
|
45
|
+
def self.compile_forms(forms, path: '(kapusta)', target: nil)
|
|
46
46
|
normalized = Normalizer.new.normalize_all(forms)
|
|
47
|
-
Emitter.new(path:).emit_file(normalized)
|
|
47
|
+
Emitter.new(path:, target: normalize_target(target)).emit_file(normalized)
|
|
48
48
|
rescue Kapusta::Error => e
|
|
49
49
|
raise e.with_defaults(path:)
|
|
50
50
|
end
|
|
@@ -57,5 +57,14 @@ module Kapusta
|
|
|
57
57
|
def self.run_file(path)
|
|
58
58
|
run(File.read(path), path:)
|
|
59
59
|
end
|
|
60
|
+
|
|
61
|
+
def self.normalize_target(target)
|
|
62
|
+
case target
|
|
63
|
+
when nil then nil
|
|
64
|
+
when :mruby, 'mruby' then :mruby
|
|
65
|
+
else
|
|
66
|
+
raise Error, Kapusta::Errors.format(:unknown_target, target: target.inspect)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
60
69
|
end
|
|
61
70
|
end
|
data/lib/kapusta/errors.rb
CHANGED
|
@@ -10,6 +10,7 @@ module Kapusta
|
|
|
10
10
|
bad_set_target: 'bad set target: %{target}',
|
|
11
11
|
bad_shorthand: 'bad shorthand',
|
|
12
12
|
bind_table_dots: 'unable to bind table ...',
|
|
13
|
+
cannot_call_constant: 'cannot call constant %{name}; reference it without parentheses',
|
|
13
14
|
cannot_call_literal: 'cannot call literal value %{value}',
|
|
14
15
|
cannot_emit_form: 'cannot emit form: %{form}',
|
|
15
16
|
cannot_set_method_binding: 'cannot set method binding: %{name}',
|
|
@@ -51,6 +52,7 @@ module Kapusta
|
|
|
51
52
|
odd_forms_in_hash: 'odd number of forms in hash',
|
|
52
53
|
rest_not_last: 'expected rest argument before last parameter',
|
|
53
54
|
shadowed_special: 'local %{name} was overshadowed by a special form or macro',
|
|
55
|
+
target_requires_compile: '--target requires --compile',
|
|
54
56
|
tset_no_value: 'tset: expected table, key, and value arguments',
|
|
55
57
|
unclosed_delimiter: "unclosed opening delimiter '%{char}'",
|
|
56
58
|
undefined_symbol: 'undefined symbol: %{name}',
|
|
@@ -58,6 +60,7 @@ module Kapusta
|
|
|
58
60
|
unexpected_eof: 'unexpected eof',
|
|
59
61
|
unexpected_vararg: 'unexpected vararg',
|
|
60
62
|
unknown_special_form: 'unknown special form: %{name}',
|
|
63
|
+
unknown_target: 'unknown target %{target}; only mruby is supported',
|
|
61
64
|
unquote_outside_quasiquote: 'unquote outside quasiquote',
|
|
62
65
|
unquote_splice_outside_list: 'unquote-splice must appear inside a quoted list/vec',
|
|
63
66
|
unterminated_string: 'unterminated string',
|
data/lib/kapusta/formatter.rb
CHANGED
|
@@ -178,7 +178,7 @@ module Kapusta
|
|
|
178
178
|
|
|
179
179
|
case form
|
|
180
180
|
when Comment then form.text
|
|
181
|
-
when List then render_list(form, indent, top_level:)
|
|
181
|
+
when List then form.sigil ? render_sigil(form) : render_list(form, indent, top_level:)
|
|
182
182
|
when Vec then render_vec(form, indent, layout:, top_level:, force_expand:)
|
|
183
183
|
when HashLit then render_hash(form, indent)
|
|
184
184
|
when Quasiquote then render_prefix('`', form.form, indent, force_expand:)
|
|
@@ -189,6 +189,13 @@ module Kapusta
|
|
|
189
189
|
end
|
|
190
190
|
end
|
|
191
191
|
|
|
192
|
+
SIGIL_PREFIXES = { ivar: '@', cvar: '@@', gvar: '$' }.freeze
|
|
193
|
+
private_constant :SIGIL_PREFIXES
|
|
194
|
+
|
|
195
|
+
def render_sigil(list)
|
|
196
|
+
"#{SIGIL_PREFIXES.fetch(list.sigil)}#{list.items[1].name}"
|
|
197
|
+
end
|
|
198
|
+
|
|
192
199
|
def render_prefix(prefix, inner, indent, force_expand: false)
|
|
193
200
|
rendered = render(inner, indent + prefix.length, force_expand:)
|
|
194
201
|
lines = rendered.lines(chomp: true)
|
|
@@ -221,6 +228,7 @@ module Kapusta
|
|
|
221
228
|
|
|
222
229
|
"{#{rendered.join(' ')}}"
|
|
223
230
|
when List
|
|
231
|
+
return render_sigil(form) if form.sigil
|
|
224
232
|
return if contains_comments?(form.items)
|
|
225
233
|
return "##{flat_render(semantic_items(form.items)[1])}" if hashfn_literal?(form)
|
|
226
234
|
return if multiline_in_source?(form)
|
|
@@ -649,7 +657,6 @@ module Kapusta
|
|
|
649
657
|
|
|
650
658
|
def render_let_bindings(bindings, indent)
|
|
651
659
|
return render(bindings, indent + '(let '.length, force_expand: true) if contains_comments?(bindings.items)
|
|
652
|
-
return render(bindings, indent + '(let '.length, layout: :pairwise) if bindings.items.length <= 2
|
|
653
660
|
|
|
654
661
|
hanging = render_hanging_pairwise_vec(bindings)
|
|
655
662
|
hanging || render(bindings, indent + '(let '.length, layout: :pairwise)
|
|
@@ -16,7 +16,7 @@ module Kapusta
|
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
SKIPPED_HEADS = %w[macros
|
|
19
|
+
SKIPPED_HEADS = %w[macros quasi-sym quasi-list
|
|
20
20
|
quasi-list-tail quasi-vec quasi-vec-tail quasi-hash quasi-gensym].freeze
|
|
21
21
|
|
|
22
22
|
DISPATCHERS = {
|
|
@@ -42,7 +42,10 @@ module Kapusta
|
|
|
42
42
|
'class' => :walk_module_class,
|
|
43
43
|
'hashfn' => :walk_hashfn,
|
|
44
44
|
'macro' => :walk_macro_def,
|
|
45
|
-
'import-macros' => :walk_import_macros
|
|
45
|
+
'import-macros' => :walk_import_macros,
|
|
46
|
+
'ivar' => :walk_sigil_form,
|
|
47
|
+
'cvar' => :walk_sigil_form,
|
|
48
|
+
'gvar' => :walk_sigil_form
|
|
46
49
|
}.freeze
|
|
47
50
|
|
|
48
51
|
attr_reader :bindings, :references, :root_scope
|
|
@@ -58,7 +61,9 @@ module Kapusta
|
|
|
58
61
|
@references = []
|
|
59
62
|
@scope_seq = 0
|
|
60
63
|
@root_scope = make_scope(nil, :file)
|
|
64
|
+
@gvar_scope = make_scope(nil, :gvars)
|
|
61
65
|
@in_module_or_class = 0
|
|
66
|
+
@sigil_scope_stack = [make_sigil_scopes]
|
|
62
67
|
end
|
|
63
68
|
|
|
64
69
|
def walk_top(forms)
|
|
@@ -132,7 +137,7 @@ module Kapusta
|
|
|
132
137
|
name_sym, supers, = split_class_args(form.items[1..] || [])
|
|
133
138
|
supers&.items&.each { |item| walk_form(item, scope) }
|
|
134
139
|
add_constant_binding(name_sym, scope, :class) if name_sym.is_a?(Sym)
|
|
135
|
-
|
|
140
|
+
inside_class do
|
|
136
141
|
remaining_forms.each { |item| walk_form(item, scope) }
|
|
137
142
|
end
|
|
138
143
|
end
|
|
@@ -154,6 +159,21 @@ module Kapusta
|
|
|
154
159
|
@in_module_or_class -= 1
|
|
155
160
|
end
|
|
156
161
|
|
|
162
|
+
def inside_class
|
|
163
|
+
inside_module_or_class do
|
|
164
|
+
@sigil_scope_stack.push(make_sigil_scopes)
|
|
165
|
+
begin
|
|
166
|
+
yield
|
|
167
|
+
ensure
|
|
168
|
+
@sigil_scope_stack.pop
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def make_sigil_scopes
|
|
174
|
+
{ ivar: make_scope(nil, :ivars), cvar: make_scope(nil, :cvars) }
|
|
175
|
+
end
|
|
176
|
+
|
|
157
177
|
def walk_form(form, scope)
|
|
158
178
|
case form
|
|
159
179
|
when List then walk_list(form, scope)
|
|
@@ -291,6 +311,10 @@ module Kapusta
|
|
|
291
311
|
target = list.items[1]
|
|
292
312
|
value = list.items[2]
|
|
293
313
|
walk_form(value, scope) if value
|
|
314
|
+
if target.is_a?(List)
|
|
315
|
+
walk_form(target, scope)
|
|
316
|
+
return
|
|
317
|
+
end
|
|
294
318
|
return unless target.is_a?(Sym) && !target.dotted?
|
|
295
319
|
|
|
296
320
|
existing = scope.lookup(target.name)
|
|
@@ -301,6 +325,29 @@ module Kapusta
|
|
|
301
325
|
end
|
|
302
326
|
end
|
|
303
327
|
|
|
328
|
+
def walk_sigil_form(list, _scope)
|
|
329
|
+
return if list.items.length < 2
|
|
330
|
+
|
|
331
|
+
inner = list.items[1]
|
|
332
|
+
return unless inner.is_a?(Sym)
|
|
333
|
+
|
|
334
|
+
kind = list.head.name.to_sym
|
|
335
|
+
target_scope = sigil_target_scope(kind)
|
|
336
|
+
existing = target_scope.bindings[inner.name]
|
|
337
|
+
if existing
|
|
338
|
+
add_reference(inner, target_scope, existing)
|
|
339
|
+
else
|
|
340
|
+
add_binding(inner, target_scope, kind)
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def sigil_target_scope(kind)
|
|
345
|
+
case kind
|
|
346
|
+
when :ivar, :cvar then @sigil_scope_stack.last.fetch(kind)
|
|
347
|
+
when :gvar then @gvar_scope
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
304
351
|
def walk_fn(list, scope)
|
|
305
352
|
items = list.items
|
|
306
353
|
if items[1].is_a?(Vec)
|
|
@@ -472,8 +519,11 @@ module Kapusta
|
|
|
472
519
|
|
|
473
520
|
add_constant_binding(name_sym, scope, kind) if name_sym.is_a?(Sym)
|
|
474
521
|
|
|
475
|
-
|
|
476
|
-
|
|
522
|
+
body = list.items[body_start..] || []
|
|
523
|
+
if kind == :class
|
|
524
|
+
inside_class { body.each { |form| walk_form(form, scope) } }
|
|
525
|
+
else
|
|
526
|
+
inside_module_or_class { body.each { |form| walk_form(form, scope) } }
|
|
477
527
|
end
|
|
478
528
|
end
|
|
479
529
|
|