kapusta 0.4.1 → 0.7.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 +38 -6
- data/examples/classify-wallet.kap +11 -0
- data/examples/even-squares.kap +22 -7
- data/examples/power-of-three.kap +12 -0
- data/examples/roman-to-integer.kap +3 -3
- data/exe/kapusta-ls +14 -0
- data/kapusta.gemspec +2 -2
- data/lib/kapusta/ast.rb +38 -4
- data/lib/kapusta/cli.rb +3 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +90 -10
- data/lib/kapusta/compiler/emitter/collections.rb +85 -49
- data/lib/kapusta/compiler/emitter/control_flow.rb +31 -6
- data/lib/kapusta/compiler/emitter/expressions.rb +25 -16
- data/lib/kapusta/compiler/emitter/interop.rb +8 -5
- data/lib/kapusta/compiler/emitter/patterns.rb +74 -5
- data/lib/kapusta/compiler/emitter/support.rb +45 -23
- data/lib/kapusta/compiler/emitter.rb +1 -1
- data/lib/kapusta/compiler/lua_compat.rb +149 -0
- data/lib/kapusta/compiler/macro_expander.rb +57 -25
- data/lib/kapusta/compiler/normalizer.rb +39 -28
- data/lib/kapusta/compiler.rb +10 -4
- data/lib/kapusta/error.rb +25 -1
- data/lib/kapusta/errors.rb +70 -0
- data/lib/kapusta/formatter.rb +16 -5
- data/lib/kapusta/lsp.rb +258 -0
- data/lib/kapusta/reader.rb +33 -13
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +1 -0
- data/spec/examples_errors_spec.rb +354 -0
- data/spec/formatter_spec.rb +7 -6
- data/spec/lsp_spec.rb +83 -0
- metadata +10 -2
- data/spec/reader_spec.rb +0 -26
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 76f35530eebc269dfec62ee1f36f6cc11a252ee4590eeac961c9b47e4e49ca09
|
|
4
|
+
data.tar.gz: a0ee60d21fb9a6066915d916e465bef09d9208c0469712673db35bea83efd7c4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 350e111e7cfe045723ea1982c18762727d64ea615219d904a79af2933874af5ce355076e242ed9c0bf5e91d135dbd4498b2b5ae107b05035f205e3ccb87bbb54
|
|
7
|
+
data.tar.gz: 7e242c3fb28fb0d5d2839f7e3d6e5dca96dda90204e1a81024111e1fc5ef4f6c7abb2a6e3628e5894eac46ce9d1c10f21db80bcae63da3ec9b4874a747b1bba1
|
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
|
@@ -5,15 +5,18 @@ require 'open3'
|
|
|
5
5
|
|
|
6
6
|
ROOT = File.expand_path('..', __dir__)
|
|
7
7
|
EXAMPLES = File.join(ROOT, 'examples')
|
|
8
|
+
EXAMPLES_ERRORS = File.join(ROOT, 'examples-errors')
|
|
8
9
|
KAPUSTA = File.join(ROOT, 'exe', 'kapusta')
|
|
9
10
|
KAPFMT = File.join(ROOT, 'exe', 'kapfmt')
|
|
10
11
|
|
|
11
12
|
COMPATIBLE = %w[
|
|
12
13
|
ackermann.kap
|
|
13
14
|
anonymous-greeter.kap
|
|
15
|
+
classify-wallet.kap
|
|
14
16
|
climbing-stairs.kap
|
|
15
17
|
describe.kap
|
|
16
18
|
destructure.kap
|
|
19
|
+
even-squares.kap
|
|
17
20
|
factorial.kap
|
|
18
21
|
fib.kap
|
|
19
22
|
fizzbuzz.kap
|
|
@@ -29,6 +32,7 @@ COMPATIBLE = %w[
|
|
|
29
32
|
match.kap
|
|
30
33
|
min-max.kap
|
|
31
34
|
or-patterns.kap
|
|
35
|
+
power-of-three.kap
|
|
32
36
|
packet-router.kap
|
|
33
37
|
points.kap
|
|
34
38
|
primes.kap
|
|
@@ -40,8 +44,8 @@ COMPATIBLE = %w[
|
|
|
40
44
|
underscore-patterns.kap
|
|
41
45
|
].freeze
|
|
42
46
|
|
|
43
|
-
def run(cmd, file)
|
|
44
|
-
out, err, status = Open3.capture3(cmd, file, chdir:
|
|
47
|
+
def run(cmd, file, chdir: EXAMPLES)
|
|
48
|
+
out, err, status = Open3.capture3(cmd, file, chdir:)
|
|
45
49
|
[out, err, status]
|
|
46
50
|
rescue StandardError => e
|
|
47
51
|
['', e.message, nil]
|
|
@@ -125,6 +129,17 @@ def check(name)
|
|
|
125
129
|
[:ok, nil]
|
|
126
130
|
end
|
|
127
131
|
|
|
132
|
+
def check_error(name)
|
|
133
|
+
path = File.join(EXAMPLES_ERRORS, name)
|
|
134
|
+
_, _, k_status = run(KAPUSTA, path, chdir: EXAMPLES_ERRORS)
|
|
135
|
+
_, _, f_status = run('fennel', path, chdir: EXAMPLES_ERRORS)
|
|
136
|
+
|
|
137
|
+
return [:fail, 'fennel unexpectedly succeeded'] if f_status&.success?
|
|
138
|
+
return [:fail, "kapusta unexpectedly succeeded (exit #{k_status&.exitstatus})"] if k_status&.success?
|
|
139
|
+
|
|
140
|
+
[:ok, nil]
|
|
141
|
+
end
|
|
142
|
+
|
|
128
143
|
puts "Checking #{COMPATIBLE.size} compatible examples (kapfmt vs fnlfmt, kapusta vs fennel)...\n\n"
|
|
129
144
|
|
|
130
145
|
failures = []
|
|
@@ -142,13 +157,30 @@ COMPATIBLE.each do |name|
|
|
|
142
157
|
end
|
|
143
158
|
end
|
|
144
159
|
|
|
160
|
+
error_files = Dir.children(EXAMPLES_ERRORS).select { |n| n.end_with?('.kap') }.sort
|
|
161
|
+
puts "\nChecking #{error_files.size} error examples (both kapusta and fennel must fail)...\n\n"
|
|
162
|
+
|
|
163
|
+
error_failures = []
|
|
164
|
+
error_files.each do |name|
|
|
165
|
+
status, reason = check_error(name)
|
|
166
|
+
case status
|
|
167
|
+
when :ok
|
|
168
|
+
puts "OK #{name}"
|
|
169
|
+
when :fail
|
|
170
|
+
error_failures << [name, reason]
|
|
171
|
+
puts "FAIL #{name}"
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
145
175
|
puts
|
|
146
|
-
|
|
147
|
-
|
|
176
|
+
total_failures = failures + error_failures
|
|
177
|
+
total = COMPATIBLE.size + error_files.size
|
|
178
|
+
if total_failures.empty?
|
|
179
|
+
puts "All #{total} examples (#{COMPATIBLE.size} compatible + #{error_files.size} error) match expectations."
|
|
148
180
|
exit 0
|
|
149
181
|
else
|
|
150
|
-
puts "#{
|
|
151
|
-
|
|
182
|
+
puts "#{total_failures.size} of #{total} examples failed:\n\n"
|
|
183
|
+
total_failures.each do |name, reason|
|
|
152
184
|
puts " * #{name}"
|
|
153
185
|
reason.each_line { |l| puts " #{l.rstrip}" }
|
|
154
186
|
puts
|
|
@@ -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}))
|
data/examples/even-squares.kap
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
|
-
(
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
(fn even? [n] (= 0 (% n 2)))
|
|
2
|
+
|
|
3
|
+
(fn select [tbl pred]
|
|
4
|
+
(icollect [_ x (ipairs tbl)]
|
|
5
|
+
(when (pred x) x)))
|
|
6
|
+
|
|
7
|
+
(fn map [tbl f]
|
|
8
|
+
(icollect [_ x (ipairs tbl)]
|
|
9
|
+
(f x)))
|
|
10
|
+
|
|
11
|
+
(fn join [tbl sep]
|
|
12
|
+
(var s "")
|
|
13
|
+
(each [_ x (ipairs tbl)]
|
|
14
|
+
(if (= s "")
|
|
15
|
+
(set s (.. x))
|
|
16
|
+
(set s (.. s sep x))))
|
|
17
|
+
s)
|
|
18
|
+
|
|
19
|
+
(let [xs [1 2 3 4 5 6]
|
|
20
|
+
filtered (select xs even?)
|
|
21
|
+
squared (map filtered #(* $ $))]
|
|
22
|
+
(print (join squared ", ")))
|
|
@@ -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))
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
(fn roman-to-integer [s]
|
|
2
|
-
(let [
|
|
2
|
+
(let [value-map {"I" 1 "V" 5 "X" 10 "L" 50 "C" 100 "D" 500 "M" 1000}
|
|
3
3
|
chars (s.chars)
|
|
4
4
|
n (length chars)]
|
|
5
5
|
(var total 0)
|
|
6
6
|
(var i 0)
|
|
7
7
|
(while (< i n)
|
|
8
|
-
(let [curr (.
|
|
9
|
-
ahead (if (< (+ i 1) n) (.
|
|
8
|
+
(let [curr (. value-map (. chars i))
|
|
9
|
+
ahead (if (< (+ i 1) n) (. value-map (. chars (+ i 1))) 0)
|
|
10
10
|
subtract? (< curr ahead)]
|
|
11
11
|
(set total (+ total (if subtract? (- ahead curr) curr)))
|
|
12
12
|
(set i (+ i (if subtract? 2 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
|
data/lib/kapusta/ast.rb
CHANGED
|
@@ -13,6 +13,7 @@ module Kapusta
|
|
|
13
13
|
|
|
14
14
|
class Sym
|
|
15
15
|
attr_reader :name
|
|
16
|
+
attr_accessor :line, :column
|
|
16
17
|
|
|
17
18
|
def initialize(name)
|
|
18
19
|
@name = name.to_s
|
|
@@ -23,7 +24,7 @@ module Kapusta
|
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def inspect
|
|
26
|
-
|
|
27
|
+
@name.to_s
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
def ==(other)
|
|
@@ -76,7 +77,7 @@ module Kapusta
|
|
|
76
77
|
|
|
77
78
|
class Vec
|
|
78
79
|
attr_reader :items
|
|
79
|
-
attr_accessor :multiline_source
|
|
80
|
+
attr_accessor :multiline_source, :line, :column
|
|
80
81
|
|
|
81
82
|
def initialize(items)
|
|
82
83
|
@items = items
|
|
@@ -86,17 +87,25 @@ module Kapusta
|
|
|
86
87
|
def to_ary
|
|
87
88
|
@items
|
|
88
89
|
end
|
|
90
|
+
|
|
91
|
+
def inspect
|
|
92
|
+
"[#{@items.map(&:inspect).join(' ')}]"
|
|
93
|
+
end
|
|
89
94
|
end
|
|
90
95
|
|
|
91
96
|
class HashLit
|
|
92
97
|
attr_reader :entries
|
|
93
|
-
attr_accessor :multiline_source
|
|
98
|
+
attr_accessor :multiline_source, :line, :column
|
|
94
99
|
|
|
95
100
|
def initialize(entries)
|
|
96
101
|
@entries = entries
|
|
97
102
|
@multiline_source = false
|
|
98
103
|
end
|
|
99
104
|
|
|
105
|
+
def inspect
|
|
106
|
+
pairs.map { |k, v| "#{k.inspect} #{v.inspect}" }.then { |p| "{#{p.join(' ')}}" }
|
|
107
|
+
end
|
|
108
|
+
|
|
100
109
|
def pairs
|
|
101
110
|
@entries.grep(Array)
|
|
102
111
|
end
|
|
@@ -108,7 +117,7 @@ module Kapusta
|
|
|
108
117
|
|
|
109
118
|
class List
|
|
110
119
|
attr_reader :items
|
|
111
|
-
attr_accessor :multiline_source
|
|
120
|
+
attr_accessor :multiline_source, :line, :column
|
|
112
121
|
|
|
113
122
|
def initialize(items)
|
|
114
123
|
@items = items
|
|
@@ -126,6 +135,10 @@ module Kapusta
|
|
|
126
135
|
def empty?
|
|
127
136
|
@items.empty?
|
|
128
137
|
end
|
|
138
|
+
|
|
139
|
+
def inspect
|
|
140
|
+
"(#{@items.map(&:inspect).join(' ')})"
|
|
141
|
+
end
|
|
129
142
|
end
|
|
130
143
|
|
|
131
144
|
class AutoGensym < Sym
|
|
@@ -134,27 +147,48 @@ module Kapusta
|
|
|
134
147
|
end
|
|
135
148
|
end
|
|
136
149
|
|
|
150
|
+
class MacroSym < Sym
|
|
151
|
+
def inspect
|
|
152
|
+
"#<MacroSym #{@name}>"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
137
156
|
class Quasiquote
|
|
138
157
|
attr_reader :form
|
|
158
|
+
attr_accessor :line, :column
|
|
139
159
|
|
|
140
160
|
def initialize(form)
|
|
141
161
|
@form = form
|
|
142
162
|
end
|
|
163
|
+
|
|
164
|
+
def inspect
|
|
165
|
+
"`#{@form.inspect}"
|
|
166
|
+
end
|
|
143
167
|
end
|
|
144
168
|
|
|
145
169
|
class Unquote
|
|
146
170
|
attr_reader :form
|
|
171
|
+
attr_accessor :line, :column
|
|
147
172
|
|
|
148
173
|
def initialize(form)
|
|
149
174
|
@form = form
|
|
150
175
|
end
|
|
176
|
+
|
|
177
|
+
def inspect
|
|
178
|
+
",#{@form.inspect}"
|
|
179
|
+
end
|
|
151
180
|
end
|
|
152
181
|
|
|
153
182
|
class UnquoteSplice
|
|
154
183
|
attr_reader :form
|
|
184
|
+
attr_accessor :line, :column
|
|
155
185
|
|
|
156
186
|
def initialize(form)
|
|
157
187
|
@form = form
|
|
158
188
|
end
|
|
189
|
+
|
|
190
|
+
def inspect
|
|
191
|
+
",@#{@form.inspect}"
|
|
192
|
+
end
|
|
159
193
|
end
|
|
160
194
|
end
|
data/lib/kapusta/cli.rb
CHANGED
|
@@ -13,6 +13,8 @@ module Kapusta
|
|
|
13
13
|
name_sym = args[0]
|
|
14
14
|
pattern = args[1]
|
|
15
15
|
body = args[2..]
|
|
16
|
+
emit_error!(:fn_no_params) unless name_sym.is_a?(Sym) && pattern.is_a?(Vec)
|
|
17
|
+
|
|
16
18
|
fn_env = env.child
|
|
17
19
|
ruby_name = define_local(fn_env, name_sym.name)
|
|
18
20
|
<<~RUBY.chomp
|
|
@@ -26,12 +28,14 @@ module Kapusta
|
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
def emit_lambda(pattern, body, env, current_scope)
|
|
31
|
+
validate_fn_params!(pattern)
|
|
29
32
|
return emit_simple_lambda(pattern, body, env, current_scope) if simple_parameter_pattern?(pattern)
|
|
30
33
|
|
|
31
34
|
args_var = temp('args')
|
|
32
35
|
body_env = env.child
|
|
33
36
|
bindings_code, body_env = emit_pattern_bind(pattern, args_var, body_env)
|
|
34
|
-
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)
|
|
35
39
|
block_locals = pattern_names(pattern).map { |name| body_env.lookup(name) }.uniq
|
|
36
40
|
block_locals_clause = block_locals.empty? ? '' : "; #{block_locals.join(', ')}"
|
|
37
41
|
[
|
|
@@ -54,12 +58,26 @@ module Kapusta
|
|
|
54
58
|
def build_simple_block_parts(pattern, body, env, current_scope)
|
|
55
59
|
body_env = env.child
|
|
56
60
|
params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
|
|
57
|
-
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)
|
|
58
63
|
[params, body_code]
|
|
59
64
|
end
|
|
60
65
|
|
|
61
66
|
def simple_parameter_pattern?(pattern)
|
|
62
|
-
pattern.is_a?(Vec) && pattern.items.all?
|
|
67
|
+
pattern.is_a?(Vec) && pattern.items.all? do |item|
|
|
68
|
+
item.is_a?(Sym) && (!item.dotted? || item.name == '...') && item.name != '&'
|
|
69
|
+
end
|
|
70
|
+
end
|
|
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
|
|
63
81
|
end
|
|
64
82
|
|
|
65
83
|
def emit_definition_form(form, env, current_scope)
|
|
@@ -123,6 +141,7 @@ module Kapusta
|
|
|
123
141
|
end
|
|
124
142
|
|
|
125
143
|
def emit_direct_method_definition(name_sym, pattern, body, env)
|
|
144
|
+
validate_fn_params!(pattern)
|
|
126
145
|
return unless simple_parameter_pattern?(pattern)
|
|
127
146
|
|
|
128
147
|
ruby_name = direct_method_definition_name(name_sym)
|
|
@@ -131,7 +150,8 @@ module Kapusta
|
|
|
131
150
|
|
|
132
151
|
body_env = env.child
|
|
133
152
|
params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
|
|
134
|
-
body_code, = emit_sequence(body, body_env, :toplevel,
|
|
153
|
+
body_code, = emit_sequence(body, body_env, :toplevel,
|
|
154
|
+
allow_method_definitions: false, result: false)
|
|
135
155
|
header = params.empty? ? "def #{ruby_name}" : "def #{ruby_name}(#{params.join(', ')})"
|
|
136
156
|
[
|
|
137
157
|
header,
|
|
@@ -165,12 +185,14 @@ module Kapusta
|
|
|
165
185
|
end
|
|
166
186
|
|
|
167
187
|
def emit_method_body(pattern, body, env)
|
|
188
|
+
validate_fn_params!(pattern)
|
|
168
189
|
return emit_simple_method_body(pattern, body, env) if simple_parameter_pattern?(pattern)
|
|
169
190
|
|
|
170
191
|
args_var = temp('args')
|
|
171
192
|
method_env = env.child
|
|
172
193
|
bindings_code, body_env = emit_pattern_bind(pattern, args_var, method_env)
|
|
173
|
-
body_code, = emit_sequence(body, body_env, :toplevel,
|
|
194
|
+
body_code, = emit_sequence(body, body_env, :toplevel,
|
|
195
|
+
allow_method_definitions: false, result: false)
|
|
174
196
|
block_locals = pattern_names(pattern).map { |name| body_env.lookup(name) }.uniq
|
|
175
197
|
block_locals_clause = block_locals.empty? ? '' : "; #{block_locals.join(', ')}"
|
|
176
198
|
["do |*#{args_var}#{block_locals_clause}|", join_code(bindings_code, body_code)]
|
|
@@ -227,6 +249,9 @@ module Kapusta
|
|
|
227
249
|
|
|
228
250
|
def emit_let_parts(args, env, current_scope, result:)
|
|
229
251
|
bindings = args[0]
|
|
252
|
+
emit_error!(:let_odd_bindings) if bindings.items.length.odd?
|
|
253
|
+
emit_error!(:let_no_body) if args.length < 2
|
|
254
|
+
|
|
230
255
|
body = args[1..]
|
|
231
256
|
child_env = env.child
|
|
232
257
|
binding_codes = []
|
|
@@ -234,8 +259,11 @@ module Kapusta
|
|
|
234
259
|
i = 0
|
|
235
260
|
while i < items.length
|
|
236
261
|
pattern = items[i]
|
|
237
|
-
|
|
262
|
+
value_form = items[i + 1]
|
|
263
|
+
check_destructure_value!(pattern, value_form)
|
|
264
|
+
value_code = emit_expr(value_form, child_env, current_scope)
|
|
238
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) }
|
|
239
267
|
binding_codes << bind_code
|
|
240
268
|
i += 2
|
|
241
269
|
end
|
|
@@ -250,11 +278,15 @@ module Kapusta
|
|
|
250
278
|
end
|
|
251
279
|
|
|
252
280
|
def emit_local_form(form, env, current_scope)
|
|
281
|
+
emit_error!(:local_arity, form: form.head.name) unless form.items.length == 3
|
|
282
|
+
|
|
253
283
|
target = form.items[1]
|
|
254
284
|
value_code = emit_expr(form.items[2], env, current_scope)
|
|
255
285
|
|
|
256
286
|
if target.is_a?(Sym)
|
|
287
|
+
validate_binding_symbol!(target)
|
|
257
288
|
ruby_name = define_local(env, target.name)
|
|
289
|
+
mark_mutability(env, target.name, mutable: form.head.name == 'var')
|
|
258
290
|
["#{ruby_name} = #{value_code}\nnil", env]
|
|
259
291
|
else
|
|
260
292
|
bind_code, env = emit_pattern_bind(target, value_code, env)
|
|
@@ -262,11 +294,58 @@ module Kapusta
|
|
|
262
294
|
end
|
|
263
295
|
end
|
|
264
296
|
|
|
297
|
+
def check_destructure_value!(pattern, value_form)
|
|
298
|
+
return unless pattern.is_a?(Vec) || pattern.is_a?(HashLit)
|
|
299
|
+
|
|
300
|
+
case value_form
|
|
301
|
+
when String, Numeric, Symbol, true, false
|
|
302
|
+
emit_error!(:could_not_destructure_literal)
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def mark_mutability(env, name, mutable:)
|
|
307
|
+
@binding_mutability ||= {}
|
|
308
|
+
ruby_name = env.lookup(name)
|
|
309
|
+
@binding_mutability[ruby_name] = mutable
|
|
310
|
+
end
|
|
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
|
+
|
|
327
|
+
def mutable_binding?(env, name)
|
|
328
|
+
ruby_name = env.lookup_if_defined(name)
|
|
329
|
+
return false unless ruby_name
|
|
330
|
+
|
|
331
|
+
(@binding_mutability ||= {}).fetch(ruby_name, true)
|
|
332
|
+
end
|
|
333
|
+
|
|
265
334
|
def emit_local_expr(args, env, current_scope)
|
|
266
335
|
code, = emit_local_form(List.new([Sym.new('local'), *args]), env.child, current_scope)
|
|
267
336
|
"(-> do\n#{indent(code)}\nend).call"
|
|
268
337
|
end
|
|
269
338
|
|
|
339
|
+
def emit_global_expr(args, _env, _current_scope)
|
|
340
|
+
emit_error!(:global_arity) unless args.length == 2
|
|
341
|
+
unless args[0].is_a?(Sym)
|
|
342
|
+
emit_error!(:global_non_symbol_name, type: args[0].class.name.downcase, value: args[0].inspect)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
name = args[0].name
|
|
346
|
+
"$#{global_name(name)} = #{emit_expr(args[1], Env.new, :toplevel)}\nnil"
|
|
347
|
+
end
|
|
348
|
+
|
|
270
349
|
def emit_set_form(form, env, current_scope)
|
|
271
350
|
target = form.items[1]
|
|
272
351
|
value_code = emit_expr(form.items[2], env, current_scope)
|
|
@@ -275,7 +354,7 @@ module Kapusta
|
|
|
275
354
|
binding = env.lookup_if_defined(target.name)
|
|
276
355
|
ruby_name =
|
|
277
356
|
if binding
|
|
278
|
-
emit_error!(
|
|
357
|
+
emit_error!(:cannot_set_method_binding, name: target.name) if method_binding?(binding)
|
|
279
358
|
|
|
280
359
|
binding
|
|
281
360
|
else
|
|
@@ -319,7 +398,8 @@ module Kapusta
|
|
|
319
398
|
end
|
|
320
399
|
else
|
|
321
400
|
binding = env.lookup(target.name)
|
|
322
|
-
emit_error!(
|
|
401
|
+
emit_error!(:cannot_set_method_binding, name: target.name) if method_binding?(binding)
|
|
402
|
+
emit_error!(:expected_var, name: target.name) unless mutable_binding?(env, target.name)
|
|
323
403
|
|
|
324
404
|
emit_assignment(binding, value_code)
|
|
325
405
|
end
|
|
@@ -338,10 +418,10 @@ module Kapusta
|
|
|
338
418
|
elsif head.is_a?(Sym) && head.name == 'gvar'
|
|
339
419
|
emit_assignment("$#{global_name(target.items[1].name)}", value_code)
|
|
340
420
|
else
|
|
341
|
-
emit_error!(
|
|
421
|
+
emit_error!(:bad_set_target, target: target.inspect)
|
|
342
422
|
end
|
|
343
423
|
else
|
|
344
|
-
emit_error!(
|
|
424
|
+
emit_error!(:bad_set_target, target: target.inspect)
|
|
345
425
|
end
|
|
346
426
|
end
|
|
347
427
|
end
|