kapusta 0.5.0 → 0.8.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 +24 -6
- data/bin/fennel-parity +11 -4
- data/examples/classify-wallet.kap +11 -0
- data/examples/import-helpers.kapm +9 -0
- data/examples/macros-import-helpers.kap +3 -0
- data/examples/macros-import-whole.kap +5 -0
- data/examples/macros-import.kap +6 -0
- data/examples/power-of-three.kap +12 -0
- data/examples/shared-macros.kapm +4 -0
- data/exe/kapusta-ls +14 -0
- data/kapusta.gemspec +2 -2
- data/lib/kapusta/compiler/emitter/bindings.rb +38 -4
- data/lib/kapusta/compiler/emitter/collections.rb +51 -59
- data/lib/kapusta/compiler/emitter/control_flow.rb +24 -2
- data/lib/kapusta/compiler/emitter/expressions.rb +0 -2
- data/lib/kapusta/compiler/emitter/interop.rb +2 -1
- data/lib/kapusta/compiler/emitter/patterns.rb +52 -4
- data/lib/kapusta/compiler/emitter/support.rb +1 -1
- data/lib/kapusta/compiler/emitter.rb +1 -1
- data/lib/kapusta/compiler/lua_compat.rb +149 -0
- data/lib/kapusta/compiler/macro_expander.rb +55 -141
- data/lib/kapusta/compiler/macro_gensym.rb +21 -0
- data/lib/kapusta/compiler/macro_importer.rb +81 -0
- data/lib/kapusta/compiler/macro_lowerer.rb +184 -0
- data/lib/kapusta/compiler/normalizer.rb +4 -19
- data/lib/kapusta/compiler.rb +4 -2
- data/lib/kapusta/errors.rb +9 -3
- data/lib/kapusta/formatter.rb +4 -0
- data/lib/kapusta/lsp/definition.rb +67 -0
- data/lib/kapusta/lsp/diagnostics.rb +42 -0
- data/lib/kapusta/lsp/formatting.rb +30 -0
- data/lib/kapusta/lsp/identifier.rb +28 -0
- data/lib/kapusta/lsp/rename.rb +417 -0
- data/lib/kapusta/lsp/scope_walker.rb +643 -0
- data/lib/kapusta/lsp/workspace_index.rb +225 -0
- data/lib/kapusta/lsp.rb +312 -0
- data/lib/kapusta/reader.rb +0 -2
- data/lib/kapusta/version.rb +1 -1
- data/spec/examples_errors_spec.rb +142 -1
- data/spec/examples_spec.rb +12 -0
- data/spec/lsp_spec.rb +603 -0
- metadata +23 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f64991d78295bfc4a633136af86607f2d9536f3e113ae9ea819a0c5708837427
|
|
4
|
+
data.tar.gz: 0d656fcf3afefbb2c89acad7a0fa4de8a57f8a484c2ce887e5bc70e7493c71ea
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a27e51a26db8069c998d7e0b586beb4fda1505c2f13f572b82bbbda92bc79dbe6e3d4e308547877a0e455cd56b5c9f1c226830ca2645664dae65b47b552dc3a1
|
|
7
|
+
data.tar.gz: e6e675b9be4492d7bf75e341915bfd5e022a733240a2b7c4139c2904a99577207a7b4d182208ad1706c31bcded3ebaf3a50e03d18f27eafc68373b96141ae8b0
|
data/README.md
CHANGED
|
@@ -14,11 +14,21 @@ For more information about Kapusta, see the official Fennel documentation and tu
|
|
|
14
14
|
2. Compiled `.rb` files don't depend on Kapusta. Run with plain `ruby`, or load `.kap` files at runtime via `require 'kapusta'`.
|
|
15
15
|
3. Two-way Ruby interop.
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## Install
|
|
18
18
|
|
|
19
19
|
```
|
|
20
20
|
gem install kapusta
|
|
21
|
-
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
It installs three executables:
|
|
24
|
+
|
|
25
|
+
1. `kapusta`
|
|
26
|
+
2. `kapfmt`
|
|
27
|
+
3. `kapusta-ls`
|
|
28
|
+
|
|
29
|
+
## Use
|
|
30
|
+
|
|
31
|
+
```
|
|
22
32
|
kapusta examples/fizzbuzz.kap
|
|
23
33
|
```
|
|
24
34
|
|
|
@@ -35,7 +45,7 @@ exe/kapusta --compile examples/fizzbuzz.kap > examples/fizzbuzz.rb
|
|
|
35
45
|
ruby examples/fizzbuzz.rb
|
|
36
46
|
```
|
|
37
47
|
|
|
38
|
-
##
|
|
48
|
+
## Use from Ruby
|
|
39
49
|
|
|
40
50
|
Ruby can require a `.kap` file and use it directly.
|
|
41
51
|
|
|
@@ -100,12 +110,20 @@ Kapusta-specific additions:
|
|
|
100
110
|
- a trailing symbol-keyed hash is emitted as Ruby keyword arguments
|
|
101
111
|
- a final function literal argument is emitted as a Ruby block
|
|
102
112
|
|
|
103
|
-
##
|
|
113
|
+
## Format
|
|
104
114
|
|
|
105
115
|
```
|
|
106
|
-
|
|
116
|
+
kapfmt --fix examples/fizzbuzz.kap
|
|
107
117
|
```
|
|
108
118
|
|
|
119
|
+
## LSP
|
|
120
|
+
|
|
121
|
+
Use `kapusta-ls` in the editor of your choice.
|
|
122
|
+
|
|
109
123
|
## Syntax highlight
|
|
110
124
|
|
|
111
|
-
For Vim you can use https://git.sr.ht/~m15a/vim-fennel-syntax
|
|
125
|
+
For Vim, you can use https://git.sr.ht/~m15a/vim-fennel-syntax
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
[MIT](LICENSE)
|
data/bin/fennel-parity
CHANGED
|
@@ -12,6 +12,7 @@ KAPFMT = File.join(ROOT, 'exe', 'kapfmt')
|
|
|
12
12
|
COMPATIBLE = %w[
|
|
13
13
|
ackermann.kap
|
|
14
14
|
anonymous-greeter.kap
|
|
15
|
+
classify-wallet.kap
|
|
15
16
|
climbing-stairs.kap
|
|
16
17
|
describe.kap
|
|
17
18
|
destructure.kap
|
|
@@ -23,6 +24,9 @@ COMPATIBLE = %w[
|
|
|
23
24
|
hashfn.kap
|
|
24
25
|
leap-year.kap
|
|
25
26
|
macros-dbg.kap
|
|
27
|
+
macros-import-helpers.kap
|
|
28
|
+
macros-import-whole.kap
|
|
29
|
+
macros-import.kap
|
|
26
30
|
macros-multi.kap
|
|
27
31
|
macros-swap.kap
|
|
28
32
|
macros-thrice-if.kap
|
|
@@ -31,6 +35,7 @@ COMPATIBLE = %w[
|
|
|
31
35
|
match.kap
|
|
32
36
|
min-max.kap
|
|
33
37
|
or-patterns.kap
|
|
38
|
+
power-of-three.kap
|
|
34
39
|
packet-router.kap
|
|
35
40
|
points.kap
|
|
36
41
|
primes.kap
|
|
@@ -42,13 +47,15 @@ COMPATIBLE = %w[
|
|
|
42
47
|
underscore-patterns.kap
|
|
43
48
|
].freeze
|
|
44
49
|
|
|
45
|
-
def run(cmd, file, chdir: EXAMPLES)
|
|
46
|
-
out, err, status = Open3.capture3(cmd, file, chdir:)
|
|
50
|
+
def run(cmd, file, chdir: EXAMPLES, env: {})
|
|
51
|
+
out, err, status = Open3.capture3(env, cmd, file, chdir:)
|
|
47
52
|
[out, err, status]
|
|
48
53
|
rescue StandardError => e
|
|
49
54
|
['', e.message, nil]
|
|
50
55
|
end
|
|
51
56
|
|
|
57
|
+
FENNEL_ENV = { 'FENNEL_MACRO_PATH' => './?.kapm;./?.kap' }.freeze
|
|
58
|
+
|
|
52
59
|
def strip_outer_quotes(line)
|
|
53
60
|
if line.length >= 2 && line.start_with?('"') && line.end_with?('"')
|
|
54
61
|
line[1..-2]
|
|
@@ -98,7 +105,7 @@ end
|
|
|
98
105
|
|
|
99
106
|
def check_run(path)
|
|
100
107
|
k_out, k_err, k_status = run(KAPUSTA, path)
|
|
101
|
-
f_out, f_err, f_status = run('fennel', path)
|
|
108
|
+
f_out, f_err, f_status = run('fennel', path, env: FENNEL_ENV)
|
|
102
109
|
|
|
103
110
|
return "kapusta exited #{k_status&.exitstatus}: #{k_err.strip}" if k_status.nil? || !k_status.success?
|
|
104
111
|
return "fennel exited #{f_status&.exitstatus}: #{f_err.strip}" if f_status.nil? || !f_status.success?
|
|
@@ -130,7 +137,7 @@ end
|
|
|
130
137
|
def check_error(name)
|
|
131
138
|
path = File.join(EXAMPLES_ERRORS, name)
|
|
132
139
|
_, _, k_status = run(KAPUSTA, path, chdir: EXAMPLES_ERRORS)
|
|
133
|
-
_, _, f_status = run('fennel', path, chdir: EXAMPLES_ERRORS)
|
|
140
|
+
_, _, f_status = run('fennel', path, chdir: EXAMPLES_ERRORS, env: FENNEL_ENV)
|
|
134
141
|
|
|
135
142
|
return [:fail, 'fennel unexpectedly succeeded'] if f_status&.success?
|
|
136
143
|
return [:fail, "kapusta unexpectedly succeeded (exit #{k_status&.exitstatus})"] if k_status&.success?
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
(fn classify-wallet [wallet]
|
|
2
|
+
(case wallet
|
|
3
|
+
{1 a 25 b} (.. "pennies-" a "-quarters-" b)
|
|
4
|
+
{25 q} (.. "quarters-only-" q)
|
|
5
|
+
{1 p} (.. "pennies-only-" p)
|
|
6
|
+
_ "mixed"))
|
|
7
|
+
|
|
8
|
+
(print (classify-wallet {1 5 25 2}))
|
|
9
|
+
(print (classify-wallet {25 4}))
|
|
10
|
+
(print (classify-wallet {1 10}))
|
|
11
|
+
(print (classify-wallet {5 3 10 2}))
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
(fn power-of-three? [n]
|
|
2
|
+
(macro divisible? [v k]
|
|
3
|
+
`(= 0 (% ,v ,k)))
|
|
4
|
+
(var x n)
|
|
5
|
+
(while (and (> x 1) (divisible? x 3))
|
|
6
|
+
(set x (/ x 3)))
|
|
7
|
+
(= x 1))
|
|
8
|
+
|
|
9
|
+
(print (power-of-three? 27))
|
|
10
|
+
(print (power-of-three? 9))
|
|
11
|
+
(print (power-of-three? 45))
|
|
12
|
+
(print (power-of-three? 1))
|
data/exe/kapusta-ls
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../lib/kapusta/lsp'
|
|
5
|
+
|
|
6
|
+
case ARGV.first
|
|
7
|
+
when '--version', '-v'
|
|
8
|
+
puts "kapusta-ls #{Kapusta::VERSION}"
|
|
9
|
+
when '--help', '-h'
|
|
10
|
+
puts 'usage: kapusta-ls # speak LSP over stdio'
|
|
11
|
+
puts ' kapusta-ls --version'
|
|
12
|
+
else
|
|
13
|
+
Kapusta::LSP.start
|
|
14
|
+
end
|
data/kapusta.gemspec
CHANGED
|
@@ -20,10 +20,10 @@ Gem::Specification.new do |spec|
|
|
|
20
20
|
spec.files = Dir.chdir(__dir__) do
|
|
21
21
|
`git ls-files -z`.split("\x0").select do |path|
|
|
22
22
|
path.start_with?('bin/', 'docs/', 'examples/', 'exe/', 'lib/', 'spec/') ||
|
|
23
|
-
%w[.rspec Gemfile README.md Rakefile kapfmt kapusta.gemspec].include?(path)
|
|
23
|
+
%w[.rspec Gemfile README.md Rakefile kapfmt kapusta-ls kapusta.gemspec].include?(path)
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
spec.bindir = 'exe'
|
|
27
|
-
spec.executables = %w[kapfmt kapusta]
|
|
27
|
+
spec.executables = %w[kapfmt kapusta kapusta-ls]
|
|
28
28
|
spec.require_paths = ['lib']
|
|
29
29
|
end
|
|
@@ -28,12 +28,14 @@ module Kapusta
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def emit_lambda(pattern, body, env, current_scope)
|
|
31
|
+
validate_fn_params!(pattern)
|
|
31
32
|
return emit_simple_lambda(pattern, body, env, current_scope) if simple_parameter_pattern?(pattern)
|
|
32
33
|
|
|
33
34
|
args_var = temp('args')
|
|
34
35
|
body_env = env.child
|
|
35
36
|
bindings_code, body_env = emit_pattern_bind(pattern, args_var, body_env)
|
|
36
|
-
body_code, = emit_sequence(body, body_env, current_scope,
|
|
37
|
+
body_code, = emit_sequence(body, body_env, current_scope,
|
|
38
|
+
allow_method_definitions: false, result: false)
|
|
37
39
|
block_locals = pattern_names(pattern).map { |name| body_env.lookup(name) }.uniq
|
|
38
40
|
block_locals_clause = block_locals.empty? ? '' : "; #{block_locals.join(', ')}"
|
|
39
41
|
[
|
|
@@ -56,7 +58,8 @@ module Kapusta
|
|
|
56
58
|
def build_simple_block_parts(pattern, body, env, current_scope)
|
|
57
59
|
body_env = env.child
|
|
58
60
|
params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
|
|
59
|
-
body_code, = emit_sequence(body, body_env, current_scope,
|
|
61
|
+
body_code, = emit_sequence(body, body_env, current_scope,
|
|
62
|
+
allow_method_definitions: false, result: false)
|
|
60
63
|
[params, body_code]
|
|
61
64
|
end
|
|
62
65
|
|
|
@@ -66,6 +69,17 @@ module Kapusta
|
|
|
66
69
|
end
|
|
67
70
|
end
|
|
68
71
|
|
|
72
|
+
def validate_fn_params!(pattern)
|
|
73
|
+
return unless pattern.is_a?(Vec)
|
|
74
|
+
|
|
75
|
+
pattern.items.each_with_index do |item, idx|
|
|
76
|
+
next unless item.is_a?(Sym) && item.name == '...'
|
|
77
|
+
next if idx == pattern.items.length - 1
|
|
78
|
+
|
|
79
|
+
emit_error!(:vararg_not_last)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
69
83
|
def emit_definition_form(form, env, current_scope)
|
|
70
84
|
return [emit_method_definition(form, env), env] unless current_scope == :toplevel
|
|
71
85
|
|
|
@@ -127,6 +141,7 @@ module Kapusta
|
|
|
127
141
|
end
|
|
128
142
|
|
|
129
143
|
def emit_direct_method_definition(name_sym, pattern, body, env)
|
|
144
|
+
validate_fn_params!(pattern)
|
|
130
145
|
return unless simple_parameter_pattern?(pattern)
|
|
131
146
|
|
|
132
147
|
ruby_name = direct_method_definition_name(name_sym)
|
|
@@ -135,7 +150,8 @@ module Kapusta
|
|
|
135
150
|
|
|
136
151
|
body_env = env.child
|
|
137
152
|
params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
|
|
138
|
-
body_code, = emit_sequence(body, body_env, :toplevel,
|
|
153
|
+
body_code, = emit_sequence(body, body_env, :toplevel,
|
|
154
|
+
allow_method_definitions: false, result: false)
|
|
139
155
|
header = params.empty? ? "def #{ruby_name}" : "def #{ruby_name}(#{params.join(', ')})"
|
|
140
156
|
[
|
|
141
157
|
header,
|
|
@@ -169,12 +185,14 @@ module Kapusta
|
|
|
169
185
|
end
|
|
170
186
|
|
|
171
187
|
def emit_method_body(pattern, body, env)
|
|
188
|
+
validate_fn_params!(pattern)
|
|
172
189
|
return emit_simple_method_body(pattern, body, env) if simple_parameter_pattern?(pattern)
|
|
173
190
|
|
|
174
191
|
args_var = temp('args')
|
|
175
192
|
method_env = env.child
|
|
176
193
|
bindings_code, body_env = emit_pattern_bind(pattern, args_var, method_env)
|
|
177
|
-
body_code, = emit_sequence(body, body_env, :toplevel,
|
|
194
|
+
body_code, = emit_sequence(body, body_env, :toplevel,
|
|
195
|
+
allow_method_definitions: false, result: false)
|
|
178
196
|
block_locals = pattern_names(pattern).map { |name| body_env.lookup(name) }.uniq
|
|
179
197
|
block_locals_clause = block_locals.empty? ? '' : "; #{block_locals.join(', ')}"
|
|
180
198
|
["do |*#{args_var}#{block_locals_clause}|", join_code(bindings_code, body_code)]
|
|
@@ -245,6 +263,7 @@ module Kapusta
|
|
|
245
263
|
check_destructure_value!(pattern, value_form)
|
|
246
264
|
value_code = emit_expr(value_form, child_env, current_scope)
|
|
247
265
|
bind_code, child_env = emit_pattern_bind(pattern, value_code, child_env)
|
|
266
|
+
walk_pattern_syms(pattern) { |sym| mark_mutability(child_env, sym, mutable: false) }
|
|
248
267
|
binding_codes << bind_code
|
|
249
268
|
i += 2
|
|
250
269
|
end
|
|
@@ -290,6 +309,21 @@ module Kapusta
|
|
|
290
309
|
@binding_mutability[ruby_name] = mutable
|
|
291
310
|
end
|
|
292
311
|
|
|
312
|
+
def walk_pattern_syms(pattern, &block)
|
|
313
|
+
case pattern
|
|
314
|
+
when Sym
|
|
315
|
+
yield pattern unless pattern.name == '_'
|
|
316
|
+
when Vec
|
|
317
|
+
pattern.items.each do |item|
|
|
318
|
+
next if item.is_a?(Sym) && ['&', '...'].include?(item.name)
|
|
319
|
+
|
|
320
|
+
walk_pattern_syms(item, &block)
|
|
321
|
+
end
|
|
322
|
+
when HashLit
|
|
323
|
+
pattern.pairs.each { |pair| walk_pattern_syms(pair[1], &block) }
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
293
327
|
def mutable_binding?(env, name)
|
|
294
328
|
ruby_name = env.lookup_if_defined(name)
|
|
295
329
|
return false unless ruby_name
|
|
@@ -4,6 +4,8 @@ module Kapusta
|
|
|
4
4
|
module Compiler
|
|
5
5
|
module EmitterModules
|
|
6
6
|
module Collections
|
|
7
|
+
include LuaCompat::Emission
|
|
8
|
+
|
|
7
9
|
private
|
|
8
10
|
|
|
9
11
|
def emit_icollect(args, env, current_scope)
|
|
@@ -16,11 +18,44 @@ module Kapusta
|
|
|
16
18
|
|
|
17
19
|
def emit_collect(args, env, current_scope)
|
|
18
20
|
result_var = temp('result')
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
values_form = simple_values_call(args[1]) if args.length == 2
|
|
22
|
+
emit_iteration(args[0], env, current_scope,
|
|
23
|
+
method: 'each_with_object({})', extra_block_param: result_var) do |iter_env|
|
|
24
|
+
if values_form
|
|
25
|
+
emit_collect_values_step(result_var, values_form, iter_env, current_scope)
|
|
26
|
+
else
|
|
27
|
+
body = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
|
|
28
|
+
emit_hash_collection_step(result_var, body)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def simple_values_call(form)
|
|
34
|
+
return unless form.is_a?(List) && form.items.length == 3
|
|
35
|
+
|
|
36
|
+
head = form.head
|
|
37
|
+
form if head.is_a?(Sym) && head.name == 'values'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def emit_collect_values_step(result_var, values_form, iter_env, current_scope)
|
|
41
|
+
key_form = values_form.items[1]
|
|
42
|
+
val_form = values_form.items[2]
|
|
43
|
+
key_code = emit_expr(key_form, iter_env, current_scope)
|
|
44
|
+
val_code = emit_expr(val_form, iter_env, current_scope)
|
|
45
|
+
assignment = "#{result_var}[#{key_code}] = #{val_code}"
|
|
46
|
+
guards = []
|
|
47
|
+
guards << "#{key_code}.nil?" unless definitely_non_nil?(key_form)
|
|
48
|
+
guards << "#{val_code}.nil?" unless definitely_non_nil?(val_form)
|
|
49
|
+
return assignment if guards.empty?
|
|
50
|
+
|
|
51
|
+
"#{assignment} unless #{guards.join(' || ')}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def definitely_non_nil?(form)
|
|
55
|
+
case form
|
|
56
|
+
when Numeric, String, ::Symbol, TrueClass, FalseClass then true
|
|
57
|
+
else false
|
|
22
58
|
end
|
|
23
|
-
emit_collection_result(result_var, '{}', iter_code)
|
|
24
59
|
end
|
|
25
60
|
|
|
26
61
|
def emit_fcollect(args, env, current_scope)
|
|
@@ -63,29 +98,8 @@ module Kapusta
|
|
|
63
98
|
end
|
|
64
99
|
|
|
65
100
|
def try_emit_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var, init_code, body_forms)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
coll_code = emit_expr(iter_expr.items[1], env, current_scope)
|
|
69
|
-
case iter_expr.head.name
|
|
70
|
-
when 'ipairs'
|
|
71
|
-
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
72
|
-
if ignored_pattern?(binding_pats[0])
|
|
73
|
-
body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
|
|
74
|
-
return inject_block(coll_code, "#{acc_var}, #{value_var}", init_code, value_bind || '', body_code)
|
|
75
|
-
end
|
|
76
|
-
index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
|
|
77
|
-
bind_code = [index_bind, value_bind].compact.join("\n")
|
|
78
|
-
body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
|
|
79
|
-
inject_block("#{coll_code}.each_with_index", "#{acc_var}, (#{value_var}, #{index_var})",
|
|
80
|
-
init_code, bind_code, body_code)
|
|
81
|
-
when 'pairs'
|
|
82
|
-
key_var, key_bind = bind_iteration_param(binding_pats[0], 'key', body_env)
|
|
83
|
-
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
84
|
-
bind_code = [key_bind, value_bind].compact.join("\n")
|
|
85
|
-
body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
|
|
86
|
-
inject_block(coll_code, "#{acc_var}, (#{key_var}, #{value_var})",
|
|
87
|
-
init_code, bind_code, body_code)
|
|
88
|
-
end
|
|
101
|
+
emit_lua_compat_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var,
|
|
102
|
+
init_code, body_forms)
|
|
89
103
|
end
|
|
90
104
|
|
|
91
105
|
def inject_block(receiver, params, init_code, bind_code, body_code)
|
|
@@ -153,54 +167,32 @@ module Kapusta
|
|
|
153
167
|
end
|
|
154
168
|
end
|
|
155
169
|
|
|
156
|
-
def emit_iteration(bindings_vec, env, current_scope, method: 'each')
|
|
170
|
+
def emit_iteration(bindings_vec, env, current_scope, method: 'each', extra_block_param: nil, &block)
|
|
157
171
|
emit_error!(:each_no_binding) unless bindings_vec.is_a?(Vec)
|
|
158
172
|
|
|
159
173
|
items = bindings_vec.items
|
|
160
174
|
iter_expr = items.last
|
|
161
175
|
binding_pats = items[0...-1]
|
|
162
176
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
body_env = env.child
|
|
167
|
-
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
168
|
-
coll_code = emit_expr(iter_expr.items[1], env, current_scope)
|
|
169
|
-
if ignored_pattern?(binding_pats[0])
|
|
170
|
-
bind_code = value_bind || ''
|
|
171
|
-
body_code = yield(body_env)
|
|
172
|
-
return iteration_block("#{coll_code}.#{method} do |#{value_var}|", bind_code, body_code)
|
|
173
|
-
end
|
|
174
|
-
index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
|
|
175
|
-
bind_code = [index_bind, value_bind].compact.join("\n")
|
|
176
|
-
body_code = yield(body_env)
|
|
177
|
-
receiver = method == 'each' ? "#{coll_code}.each_with_index" : "#{coll_code}.each_with_index.#{method}"
|
|
178
|
-
header = "#{receiver} do |#{value_var}, #{index_var}|"
|
|
179
|
-
return iteration_block(header, bind_code, body_code)
|
|
180
|
-
when 'pairs'
|
|
181
|
-
body_env = env.child
|
|
182
|
-
key_var, key_bind = bind_iteration_param(binding_pats[0], 'key', body_env)
|
|
183
|
-
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
184
|
-
bind_code = [key_bind, value_bind].compact.join("\n")
|
|
185
|
-
body_code = yield(body_env)
|
|
186
|
-
header = "#{emit_expr(iter_expr.items[1], env, current_scope)}.#{method} do |#{key_var}, #{value_var}|"
|
|
187
|
-
return iteration_block(header, bind_code, body_code)
|
|
188
|
-
end
|
|
189
|
-
end
|
|
177
|
+
lua_iteration = emit_lua_compat_iteration(iter_expr, binding_pats, env, current_scope,
|
|
178
|
+
method:, extra_block_param:, &block)
|
|
179
|
+
return lua_iteration if lua_iteration
|
|
190
180
|
|
|
191
181
|
coll_code = emit_expr(iter_expr, env, current_scope)
|
|
192
182
|
if binding_pats.length == 1
|
|
193
183
|
body_env = env.child
|
|
194
184
|
value_var, bind_code = bind_iteration_param(binding_pats[0], 'value', body_env)
|
|
195
|
-
body_code =
|
|
196
|
-
|
|
185
|
+
body_code = block.call(body_env)
|
|
186
|
+
params = extra_block_param ? "#{value_var}, #{extra_block_param}" : value_var
|
|
187
|
+
iteration_block("#{coll_code}.#{method} do |#{params}|", bind_code || '', body_code)
|
|
197
188
|
else
|
|
198
189
|
parts_var = temp('parts')
|
|
199
190
|
body_env = env.child
|
|
200
191
|
pairs = binding_pats.each_with_index.map { |pattern, i| [pattern, "#{parts_var}[#{i}]"] }
|
|
201
192
|
bind_code, body_env = emit_iteration_bindings(pairs, body_env)
|
|
202
|
-
body_code =
|
|
203
|
-
|
|
193
|
+
body_code = block.call(body_env)
|
|
194
|
+
params = extra_block_param ? "#{parts_var}, #{extra_block_param}" : "*#{parts_var}"
|
|
195
|
+
iteration_block("#{coll_code}.#{method} do |#{params}|", bind_code, body_code)
|
|
204
196
|
end
|
|
205
197
|
end
|
|
206
198
|
|
|
@@ -57,21 +57,38 @@ module Kapusta
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def emit_case(args, env, current_scope, mode)
|
|
60
|
+
emit_error!(:case_no_subject) if args.empty?
|
|
61
|
+
|
|
60
62
|
clauses = args[1..]
|
|
61
63
|
emit_error!(:case_no_patterns) if clauses.empty?
|
|
62
64
|
emit_error!(:case_odd_patterns) if clauses.length.odd?
|
|
63
65
|
|
|
66
|
+
value_code = emit_expr(args[0], env, current_scope)
|
|
67
|
+
if simple_case_subject?(args[0]) && simple_expression?(value_code)
|
|
68
|
+
body = try_emit_native_case(value_code, clauses, env, current_scope, mode)
|
|
69
|
+
emit_error!(:case_unsupported) unless body
|
|
70
|
+
return body
|
|
71
|
+
end
|
|
72
|
+
|
|
64
73
|
value_var = temp('case_value')
|
|
65
74
|
body = try_emit_native_case(value_var, clauses, env, current_scope, mode)
|
|
66
75
|
emit_error!(:case_unsupported) unless body
|
|
67
76
|
[
|
|
68
77
|
'(-> do',
|
|
69
|
-
indent("#{value_var} = #{
|
|
78
|
+
indent("#{value_var} = #{value_code}"),
|
|
70
79
|
indent(body),
|
|
71
80
|
'end).call'
|
|
72
81
|
].join("\n")
|
|
73
82
|
end
|
|
74
83
|
|
|
84
|
+
def simple_case_subject?(form)
|
|
85
|
+
case form
|
|
86
|
+
when Sym then !form.dotted?
|
|
87
|
+
when Numeric, String, Symbol, true, false, nil then true
|
|
88
|
+
else false
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
75
92
|
def try_emit_native_case(value_var, clauses, env, current_scope, mode)
|
|
76
93
|
arms = []
|
|
77
94
|
i = 0
|
|
@@ -92,10 +109,15 @@ module Kapusta
|
|
|
92
109
|
arms.concat(sub_arms)
|
|
93
110
|
i += 2
|
|
94
111
|
end
|
|
95
|
-
arms << ['else', indent('nil')].join("\n")
|
|
112
|
+
arms << ['else', indent('nil')].join("\n") unless wildcard_last?(clauses)
|
|
96
113
|
["case #{value_var}", *arms, 'end'].join("\n")
|
|
97
114
|
end
|
|
98
115
|
|
|
116
|
+
def wildcard_last?(clauses)
|
|
117
|
+
last_pattern = clauses[-2]
|
|
118
|
+
last_pattern.is_a?(Sym) && last_pattern.name == '_'
|
|
119
|
+
end
|
|
120
|
+
|
|
99
121
|
def try_native_arm(pattern, body, where_guards, env, current_scope, mode)
|
|
100
122
|
allow_pins = !where_guards.empty? && mode == :case
|
|
101
123
|
plan = native_pattern_plan(pattern, env, mode:, allow_pins:)
|
|
@@ -111,8 +111,6 @@ module Kapusta
|
|
|
111
111
|
when 'quasi-vec-tail' then emit_quasi_vec_tail(args, env, current_scope)
|
|
112
112
|
when 'quasi-hash' then emit_quasi_hash(args, env, current_scope)
|
|
113
113
|
when 'quasi-gensym' then emit_quasi_gensym(args[0], env, current_scope)
|
|
114
|
-
when 'macro', 'macros', 'import-macros'
|
|
115
|
-
emit_error!(:special_must_be_toplevel, name:)
|
|
116
114
|
else
|
|
117
115
|
emit_error!(:unknown_special_form, name:)
|
|
118
116
|
end
|
|
@@ -427,11 +427,12 @@ module Kapusta
|
|
|
427
427
|
def emit_sym(sym, env)
|
|
428
428
|
name = sym.name
|
|
429
429
|
return 'self' if name == 'self'
|
|
430
|
-
return 'Float::INFINITY' if name == 'math.huge'
|
|
431
430
|
|
|
432
431
|
if (binding = env.lookup_if_defined(sym))
|
|
433
432
|
return binding_value_code(binding)
|
|
434
433
|
end
|
|
434
|
+
|
|
435
|
+
emit_error!(:unexpected_vararg) if name == '...'
|
|
435
436
|
return emit_multisym_value(sym, env) if sym.dotted?
|
|
436
437
|
return 'ARGV' if name == 'ARGV'
|
|
437
438
|
return name if name.match?(/\A[A-Z]/)
|
|
@@ -246,12 +246,60 @@ module Kapusta
|
|
|
246
246
|
end
|
|
247
247
|
|
|
248
248
|
def compile_native_hash(pattern, env, mode:, allow_pins:, state:)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
249
|
+
sym_pairs, other_pairs = pattern.pairs.partition { |key, _| key.is_a?(Symbol) }
|
|
250
|
+
sym_parts = sym_pairs.map do |key, value|
|
|
252
251
|
"#{key}: #{compile_native_pattern(value, env, mode:, allow_pins:, state:)}"
|
|
253
252
|
end
|
|
254
|
-
"{#{
|
|
253
|
+
return "{#{sym_parts.join(', ')}}" if other_pairs.empty?
|
|
254
|
+
|
|
255
|
+
capture = temp('hash')
|
|
256
|
+
sym_pattern = sym_parts.empty? ? 'Hash' : "{#{sym_parts.join(', ')}}"
|
|
257
|
+
other_pairs.each do |key, value|
|
|
258
|
+
compile_native_hash_value(value, capture, compile_native_hash_key(key), env, mode:, state:)
|
|
259
|
+
end
|
|
260
|
+
"#{sym_pattern} => #{capture}"
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def compile_native_hash_key(key)
|
|
264
|
+
case key
|
|
265
|
+
when Symbol, String, Numeric, true, false then key.inspect
|
|
266
|
+
when nil then 'nil'
|
|
267
|
+
else raise PatternNotTranslatable
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def compile_native_hash_value(value, capture, key_code, env, mode:, state:)
|
|
272
|
+
lookup = "#{capture}[#{key_code}]"
|
|
273
|
+
case value
|
|
274
|
+
when Sym
|
|
275
|
+
name = value.name
|
|
276
|
+
return if name == '_'
|
|
277
|
+
|
|
278
|
+
if nil_allowing_pattern_name?(name)
|
|
279
|
+
raise PatternNotTranslatable if state[:bound_names].key?(name)
|
|
280
|
+
|
|
281
|
+
state[:bound_names][name] = true
|
|
282
|
+
state[:binding_names] << name
|
|
283
|
+
state[:guards] << "(#{sanitize_local(name)} = #{lookup}; true)"
|
|
284
|
+
else
|
|
285
|
+
binding = mode == :match ? env.lookup_if_defined(name) : nil
|
|
286
|
+
if state[:bound_names].key?(name)
|
|
287
|
+
raise PatternNotTranslatable
|
|
288
|
+
elsif binding
|
|
289
|
+
state[:guards] << "#{lookup} == #{binding_value_code(binding)}"
|
|
290
|
+
else
|
|
291
|
+
state[:bound_names][name] = true
|
|
292
|
+
state[:binding_names] << name
|
|
293
|
+
state[:guards] << "!(#{sanitize_local(name)} = #{lookup}).nil?"
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
when nil
|
|
297
|
+
state[:guards] << "#{lookup}.nil?"
|
|
298
|
+
when Symbol, String, Numeric, true, false
|
|
299
|
+
state[:guards] << "#{lookup} == #{value.inspect}"
|
|
300
|
+
else
|
|
301
|
+
raise PatternNotTranslatable
|
|
302
|
+
end
|
|
255
303
|
end
|
|
256
304
|
|
|
257
305
|
def compile_native_pin(pattern, env, mode:, allow_pins:)
|
|
@@ -128,7 +128,7 @@ module Kapusta
|
|
|
128
128
|
code, current_env = emit_form_in_sequence(form, current_env, current_scope,
|
|
129
129
|
allow_method_definitions:,
|
|
130
130
|
result_needed: result && index == forms.length - 1)
|
|
131
|
-
codes << code
|
|
131
|
+
codes << code unless code.empty?
|
|
132
132
|
end
|
|
133
133
|
[codes.join("\n"), current_env]
|
|
134
134
|
end
|