kapusta 0.9.0 → 0.11.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 +10 -2
- data/bin/check-all +17 -0
- data/bin/compile-examples +70 -0
- data/bin/fennel-parity +4 -38
- data/examples/account-lockout.kap +11 -0
- data/examples/accumulator.kap +2 -0
- data/examples/bank-account.kap +2 -0
- data/examples/bst-iterator.kap +52 -0
- data/examples/circle.kap +18 -0
- data/examples/convert-temperature.kap +14 -0
- data/examples/count-effects.kap +13 -0
- data/examples/counter.kap +2 -0
- data/examples/falling-drops.kap +12 -0
- data/examples/fennel-parity-examples.txt +40 -0
- data/examples/hit-counter.kap +2 -0
- data/examples/max-achievable.kap +8 -0
- data/examples/module-header.kap +4 -2
- data/examples/mruby-runtime-examples.txt +92 -0
- data/examples/number-of-1-bits.kap +13 -0
- data/examples/number-of-steps.kap +15 -0
- data/examples/parking-system.kap +2 -0
- data/examples/recent-counter.kap +17 -0
- data/examples/scopes.kap +2 -0
- data/examples/signal-harvest.kap +16 -0
- data/examples/stack.kap +2 -0
- data/examples/two-sum-hash.kap +11 -14
- data/examples/underscore-patterns.kap +1 -1
- data/examples/valid-parentheses-1.kap +2 -0
- 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/expressions.rb +1 -0
- data/lib/kapusta/compiler/emitter/interop.rb +4 -2
- data/lib/kapusta/compiler/emitter/patterns.rb +125 -0
- data/lib/kapusta/compiler/emitter/support.rb +63 -24
- data/lib/kapusta/compiler/emitter.rb +2 -1
- data/lib/kapusta/compiler/normalizer.rb +6 -0
- data/lib/kapusta/compiler.rb +15 -6
- data/lib/kapusta/errors.rb +6 -0
- data/lib/kapusta/formatter.rb +1 -2
- data/lib/kapusta/lsp/definition.rb +17 -0
- data/lib/kapusta/lsp/rename.rb +3 -1
- data/lib/kapusta/lsp/scope_walker.rb +40 -11
- data/lib/kapusta/lsp.rb +2 -1
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +2 -2
- data/spec/cli_spec.rb +35 -0
- data/spec/examples_errors_spec.rb +20 -0
- data/spec/examples_spec.rb +136 -0
- data/spec/lsp_spec.rb +71 -3
- data/spec/spec_helper.rb +9 -0
- metadata +14 -1
data/lib/kapusta/cli.rb
CHANGED
|
@@ -5,7 +5,7 @@ require 'optparse'
|
|
|
5
5
|
|
|
6
6
|
module Kapusta
|
|
7
7
|
class CLI
|
|
8
|
-
Options = Struct.new(:compile, :help, :version, keyword_init: true)
|
|
8
|
+
Options = Struct.new(:compile, :target, :help, :version, keyword_init: true)
|
|
9
9
|
|
|
10
10
|
def self.start(argv = ARGV)
|
|
11
11
|
args = argv.dup
|
|
@@ -21,8 +21,10 @@ module Kapusta
|
|
|
21
21
|
return
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
raise Kapusta::Error, Kapusta::Errors.format(:target_requires_compile) if options.target && !options.compile
|
|
25
|
+
|
|
24
26
|
if options.compile
|
|
25
|
-
compile_file(args)
|
|
27
|
+
compile_file(args, target: options.target)
|
|
26
28
|
else
|
|
27
29
|
run_file(args)
|
|
28
30
|
end
|
|
@@ -32,11 +34,14 @@ module Kapusta
|
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
def self.parse_options(args)
|
|
35
|
-
options = Options.new(compile: false, help: false, version: false)
|
|
37
|
+
options = Options.new(compile: false, target: nil, help: false, version: false)
|
|
36
38
|
|
|
37
39
|
OptionParser.new do |parser|
|
|
38
40
|
parser.banner = usage
|
|
39
41
|
parser.on('-c', '--compile', 'Compile .kap to Ruby') { options.compile = true }
|
|
42
|
+
parser.on('--target=TARGET', 'Compile for mruby') do |target|
|
|
43
|
+
options.target = Kapusta::Compiler.normalize_target(target)
|
|
44
|
+
end
|
|
40
45
|
parser.on('-h', '--help', 'Show this help') { options.help = true }
|
|
41
46
|
parser.on('-v', '--version', 'Show version') { options.version = true }
|
|
42
47
|
end.order!(args)
|
|
@@ -44,12 +49,12 @@ module Kapusta
|
|
|
44
49
|
options
|
|
45
50
|
end
|
|
46
51
|
|
|
47
|
-
def self.compile_file(args)
|
|
52
|
+
def self.compile_file(args, target:)
|
|
48
53
|
path = args.shift
|
|
49
54
|
abort usage unless path
|
|
50
55
|
abort usage unless args.empty?
|
|
51
56
|
|
|
52
|
-
$stdout.write(Kapusta.compile(File.read(path), path:))
|
|
57
|
+
$stdout.write(Kapusta.compile(File.read(path), path:, target:))
|
|
53
58
|
end
|
|
54
59
|
|
|
55
60
|
def self.run_file(args)
|
|
@@ -64,7 +69,7 @@ module Kapusta
|
|
|
64
69
|
end
|
|
65
70
|
|
|
66
71
|
def self.usage
|
|
67
|
-
'usage: kapusta [--compile|-c] <file.kap> | kapusta <file.kap> [args...]'
|
|
72
|
+
'usage: kapusta [--compile|-c] [--target=mruby] <file.kap> | kapusta <file.kap> [args...]'
|
|
68
73
|
end
|
|
69
74
|
end
|
|
70
75
|
end
|
|
@@ -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
|
|
@@ -83,6 +83,7 @@ module Kapusta
|
|
|
83
83
|
when 'require' then emit_require(args[0], env, current_scope)
|
|
84
84
|
when 'module' then emit_module_expr(args, env)
|
|
85
85
|
when 'class' then emit_class_expr(args, env)
|
|
86
|
+
when 'end' then emit_error!(:end_outside_header)
|
|
86
87
|
when 'try' then emit_try(args, env, current_scope)
|
|
87
88
|
when 'raise' then emit_raise(args, env, current_scope)
|
|
88
89
|
when 'ivar' then "@#{Kapusta.kebab_to_snake(args[0].name)}"
|
|
@@ -102,7 +102,7 @@ module Kapusta
|
|
|
102
102
|
segments = constant_segments(name_sym)
|
|
103
103
|
return unless segments
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
build_nested_module(segments, body)
|
|
106
106
|
end
|
|
107
107
|
|
|
108
108
|
def emit_class_wrapper(name_sym, supers, env, body)
|
|
@@ -118,7 +118,7 @@ module Kapusta
|
|
|
118
118
|
return unless segments
|
|
119
119
|
|
|
120
120
|
super_code = class_super_code(supers, env)
|
|
121
|
-
|
|
121
|
+
build_nested_class(segments, super_code, body)
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
def constant_segments(name_sym)
|
|
@@ -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,27 +25,61 @@ 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
|
|
31
35
|
|
|
32
36
|
def emit_forms_with_headers(forms, env, current_scope, result: true)
|
|
33
|
-
|
|
37
|
+
_ = result
|
|
38
|
+
code, _next = emit_form_run(forms, 0, env, current_scope)
|
|
39
|
+
code
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def emit_form_run(forms, start, env, current_scope, header_form: nil)
|
|
43
|
+
i = start
|
|
34
44
|
codes = []
|
|
35
45
|
while i < forms.length
|
|
36
46
|
form = forms[i]
|
|
47
|
+
if end_form?(form)
|
|
48
|
+
validate_end_form!(form)
|
|
49
|
+
with_current_form(form) { emit_error!(:end_outside_header) } unless header_form
|
|
50
|
+
return [codes.join("\n"), i + 1]
|
|
51
|
+
end
|
|
52
|
+
|
|
37
53
|
if bodyless_header?(form)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
code, env = emit_form_in_sequence(form, env, current_scope,
|
|
42
|
-
allow_method_definitions: true,
|
|
43
|
-
result_needed: result && i == forms.length - 1)
|
|
44
|
-
codes << code
|
|
45
|
-
i += 1
|
|
54
|
+
header_code, i = emit_bodyless_header(form, forms, i + 1, env)
|
|
55
|
+
codes << header_code
|
|
56
|
+
next
|
|
46
57
|
end
|
|
58
|
+
|
|
59
|
+
code, env = emit_form_in_sequence(form, env, current_scope,
|
|
60
|
+
allow_method_definitions: true,
|
|
61
|
+
result_needed: false)
|
|
62
|
+
codes << code
|
|
63
|
+
i += 1
|
|
47
64
|
end
|
|
48
|
-
|
|
65
|
+
with_current_form(header_form) { emit_error!(:unclosed_header) } if header_form
|
|
66
|
+
[codes.join("\n"), i]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def end_form?(form)
|
|
70
|
+
form.is_a?(List) && !form.empty? && form.head.is_a?(Sym) && form.head.name == 'end'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def validate_end_form!(form)
|
|
74
|
+
with_current_form(form) { emit_error!(:end_with_args) } if form.items.length > 1
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def validate_header_name!(form, head)
|
|
78
|
+
name_sym = head == 'module' ? form.items[1] : split_class_args(form.items[1..])[0]
|
|
79
|
+
return if constant_segments(name_sym)
|
|
80
|
+
|
|
81
|
+
code = head == 'module' ? :invalid_module_name : :invalid_class_name
|
|
82
|
+
emit_error!(code, name: name_sym.respond_to?(:name) ? name_sym.name : name_sym.inspect)
|
|
49
83
|
end
|
|
50
84
|
|
|
51
85
|
def bodyless_header?(form)
|
|
@@ -65,23 +99,25 @@ module Kapusta
|
|
|
65
99
|
end
|
|
66
100
|
end
|
|
67
101
|
|
|
68
|
-
def emit_bodyless_header(form,
|
|
102
|
+
def emit_bodyless_header(form, forms, body_start, env)
|
|
69
103
|
head = form.head.name
|
|
70
|
-
|
|
71
|
-
|
|
104
|
+
validate_header_name!(form, head)
|
|
72
105
|
if head == 'module'
|
|
106
|
+
name_sym = form.items[1]
|
|
73
107
|
inner = form.items[2..] || []
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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]
|
|
111
|
+
else
|
|
112
|
+
body, next_i = emit_form_run(forms, body_start, env, :module, header_form: form)
|
|
113
|
+
[emit_direct_module_header(name_sym, body) || emit_module_wrapper(name_sym, body), next_i]
|
|
114
|
+
end
|
|
81
115
|
else
|
|
82
116
|
name_sym, supers, = split_class_args(form.items[1..])
|
|
83
|
-
body =
|
|
84
|
-
emit_direct_class_header(name_sym, supers, body, env) ||
|
|
117
|
+
body, next_i = emit_form_run(forms, body_start, env, :class, header_form: form)
|
|
118
|
+
code = emit_direct_class_header(name_sym, supers, body, env) ||
|
|
119
|
+
emit_class_wrapper(name_sym, supers, env, body)
|
|
120
|
+
[code, next_i]
|
|
85
121
|
end
|
|
86
122
|
end
|
|
87
123
|
|
|
@@ -97,7 +133,8 @@ module Kapusta
|
|
|
97
133
|
if named_function_form?(form)
|
|
98
134
|
emit_named_fn_assignment(form, env, current_scope)
|
|
99
135
|
elsif local_form?(form)
|
|
100
|
-
code, env = emit_local_form(form, env, current_scope
|
|
136
|
+
code, env = emit_local_form(form, env, current_scope,
|
|
137
|
+
allow_constant: allow_method_definitions)
|
|
101
138
|
code = code.delete_suffix("\nnil") unless result_needed
|
|
102
139
|
[code, env]
|
|
103
140
|
elsif do_form?(form)
|
|
@@ -136,7 +173,7 @@ module Kapusta
|
|
|
136
173
|
def sequence_statement_form?(form)
|
|
137
174
|
return false unless form.is_a?(List) && form.head.is_a?(Sym)
|
|
138
175
|
|
|
139
|
-
%w[let while for each].include?(form.head.name)
|
|
176
|
+
%w[let while for each case match].include?(form.head.name)
|
|
140
177
|
end
|
|
141
178
|
|
|
142
179
|
def emit_sequence_statement_form(form, env, current_scope, result_needed:)
|
|
@@ -152,6 +189,8 @@ module Kapusta
|
|
|
152
189
|
return ["#{code}\nnil", env] if result_needed && current_scope != :toplevel
|
|
153
190
|
|
|
154
191
|
return [code, env]
|
|
192
|
+
when 'case', 'match'
|
|
193
|
+
return [emit_case_statement(form.rest, env, current_scope, form.head.name.to_sym), env] unless result_needed
|
|
155
194
|
end
|
|
156
195
|
|
|
157
196
|
[emit_expr(form, env, current_scope), env]
|
|
@@ -40,6 +40,12 @@ module Kapusta
|
|
|
40
40
|
return inherit_position(List.new(items), list) unless head.is_a?(Sym)
|
|
41
41
|
|
|
42
42
|
case head.name
|
|
43
|
+
when 'defn'
|
|
44
|
+
name_sym = items[1]
|
|
45
|
+
raise compiler_error(:fn_no_params, list) unless name_sym.is_a?(Sym)
|
|
46
|
+
|
|
47
|
+
self_name = inherit_position(Sym.new("self.#{name_sym.name}"), name_sym)
|
|
48
|
+
inherit_position(List.new([inherit_position(Sym.new('fn'), head), self_name, *items[2..]]), list)
|
|
43
49
|
when 'when'
|
|
44
50
|
raise compiler_error(:when_no_body, list, form: head.name) if items[2..].empty?
|
|
45
51
|
|
data/lib/kapusta/compiler.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Kapusta
|
|
|
10
10
|
module Compiler
|
|
11
11
|
class Error < Kapusta::Error; end
|
|
12
12
|
CORE_SPECIAL_FORMS = %w[
|
|
13
|
-
fn lambda λ let local var global set if when unless case match
|
|
13
|
+
fn defn lambda λ let local var global set if when unless case match
|
|
14
14
|
while for each do values
|
|
15
15
|
-> ->> -?> -?>> doto
|
|
16
16
|
icollect collect fcollect accumulate faccumulate
|
|
@@ -19,7 +19,7 @@ module Kapusta
|
|
|
19
19
|
..
|
|
20
20
|
length
|
|
21
21
|
require
|
|
22
|
-
module class
|
|
22
|
+
module class end
|
|
23
23
|
try catch finally
|
|
24
24
|
raise
|
|
25
25
|
ivar cvar gvar
|
|
@@ -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
|