kapusta 0.2.4 → 0.3.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 +6 -0
- data/examples/manhattan-distance.kap +9 -0
- data/examples/subtract-product-sum.kap +14 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +18 -7
- data/lib/kapusta/compiler/emitter/collections.rb +69 -45
- data/lib/kapusta/compiler/emitter/control_flow.rb +39 -66
- data/lib/kapusta/compiler/emitter/interop.rb +65 -72
- data/lib/kapusta/compiler/emitter/patterns.rb +196 -109
- data/lib/kapusta/compiler/emitter/support.rb +2 -15
- data/lib/kapusta/compiler/emitter.rb +1 -3
- data/lib/kapusta/compiler/normalizer.rb +2 -2
- data/lib/kapusta/compiler.rb +0 -1
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +28 -4
- data/spec/examples_spec.rb +8 -0
- metadata +3 -2
- data/lib/kapusta/compiler/runtime.rb +0 -226
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 179b8b97d775c6bff99bd3d5ac3e1df1f6ab8c17637b6e16f9d688ade7f9dd06
|
|
4
|
+
data.tar.gz: 899899c13c9b1d4f85216df5d3989807d874a9ec862be94d48989f851b2d8111
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 69247136466e119860e5fee232b0bb21fa40b9d23986f09051a4acccda476a9c42b5e3e63e1382367df6c3008876af2f6a69a9b6fd2bfc0e05e1c9e69ca6a8b0
|
|
7
|
+
data.tar.gz: f65709e841e372a1dcc4c9a453d2251d58fb4fffbecd51fd1f0bcfb7c0423dc51109d6533e1b85d9b39bbc82cc994dcf64bdb5aad586c2b6476858db8e320d25
|
data/README.md
CHANGED
|
@@ -8,6 +8,12 @@ Instead, Kapusta aims to bring some of the simplicity and joy of Lisp to Ruby. W
|
|
|
8
8
|
|
|
9
9
|
For more information about Kapusta, see the official Fennel documentation and tutorials.
|
|
10
10
|
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
1. Compiles to readable Ruby.
|
|
14
|
+
2. Compiled `.rb` files don't depend on Kapusta. Run with plain `ruby`, or load `.kap` files at runtime via `require 'kapusta'`.
|
|
15
|
+
3. Two-way Ruby interop.
|
|
16
|
+
|
|
11
17
|
## Usage
|
|
12
18
|
|
|
13
19
|
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
(fn manhattan [edge]
|
|
2
|
+
(let [{:from [x1 y1] :to [x2 y2]} edge]
|
|
3
|
+
(+ (: (- x1 x2) :abs) (: (- y1 y2) :abs))))
|
|
4
|
+
|
|
5
|
+
(fn total-distance [edges]
|
|
6
|
+
(accumulate [total 0 _ edge (ipairs edges)]
|
|
7
|
+
(+ total (manhattan edge))))
|
|
8
|
+
|
|
9
|
+
(print (total-distance [{:from [0 0] :to [3 4]} {:from [1 1] :to [4 5]}]))
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
(fn subtract-product-sum [n]
|
|
2
|
+
(var x n)
|
|
3
|
+
(var product 1)
|
|
4
|
+
(var sum 0)
|
|
5
|
+
(while (> x 0)
|
|
6
|
+
(let [d (% x 10)]
|
|
7
|
+
(set product (* product d))
|
|
8
|
+
(set sum (+ sum d))
|
|
9
|
+
(set x (: (/ x 10) :floor))))
|
|
10
|
+
(- product sum))
|
|
11
|
+
|
|
12
|
+
(print (subtract-product-sum 234))
|
|
13
|
+
(print (subtract-product-sum 4421))
|
|
14
|
+
(print (subtract-product-sum 1))
|
|
@@ -281,12 +281,23 @@ module Kapusta
|
|
|
281
281
|
else
|
|
282
282
|
define_local(env, target.name)
|
|
283
283
|
end
|
|
284
|
-
[
|
|
284
|
+
[emit_assignment(ruby_name, value_code), env]
|
|
285
285
|
else
|
|
286
286
|
[emit_set_target(target, value_code, env, current_scope), env]
|
|
287
287
|
end
|
|
288
288
|
end
|
|
289
289
|
|
|
290
|
+
def emit_assignment(lhs, value_code)
|
|
291
|
+
prefix = "#{lhs} "
|
|
292
|
+
if value_code.start_with?(prefix) &&
|
|
293
|
+
(m = value_code[prefix.length..].match(/\A(\S+) (.*)\z/m)) &&
|
|
294
|
+
!m[1].include?('=')
|
|
295
|
+
"#{lhs} #{m[1]}= #{m[2]}"
|
|
296
|
+
else
|
|
297
|
+
"#{lhs} = #{value_code}"
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
290
301
|
def emit_set_expr(args, env, current_scope)
|
|
291
302
|
target = args[0]
|
|
292
303
|
value_code = emit_expr(args[1], env, current_scope)
|
|
@@ -302,7 +313,7 @@ module Kapusta
|
|
|
302
313
|
last = segments.last
|
|
303
314
|
snake = Kapusta.kebab_to_snake(last)
|
|
304
315
|
if direct_method_name?(last)
|
|
305
|
-
"#{receiver}.#{snake}
|
|
316
|
+
emit_assignment("#{receiver}.#{snake}", value_code)
|
|
306
317
|
else
|
|
307
318
|
"#{receiver}.public_send(:\"#{snake}=\", #{value_code})"
|
|
308
319
|
end
|
|
@@ -310,7 +321,7 @@ module Kapusta
|
|
|
310
321
|
binding = env.lookup(target.name)
|
|
311
322
|
emit_error!("cannot set method binding: #{target.name}") if method_binding?(binding)
|
|
312
323
|
|
|
313
|
-
|
|
324
|
+
emit_assignment(binding, value_code)
|
|
314
325
|
end
|
|
315
326
|
when List
|
|
316
327
|
head = target.head
|
|
@@ -319,13 +330,13 @@ module Kapusta
|
|
|
319
330
|
keys = target.items[2..].map { |item| emit_expr(item, env, current_scope) }
|
|
320
331
|
receiver = simple_expression?(object_code) ? object_code : parenthesize(object_code)
|
|
321
332
|
prefix = keys[0...-1].map { |k| "[#{k}]" }.join
|
|
322
|
-
"#{receiver}#{prefix}[#{keys.last}]
|
|
333
|
+
emit_assignment("#{receiver}#{prefix}[#{keys.last}]", value_code)
|
|
323
334
|
elsif head.is_a?(Sym) && head.name == 'ivar'
|
|
324
|
-
"@#{Kapusta.kebab_to_snake(target.items[1].name)}
|
|
335
|
+
emit_assignment("@#{Kapusta.kebab_to_snake(target.items[1].name)}", value_code)
|
|
325
336
|
elsif head.is_a?(Sym) && head.name == 'cvar'
|
|
326
|
-
"@@#{Kapusta.kebab_to_snake(target.items[1].name)}
|
|
337
|
+
emit_assignment("@@#{Kapusta.kebab_to_snake(target.items[1].name)}", value_code)
|
|
327
338
|
elsif head.is_a?(Sym) && head.name == 'gvar'
|
|
328
|
-
"$#{global_name(target.items[1].name)}
|
|
339
|
+
emit_assignment("$#{global_name(target.items[1].name)}", value_code)
|
|
329
340
|
else
|
|
330
341
|
emit_error!("bad set target: #{target.inspect}")
|
|
331
342
|
end
|
|
@@ -45,13 +45,13 @@ module Kapusta
|
|
|
45
45
|
emit_sequence_value_assignment(acc_var, body)
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
|
-
|
|
49
|
-
(-> do
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
end).call
|
|
54
|
-
|
|
48
|
+
[
|
|
49
|
+
'(-> do',
|
|
50
|
+
indent("#{acc_var} = #{emit_expr(bindings[1], env, current_scope)}"),
|
|
51
|
+
indent(iter_code),
|
|
52
|
+
indent(acc_var),
|
|
53
|
+
'end).call'
|
|
54
|
+
].join("\n")
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def emit_faccumulate(args, env, current_scope)
|
|
@@ -73,29 +73,43 @@ module Kapusta
|
|
|
73
73
|
current_scope:,
|
|
74
74
|
body_code: accumulating_body
|
|
75
75
|
)
|
|
76
|
-
|
|
77
|
-
(-> do
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
end).call
|
|
82
|
-
|
|
76
|
+
[
|
|
77
|
+
'(-> do',
|
|
78
|
+
indent("#{acc_var} = #{emit_expr(bindings[1], env, current_scope)}"),
|
|
79
|
+
indent(loop_code),
|
|
80
|
+
indent(acc_var),
|
|
81
|
+
'end).call'
|
|
82
|
+
].join("\n")
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
def emit_hashfn(args, env, current_scope)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
hash_env.define("$#{
|
|
86
|
+
if needs_explicit_args?(args[0])
|
|
87
|
+
args_var = temp('args')
|
|
88
|
+
hash_env = env.child
|
|
89
|
+
hash_env.define('$', "#{args_var}[0]")
|
|
90
|
+
(1..9).each { |i| hash_env.define("$#{i}", "#{args_var}[#{i - 1}]") }
|
|
91
|
+
hash_env.define('$...', args_var)
|
|
92
|
+
body_code = emit_expr(args[0], hash_env, current_scope)
|
|
93
|
+
["->(*#{args_var}) do", indent(body_code), 'end'].join("\n")
|
|
94
|
+
else
|
|
95
|
+
hash_env = env.child
|
|
96
|
+
hash_env.define('$', '_1')
|
|
97
|
+
(1..9).each { |i| hash_env.define("$#{i}", "_#{i}") }
|
|
98
|
+
body_code = emit_expr(args[0], hash_env, current_scope)
|
|
99
|
+
['proc do', indent(body_code), 'end'].join("\n")
|
|
91
100
|
end
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def needs_explicit_args?(form)
|
|
104
|
+
case form
|
|
105
|
+
when Sym then form.name == '$...'
|
|
106
|
+
when List, Vec then form.items.any? { |item| needs_explicit_args?(item) }
|
|
107
|
+
when HashLit
|
|
108
|
+
form.entries.any? do |entry|
|
|
109
|
+
entry.is_a?(Array) ? entry.any? { |item| needs_explicit_args?(item) } : needs_explicit_args?(entry)
|
|
97
110
|
end
|
|
98
|
-
|
|
111
|
+
else false
|
|
112
|
+
end
|
|
99
113
|
end
|
|
100
114
|
|
|
101
115
|
def emit_iteration(bindings_vec, env, current_scope)
|
|
@@ -108,11 +122,16 @@ module Kapusta
|
|
|
108
122
|
when 'ipairs'
|
|
109
123
|
body_env = env.child
|
|
110
124
|
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
125
|
+
coll_code = emit_expr(iter_expr.items[1], env, current_scope)
|
|
126
|
+
if ignored_pattern?(binding_pats[0])
|
|
127
|
+
bind_code = value_bind || ''
|
|
128
|
+
body_code = yield(body_env)
|
|
129
|
+
return iteration_block("#{coll_code}.each do |#{value_var}|", bind_code, body_code)
|
|
130
|
+
end
|
|
111
131
|
index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
|
|
112
132
|
bind_code = [index_bind, value_bind].compact.join("\n")
|
|
113
133
|
body_code = yield(body_env)
|
|
114
|
-
header = "#{
|
|
115
|
-
".each_with_index do |#{value_var}, #{index_var}|"
|
|
134
|
+
header = "#{coll_code}.each_with_index do |#{value_var}, #{index_var}|"
|
|
116
135
|
return iteration_block(header, bind_code, body_code)
|
|
117
136
|
when 'pairs'
|
|
118
137
|
body_env = env.child
|
|
@@ -141,6 +160,10 @@ module Kapusta
|
|
|
141
160
|
end
|
|
142
161
|
end
|
|
143
162
|
|
|
163
|
+
def ignored_pattern?(pattern)
|
|
164
|
+
pattern.is_a?(Sym) && !pattern.dotted? && pattern.name == '_'
|
|
165
|
+
end
|
|
166
|
+
|
|
144
167
|
def bind_iteration_param(pattern, fallback_name, env)
|
|
145
168
|
if pattern.is_a?(Sym) && !pattern.dotted?
|
|
146
169
|
ruby_name = pattern.name == '_' ? '_' : define_local(env, pattern.name)
|
|
@@ -168,35 +191,36 @@ module Kapusta
|
|
|
168
191
|
end
|
|
169
192
|
|
|
170
193
|
def emit_collection_result(result_var, initial_code, iter_code)
|
|
171
|
-
|
|
172
|
-
(-> do
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
end).call
|
|
177
|
-
|
|
194
|
+
[
|
|
195
|
+
'(-> do',
|
|
196
|
+
indent("#{result_var} = #{initial_code}"),
|
|
197
|
+
indent(iter_code),
|
|
198
|
+
indent(result_var),
|
|
199
|
+
'end).call'
|
|
200
|
+
].join("\n")
|
|
178
201
|
end
|
|
179
202
|
|
|
180
203
|
def emit_array_collection_step(result_var, body_code)
|
|
181
204
|
value_var = temp('value')
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
#{result_var} << #{value_var} unless #{value_var}.nil?
|
|
185
|
-
|
|
205
|
+
[
|
|
206
|
+
emit_sequence_value_assignment(value_var, body_code),
|
|
207
|
+
"#{result_var} << #{value_var} unless #{value_var}.nil?"
|
|
208
|
+
].join("\n")
|
|
186
209
|
end
|
|
187
210
|
|
|
188
211
|
def emit_hash_collection_step(result_var, body_code)
|
|
189
212
|
pair_var = temp('pair')
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if #{pair_var}.is_a?(Array) && #{pair_var}.length == 2 &&
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
213
|
+
[
|
|
214
|
+
emit_sequence_value_assignment(pair_var, body_code),
|
|
215
|
+
"if #{pair_var}.is_a?(Array) && #{pair_var}.length == 2 && " \
|
|
216
|
+
"!#{pair_var}[0].nil? && !#{pair_var}[1].nil?",
|
|
217
|
+
indent("#{result_var}[#{pair_var}[0]] = #{pair_var}[1]"),
|
|
218
|
+
'end'
|
|
219
|
+
].join("\n")
|
|
196
220
|
end
|
|
197
221
|
|
|
198
222
|
def emit_sequence_value_assignment(target_var, body_code)
|
|
199
|
-
return
|
|
223
|
+
return emit_assignment(target_var, body_code) unless body_code.include?("\n")
|
|
200
224
|
|
|
201
225
|
["#{target_var} = begin", indent(body_code), 'end'].join("\n")
|
|
202
226
|
end
|
|
@@ -16,6 +16,8 @@ module Kapusta
|
|
|
16
16
|
|
|
17
17
|
cond = emit_expr(args[0], env, current_scope)
|
|
18
18
|
truthy = emit_if_branch(args[1], env, current_scope)
|
|
19
|
+
return "#{truthy} if #{cond}" if args.length == 2 && !truthy.include?("\n") && !cond.include?("\n")
|
|
20
|
+
|
|
19
21
|
lines = ["if #{cond}", indent(truthy)]
|
|
20
22
|
append_else_lines(lines, args[2..], env, current_scope)
|
|
21
23
|
lines << 'end'
|
|
@@ -57,81 +59,52 @@ module Kapusta
|
|
|
57
59
|
|
|
58
60
|
def emit_case(args, env, current_scope, mode)
|
|
59
61
|
value_var = temp('case_value')
|
|
60
|
-
body =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
body = try_emit_native_case(value_var, args[1..], env, current_scope, mode)
|
|
63
|
+
emit_error!('case/match clauses use patterns this compiler cannot translate') unless body
|
|
64
|
+
[
|
|
65
|
+
'(-> do',
|
|
66
|
+
indent("#{value_var} = #{emit_expr(args[0], env, current_scope)}"),
|
|
67
|
+
indent(body),
|
|
68
|
+
'end).call'
|
|
69
|
+
].join("\n")
|
|
67
70
|
end
|
|
68
71
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
def try_emit_native_case(value_var, clauses, env, current_scope, mode)
|
|
73
|
+
arms = []
|
|
74
|
+
i = 0
|
|
75
|
+
while i < clauses.length
|
|
76
|
+
pattern = clauses[i]
|
|
77
|
+
body = clauses[i + 1]
|
|
78
|
+
inner, where_guards = if where_pattern?(pattern)
|
|
79
|
+
[pattern.items[1], pattern.items[2..]]
|
|
80
|
+
else
|
|
81
|
+
[pattern, []]
|
|
82
|
+
end
|
|
83
|
+
sub_patterns = or_pattern?(inner) ? inner.items[1..] : [inner]
|
|
84
|
+
sub_arms = sub_patterns.map do |sub|
|
|
85
|
+
try_native_arm(sub, body, where_guards, env, current_scope, mode)
|
|
86
|
+
end
|
|
87
|
+
return if sub_arms.any?(&:nil?)
|
|
77
88
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
|
|
81
|
-
else
|
|
82
|
-
emit_simple_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
|
|
89
|
+
arms.concat(sub_arms)
|
|
90
|
+
i += 2
|
|
83
91
|
end
|
|
92
|
+
arms << ['else', indent('nil')].join("\n")
|
|
93
|
+
["case #{value_var}", *arms, 'end'].join("\n")
|
|
84
94
|
end
|
|
85
95
|
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
arm_env = env.child
|
|
91
|
-
assign_code, arm_env = emit_bindings_from_match(plan[:bindings], bindings_var, arm_env)
|
|
92
|
-
body_code = emit_expr(body, arm_env, current_scope)
|
|
93
|
-
arm_body = [assign_code, body_code].reject(&:empty?).join("\n")
|
|
94
|
-
<<~RUBY.chomp
|
|
95
|
-
#{match_var} = #{runtime_call(:match_pattern, plan[:pattern], value_var)}
|
|
96
|
-
if #{match_var}[0]
|
|
97
|
-
#{bindings_var} = #{match_var}[1]
|
|
98
|
-
#{arm_body}
|
|
99
|
-
else
|
|
100
|
-
#{indent(else_code)}
|
|
101
|
-
end
|
|
102
|
-
RUBY
|
|
103
|
-
end
|
|
96
|
+
def try_native_arm(pattern, body, where_guards, env, current_scope, mode)
|
|
97
|
+
allow_pins = !where_guards.empty? && mode == :case
|
|
98
|
+
plan = native_pattern_plan(pattern, env, mode:, allow_pins:)
|
|
99
|
+
return unless plan
|
|
104
100
|
|
|
105
|
-
def emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
|
|
106
|
-
inner = pattern.items[1]
|
|
107
|
-
guards = pattern.items[2..]
|
|
108
|
-
match_var = temp('match')
|
|
109
|
-
bindings_var = temp('bindings')
|
|
110
|
-
plan = pattern_match_plan(inner, env, mode:, allow_pins: mode == :case)
|
|
111
101
|
arm_env = env.child
|
|
112
|
-
|
|
113
|
-
|
|
102
|
+
plan[:bindings].each { |name| arm_env.define(name, sanitize_local(name)) }
|
|
103
|
+
guard_codes = plan[:guards] +
|
|
104
|
+
where_guards.map { |g| emit_expr(g, arm_env, current_scope) }
|
|
105
|
+
guard_clause = guard_codes.empty? ? '' : " if #{guard_codes.join(' && ')}"
|
|
114
106
|
body_code = emit_expr(body, arm_env, current_scope)
|
|
115
|
-
|
|
116
|
-
<<~RUBY.chomp
|
|
117
|
-
#{match_var} = #{runtime_call(:match_pattern, plan[:pattern], value_var)}
|
|
118
|
-
if #{match_var}[0]
|
|
119
|
-
#{bindings_var} = #{match_var}[1]#{bindings_line}
|
|
120
|
-
if #{guard_code}
|
|
121
|
-
#{body_code}
|
|
122
|
-
else
|
|
123
|
-
#{indent(else_code, 2)}
|
|
124
|
-
end
|
|
125
|
-
else
|
|
126
|
-
#{indent(else_code)}
|
|
127
|
-
end
|
|
128
|
-
RUBY
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def emit_case_guards(guards, env, current_scope)
|
|
132
|
-
return 'true' if guards.empty?
|
|
133
|
-
|
|
134
|
-
guards.map { |guard| parenthesize(emit_expr(guard, env, current_scope)) }.join(' && ')
|
|
107
|
+
["in #{plan[:pattern]}#{guard_clause}", indent(body_code)].join("\n")
|
|
135
108
|
end
|
|
136
109
|
|
|
137
110
|
def emit_while(args, env, current_scope)
|
|
@@ -17,8 +17,9 @@ module Kapusta
|
|
|
17
17
|
|
|
18
18
|
def emit_safe_lookup(args, env, current_scope)
|
|
19
19
|
object_code = emit_expr(args[0], env, current_scope)
|
|
20
|
-
keys = args[1..].map { |arg| emit_expr(arg, env, current_scope) }
|
|
21
|
-
|
|
20
|
+
keys = args[1..].map { |arg| emit_expr(arg, env, current_scope) }
|
|
21
|
+
receiver = simple_expression?(object_code) ? object_code : parenthesize(object_code)
|
|
22
|
+
keys.reduce(receiver) { |acc, key| "#{acc}&.[](#{key})" }
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
BINARY_OPERATOR_METHODS = %w[<=> ** << >> & | ^ === =~].freeze
|
|
@@ -53,35 +54,27 @@ module Kapusta
|
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
def emit_require(arg, env, current_scope)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
when String then arg.inspect
|
|
61
|
-
else "(#{emit_expr(arg, env, current_scope)}).to_s"
|
|
62
|
-
end
|
|
63
|
-
if kapusta_require?(arg)
|
|
64
|
-
return [
|
|
65
|
-
'unless defined?(Kapusta)',
|
|
66
|
-
indent('require "kapusta"'),
|
|
67
|
-
'end',
|
|
68
|
-
"Kapusta.require(#{path_code}, relative_to: #{@path.inspect})"
|
|
69
|
-
].join("\n")
|
|
57
|
+
literal = require_path_literal(arg)
|
|
58
|
+
if literal&.match?(%r{\A\.\.?/})
|
|
59
|
+
cleaned = literal.delete_suffix('.kap').sub(%r{\A\./}, '')
|
|
60
|
+
return "require_relative #{cleaned.inspect}"
|
|
70
61
|
end
|
|
71
62
|
|
|
63
|
+
path_code =
|
|
64
|
+
if literal
|
|
65
|
+
literal.inspect
|
|
66
|
+
else
|
|
67
|
+
"(#{emit_expr(arg, env, current_scope)}).to_s"
|
|
68
|
+
end
|
|
72
69
|
"require #{path_code}"
|
|
73
70
|
end
|
|
74
71
|
|
|
75
|
-
def
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
end
|
|
82
|
-
return false unless path
|
|
83
|
-
|
|
84
|
-
path.end_with?('.kap') || path.start_with?('./', '../') || File.absolute_path?(path)
|
|
72
|
+
def require_path_literal(arg)
|
|
73
|
+
case arg
|
|
74
|
+
when Sym then arg.name
|
|
75
|
+
when Symbol then arg.to_s
|
|
76
|
+
when String then arg
|
|
77
|
+
end
|
|
85
78
|
end
|
|
86
79
|
|
|
87
80
|
def emit_module_expr(args, env)
|
|
@@ -97,65 +90,65 @@ module Kapusta
|
|
|
97
90
|
end
|
|
98
91
|
|
|
99
92
|
def emit_module_wrapper(name_sym, body)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
indent("#{mod_var}.module_eval do"),
|
|
105
|
-
indent(body, 2),
|
|
106
|
-
indent('end'),
|
|
107
|
-
indent(mod_var),
|
|
108
|
-
'end).call'
|
|
109
|
-
].join("\n")
|
|
93
|
+
segments = constant_segments(name_sym)
|
|
94
|
+
emit_error!("invalid module name: #{name_sym.name}") unless segments
|
|
95
|
+
inner = build_nested_module(segments, body)
|
|
96
|
+
['(-> do', indent(inner), indent(segments.join('::')), 'end).call'].join("\n")
|
|
110
97
|
end
|
|
111
98
|
|
|
112
99
|
def emit_direct_module_header(name_sym, body)
|
|
113
|
-
|
|
114
|
-
return unless
|
|
100
|
+
segments = constant_segments(name_sym)
|
|
101
|
+
return unless segments
|
|
115
102
|
|
|
116
|
-
[
|
|
117
|
-
"module #{const_name}",
|
|
118
|
-
indent(body),
|
|
119
|
-
'end',
|
|
120
|
-
const_name
|
|
121
|
-
].join("\n")
|
|
103
|
+
[build_nested_module(segments, body), segments.join('::')].join("\n")
|
|
122
104
|
end
|
|
123
105
|
|
|
124
106
|
def emit_class_wrapper(name_sym, supers, env, body)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
'Object'
|
|
131
|
-
end
|
|
132
|
-
[
|
|
133
|
-
'(-> do',
|
|
134
|
-
indent("#{klass_var} = #{runtime_call(:ensure_class, 'self', name_sym.name.inspect, super_code)}"),
|
|
135
|
-
indent("#{klass_var}.class_eval do"),
|
|
136
|
-
indent(body, 2),
|
|
137
|
-
indent('end'),
|
|
138
|
-
indent(klass_var),
|
|
139
|
-
'end).call'
|
|
140
|
-
].join("\n")
|
|
107
|
+
segments = constant_segments(name_sym)
|
|
108
|
+
emit_error!("invalid class name: #{name_sym.name}") unless segments
|
|
109
|
+
super_code = class_super_code(supers, env)
|
|
110
|
+
inner = build_nested_class(segments, super_code, body)
|
|
111
|
+
['(-> do', indent(inner), indent(segments.join('::')), 'end).call'].join("\n")
|
|
141
112
|
end
|
|
142
113
|
|
|
143
|
-
def emit_direct_class_header(name_sym, supers, body)
|
|
144
|
-
|
|
145
|
-
return unless
|
|
114
|
+
def emit_direct_class_header(name_sym, supers, body, env)
|
|
115
|
+
segments = constant_segments(name_sym)
|
|
116
|
+
return unless segments
|
|
146
117
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
indent(body),
|
|
150
|
-
'end',
|
|
151
|
-
const_name
|
|
152
|
-
].join("\n")
|
|
118
|
+
super_code = class_super_code(supers, env)
|
|
119
|
+
[build_nested_class(segments, super_code, body), segments.join('::')].join("\n")
|
|
153
120
|
end
|
|
154
121
|
|
|
155
|
-
def
|
|
156
|
-
return unless name_sym.is_a?(Sym)
|
|
122
|
+
def constant_segments(name_sym)
|
|
123
|
+
return unless name_sym.is_a?(Sym)
|
|
157
124
|
|
|
158
|
-
name_sym.name
|
|
125
|
+
segments = name_sym.dotted? ? name_sym.segments : [name_sym.name]
|
|
126
|
+
return unless segments.all? { |s| s.match?(/\A[A-Z]\w*\z/) }
|
|
127
|
+
|
|
128
|
+
segments
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def class_super_code(supers, env)
|
|
132
|
+
return unless supers.is_a?(Vec) && !supers.items.empty?
|
|
133
|
+
|
|
134
|
+
emit_expr(supers.items.first, env, :toplevel)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def build_nested_module(segments, body)
|
|
138
|
+
inner = ["module #{segments.last}", indent(body), 'end'].join("\n")
|
|
139
|
+
wrap_in_modules(segments[0...-1], inner)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def build_nested_class(segments, super_code, body)
|
|
143
|
+
header = super_code ? "class #{segments.last} < #{super_code}" : "class #{segments.last}"
|
|
144
|
+
inner = [header, indent(body), 'end'].join("\n")
|
|
145
|
+
wrap_in_modules(segments[0...-1], inner)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def wrap_in_modules(parents, inner)
|
|
149
|
+
parents.reverse.reduce(inner) do |acc, mod_name|
|
|
150
|
+
["module #{mod_name}", indent(acc), 'end'].join("\n")
|
|
151
|
+
end
|
|
159
152
|
end
|
|
160
153
|
|
|
161
154
|
def emit_try(args, env, current_scope)
|