kapusta 0.1.4 → 0.2.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 +13 -0
- data/bin/compile-examples +24 -0
- data/examples/bank-account.kap +21 -0
- data/examples/baseball-game.kap +11 -0
- data/examples/climbing-stairs.kap +13 -0
- data/examples/doto-hygiene.kap +5 -0
- data/examples/happy-number.kap +20 -0
- data/examples/length-of-last-word.kap +7 -0
- data/examples/maximum-subarray.kap +12 -0
- data/examples/move-zeroes.kap +13 -0
- data/examples/stack.kap +27 -10
- data/examples/two-sum-hash.kap +17 -0
- data/examples/use_bank_account.rb +13 -0
- data/examples/valid-parentheses-1.kap +19 -0
- data/examples/valid-parentheses-2.kap +8 -0
- data/lib/kapusta/ast.rb +33 -3
- data/lib/kapusta/compiler/emitter/bindings.rb +135 -22
- data/lib/kapusta/compiler/emitter/control_flow.rb +4 -4
- data/lib/kapusta/compiler/emitter/expressions.rb +30 -14
- data/lib/kapusta/compiler/emitter/interop.rb +108 -41
- data/lib/kapusta/compiler/emitter/patterns.rb +14 -11
- data/lib/kapusta/compiler/emitter/support.rb +29 -14
- data/lib/kapusta/compiler/emitter.rb +1 -5
- data/lib/kapusta/compiler/normalizer.rb +42 -18
- data/lib/kapusta/compiler/runtime.rb +5 -156
- data/lib/kapusta/env.rb +31 -8
- data/lib/kapusta/formatter.rb +9 -10
- data/lib/kapusta/reader.rb +30 -6
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +62 -1
- data/spec/cli_spec.rb +25 -2
- data/spec/examples_spec.rb +234 -87
- data/spec/reader_spec.rb +26 -0
- metadata +17 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 53512e4cdebbb07a50f1bff53338574994140e5b16ddc5c1fd05e27b8cbc8a20
|
|
4
|
+
data.tar.gz: c7907d4901ecfb193c4df0c0b09bca2869babab65f6327d542c59a88a9edff51
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c185f6d33f64071522cef35498fb7310f47d4091d3873868361294ff40edb51e836cdb2c0168dac1f599bc9ccd7c230d34d276fa6ab1a3fca479a4a4c40b9025
|
|
7
|
+
data.tar.gz: dcf0acd60680784bb38685305f5454466e4c36358d08a2994e2ca988e5fa19d32fa6eafdb48bb1054681e5699daaddc4b9ebf73477f0450d8dc78482cfad0147
|
data/README.md
CHANGED
|
@@ -29,6 +29,18 @@ exe/kapusta --compile examples/fizzbuzz.kap > examples/fizzbuzz.rb
|
|
|
29
29
|
ruby examples/fizzbuzz.rb
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
## Using from Ruby
|
|
33
|
+
|
|
34
|
+
Ruby can require a `.kap` file and use it directly.
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
require 'kapusta'
|
|
38
|
+
Kapusta.require('./bank-account', relative_to: __FILE__)
|
|
39
|
+
account = BankAccount.new('Ada', 100)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
See `examples/bank-account.kap` and `examples/use_bank_account.rb`.
|
|
43
|
+
|
|
32
44
|
## Comparison with Fennel
|
|
33
45
|
|
|
34
46
|
Kapusta keeps most core Fennel forms. The main differences come from Ruby's runtime and object model.
|
|
@@ -40,6 +52,7 @@ Kapusta keeps most core Fennel forms. The main differences come from Ruby's runt
|
|
|
40
52
|
| `(. xs 1)` is the first element | `(. xs 0)` is the first element |
|
|
41
53
|
| `string.format`, `table.insert`, etc. | use Ruby methods and stdlib instead |
|
|
42
54
|
| `values` uses Lua multiple returns | `values` lowers to a Ruby array, usually destructured |
|
|
55
|
+
| `(print x)` is Lua's `print` (bare) | `(print x)` is Ruby's `p` (inspect-style) |
|
|
43
56
|
| `with-open`, `tail!` | not provided |
|
|
44
57
|
| macros | not provided for now |
|
|
45
58
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
SOURCE_DIR="$ROOT_DIR/examples"
|
|
6
|
+
TARGET_DIR="$ROOT_DIR/examples-compiled"
|
|
7
|
+
|
|
8
|
+
mkdir -p "$TARGET_DIR"
|
|
9
|
+
|
|
10
|
+
shopt -s nullglob
|
|
11
|
+
kap_files=("$SOURCE_DIR"/*.kap)
|
|
12
|
+
|
|
13
|
+
if ((${#kap_files[@]} == 0)); then
|
|
14
|
+
printf 'No .kap files found in %s\n' "$SOURCE_DIR" >&2
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
for kap_file in "${kap_files[@]}"; do
|
|
19
|
+
name="$(basename "$kap_file" .kap)"
|
|
20
|
+
ruby_file="$TARGET_DIR/$name.rb"
|
|
21
|
+
|
|
22
|
+
"$ROOT_DIR/exe/kapusta" --compile "$kap_file" > "$ruby_file"
|
|
23
|
+
printf 'Compiled %s -> %s\n' "$kap_file" "$ruby_file"
|
|
24
|
+
done
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
(class BankAccount)
|
|
2
|
+
|
|
3
|
+
(fn initialize [owner balance]
|
|
4
|
+
(set (ivar owner) owner)
|
|
5
|
+
(set (ivar balance) balance))
|
|
6
|
+
|
|
7
|
+
(fn owner []
|
|
8
|
+
(ivar owner))
|
|
9
|
+
|
|
10
|
+
(fn balance []
|
|
11
|
+
(ivar balance))
|
|
12
|
+
|
|
13
|
+
(fn deposit [amount]
|
|
14
|
+
(set (ivar balance)
|
|
15
|
+
(+ (ivar balance) amount))
|
|
16
|
+
self)
|
|
17
|
+
|
|
18
|
+
(fn withdraw [amount]
|
|
19
|
+
(set (ivar balance)
|
|
20
|
+
(- (ivar balance) amount))
|
|
21
|
+
self)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
(fn cal-points [ops]
|
|
2
|
+
(let [scores []]
|
|
3
|
+
(each [op ops]
|
|
4
|
+
(if (= op "C") (do (-?> (scores.pop) (: :abs)) nil)
|
|
5
|
+
(= op "D") (scores.push (* 2 (. scores -1)))
|
|
6
|
+
(= op "+") (scores.push (+ (. scores -1) (. scores -2)))
|
|
7
|
+
(scores.push (Integer op))))
|
|
8
|
+
scores.sum))
|
|
9
|
+
|
|
10
|
+
(print (cal-points ["5" "2" "C" "D" "+"]))
|
|
11
|
+
(print (cal-points ["5" "-2" "4" "C" "D" "9" "+" "+"]))
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
(fn sum-of-squares [n]
|
|
2
|
+
(var x n)
|
|
3
|
+
(var total 0)
|
|
4
|
+
(while (> x 0)
|
|
5
|
+
(let [d (% x 10)]
|
|
6
|
+
(set total (+ total (* d d)))
|
|
7
|
+
(set x (: (/ x 10) :floor))))
|
|
8
|
+
total)
|
|
9
|
+
|
|
10
|
+
(fn happy? [n]
|
|
11
|
+
(let [seen {}]
|
|
12
|
+
(var x n)
|
|
13
|
+
(while (and (not= x 1) (not (seen.key? x)))
|
|
14
|
+
(tset seen x true)
|
|
15
|
+
(set x (sum-of-squares x)))
|
|
16
|
+
(= x 1)))
|
|
17
|
+
|
|
18
|
+
(print (happy? 19))
|
|
19
|
+
(print (happy? 2))
|
|
20
|
+
(print (happy? 1))
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
(fn max-subarray [nums]
|
|
2
|
+
(var best (. nums 0))
|
|
3
|
+
(var curr (. nums 0))
|
|
4
|
+
(for [i 1 (- (length nums) 1)]
|
|
5
|
+
(let [n (. nums i)]
|
|
6
|
+
(set curr (if (> (+ curr n) n) (+ curr n) n))
|
|
7
|
+
(when (> curr best) (set best curr))))
|
|
8
|
+
best)
|
|
9
|
+
|
|
10
|
+
(print (max-subarray [-2 1 -3 4 -1 2 1 -5 4]))
|
|
11
|
+
(print (max-subarray [1]))
|
|
12
|
+
(print (max-subarray [5 4 -1 7 8]))
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
(fn move-zeroes [nums]
|
|
2
|
+
(var write 0)
|
|
3
|
+
(for [read 0 (- (length nums) 1)]
|
|
4
|
+
(when (not= (. nums read) 0)
|
|
5
|
+
(tset nums write (. nums read))
|
|
6
|
+
(set write (+ write 1))))
|
|
7
|
+
(for [i write (- (length nums) 1)]
|
|
8
|
+
(tset nums i 0))
|
|
9
|
+
nums)
|
|
10
|
+
|
|
11
|
+
(print (move-zeroes [0 1 0 3 12]))
|
|
12
|
+
(print (move-zeroes [0]))
|
|
13
|
+
(print (move-zeroes [1 2 3]))
|
data/examples/stack.kap
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
|
-
(class
|
|
1
|
+
(class MinStack)
|
|
2
2
|
|
|
3
3
|
(fn initialize []
|
|
4
|
-
(set (ivar xs) [])
|
|
4
|
+
(set (ivar xs) [])
|
|
5
|
+
(set (ivar mins) []))
|
|
5
6
|
|
|
6
|
-
(fn push
|
|
7
|
-
(let [xs (ivar xs)
|
|
8
|
-
|
|
7
|
+
(fn push [x]
|
|
8
|
+
(let [xs (ivar xs)
|
|
9
|
+
mins (ivar mins)
|
|
10
|
+
current-min (if (mins.empty?) x (< x (. mins -1)) x (. mins -1))]
|
|
11
|
+
(xs.push x)
|
|
12
|
+
(mins.push current-min))
|
|
9
13
|
self)
|
|
10
14
|
|
|
11
|
-
(fn pop
|
|
15
|
+
(fn pop []
|
|
16
|
+
(let [mins (ivar mins)]
|
|
17
|
+
(mins.pop))
|
|
12
18
|
(let [xs (ivar xs)]
|
|
13
19
|
(xs.pop)))
|
|
14
20
|
|
|
15
|
-
(fn
|
|
16
|
-
(
|
|
21
|
+
(fn top []
|
|
22
|
+
(. (ivar xs) -1))
|
|
23
|
+
|
|
24
|
+
(fn get-min []
|
|
25
|
+
(. (ivar mins) -1))
|
|
26
|
+
|
|
27
|
+
(let [s (MinStack.new)]
|
|
28
|
+
(s.push -2)
|
|
29
|
+
(s.push 0)
|
|
30
|
+
(s.push -3)
|
|
31
|
+
(print (s.get-min))
|
|
32
|
+
(s.pop)
|
|
33
|
+
(print (s.top))
|
|
34
|
+
(print (s.get-min)))
|
|
17
35
|
|
|
18
|
-
(
|
|
19
|
-
(length (ivar xs)))
|
|
36
|
+
(print (= MinStack.superclass Object))
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
(let [
|
|
2
|
+
two-sum-hash
|
|
3
|
+
(fn [nums target seen]
|
|
4
|
+
(var i 0)
|
|
5
|
+
(var answer nil)
|
|
6
|
+
(while (and (< i (length nums)) (= answer nil))
|
|
7
|
+
(let [n (. nums i)
|
|
8
|
+
complement (- target n)]
|
|
9
|
+
(if (seen.key? complement)
|
|
10
|
+
(set answer [(. seen complement) i])
|
|
11
|
+
(tset seen n i)))
|
|
12
|
+
(set i (+ i 1)))
|
|
13
|
+
answer)
|
|
14
|
+
]
|
|
15
|
+
(print (two-sum-hash [2 7 11 15] 9 {}))
|
|
16
|
+
(print (two-sum-hash [3 2 4] 6 {}))
|
|
17
|
+
(print (two-sum-hash [1 2 3] 10 {})))
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'kapusta'
|
|
5
|
+
|
|
6
|
+
Kapusta.require('./bank-account', relative_to: __FILE__)
|
|
7
|
+
|
|
8
|
+
account = BankAccount.new('Ada', 100)
|
|
9
|
+
account.deposit(50)
|
|
10
|
+
account.withdraw(30)
|
|
11
|
+
|
|
12
|
+
puts "Owner: #{account.owner}"
|
|
13
|
+
puts "Balance: #{account.balance}"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
(class ValidParenthesesSolution)
|
|
2
|
+
|
|
3
|
+
(fn initialize []
|
|
4
|
+
(set (ivar pairs)
|
|
5
|
+
{")" "(" "]" "[" "}" "{"}))
|
|
6
|
+
|
|
7
|
+
(fn valid? [s]
|
|
8
|
+
(let [pairs (ivar pairs)
|
|
9
|
+
stack []
|
|
10
|
+
chars (s.chars)]
|
|
11
|
+
(var i 0)
|
|
12
|
+
(var ok true)
|
|
13
|
+
(while (and ok (< i (length chars)))
|
|
14
|
+
(let [ch (. chars i)]
|
|
15
|
+
(if (pairs.key? ch)
|
|
16
|
+
(if (-?> (stack.pop) (= (. pairs ch))) nil (set ok false))
|
|
17
|
+
(stack.push ch)))
|
|
18
|
+
(set i (+ i 1)))
|
|
19
|
+
(and ok (stack.empty?))))
|
data/lib/kapusta/ast.rb
CHANGED
|
@@ -25,12 +25,16 @@ module Kapusta
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def ==(other)
|
|
28
|
-
other.
|
|
28
|
+
other.instance_of?(self.class) && other.name == @name
|
|
29
29
|
end
|
|
30
30
|
alias eql? ==
|
|
31
31
|
|
|
32
32
|
def hash
|
|
33
|
-
@name.hash
|
|
33
|
+
[self.class, @name].hash
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def binding_key
|
|
37
|
+
@name
|
|
34
38
|
end
|
|
35
39
|
|
|
36
40
|
def dotted?
|
|
@@ -42,6 +46,32 @@ module Kapusta
|
|
|
42
46
|
end
|
|
43
47
|
end
|
|
44
48
|
|
|
49
|
+
class GeneratedSym < Sym
|
|
50
|
+
attr_reader :id
|
|
51
|
+
|
|
52
|
+
def initialize(name, id)
|
|
53
|
+
super(name)
|
|
54
|
+
@id = id
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def inspect
|
|
58
|
+
"#<GeneratedSym #{@name} #{@id}>"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def ==(other)
|
|
62
|
+
other.is_a?(GeneratedSym) && other.id == @id
|
|
63
|
+
end
|
|
64
|
+
alias eql? ==
|
|
65
|
+
|
|
66
|
+
def hash
|
|
67
|
+
[self.class, @id].hash
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def binding_key
|
|
71
|
+
[self.class, @id]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
45
75
|
class Vec
|
|
46
76
|
attr_reader :items
|
|
47
77
|
|
|
@@ -62,7 +92,7 @@ module Kapusta
|
|
|
62
92
|
end
|
|
63
93
|
|
|
64
94
|
def all_sym_keys?
|
|
65
|
-
pairs.all? { |key, _| key.is_a?(Symbol) }
|
|
95
|
+
pairs.any? && pairs.all? { |key, _| key.is_a?(Symbol) }
|
|
66
96
|
end
|
|
67
97
|
end
|
|
68
98
|
|
|
@@ -42,9 +42,7 @@ module Kapusta
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def emit_simple_lambda(pattern, body, env, current_scope)
|
|
45
|
-
|
|
46
|
-
params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
|
|
47
|
-
body_code, = emit_sequence(body, body_env, current_scope, allow_method_definitions: false)
|
|
45
|
+
params, body_code = build_simple_block_parts(pattern, body, env, current_scope)
|
|
48
46
|
header = params.empty? ? 'proc do' : "proc do |#{params.join(', ')}|"
|
|
49
47
|
[
|
|
50
48
|
header,
|
|
@@ -53,10 +51,42 @@ module Kapusta
|
|
|
53
51
|
].join("\n")
|
|
54
52
|
end
|
|
55
53
|
|
|
54
|
+
def build_simple_block_parts(pattern, body, env, current_scope)
|
|
55
|
+
body_env = env.child
|
|
56
|
+
params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
|
|
57
|
+
body_code, = emit_sequence(body, body_env, current_scope, allow_method_definitions: false)
|
|
58
|
+
[params, body_code]
|
|
59
|
+
end
|
|
60
|
+
|
|
56
61
|
def simple_parameter_pattern?(pattern)
|
|
57
62
|
pattern.is_a?(Vec) && pattern.items.all? { |item| item.is_a?(Sym) && !item.dotted? && item.name != '&' }
|
|
58
63
|
end
|
|
59
64
|
|
|
65
|
+
def emit_definition_form(form, env, current_scope)
|
|
66
|
+
return [emit_method_definition(form, env), env] unless current_scope == :toplevel
|
|
67
|
+
|
|
68
|
+
emit_toplevel_method_definition(form, env)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def emit_toplevel_method_definition(form, env)
|
|
72
|
+
name_sym = form.items[1]
|
|
73
|
+
pattern = form.items[2]
|
|
74
|
+
body = form.items[3..]
|
|
75
|
+
return [nil, env] if name_sym.dotted?
|
|
76
|
+
return [nil, env] unless simple_parameter_pattern?(pattern)
|
|
77
|
+
|
|
78
|
+
ruby_name = direct_method_definition_name(name_sym)
|
|
79
|
+
return [nil, env] unless ruby_name
|
|
80
|
+
return [nil, env] if captures_outer_binding?(body, env, pattern_names(pattern))
|
|
81
|
+
|
|
82
|
+
env.define(name_sym.name, Env::MethodBinding.new(ruby_name))
|
|
83
|
+
definition = emit_direct_method_definition(name_sym, pattern, body, env)
|
|
84
|
+
if needs_toplevel_method_bridge?(ruby_name)
|
|
85
|
+
definition = join_code(definition, emit_toplevel_method_bridge(ruby_name))
|
|
86
|
+
end
|
|
87
|
+
[definition, env]
|
|
88
|
+
end
|
|
89
|
+
|
|
60
90
|
def emit_named_fn_assignment(form, env, current_scope)
|
|
61
91
|
name_sym = form.items[1]
|
|
62
92
|
ruby_name = define_local(env, name_sym.name)
|
|
@@ -70,6 +100,9 @@ module Kapusta
|
|
|
70
100
|
name_sym = form.items[1]
|
|
71
101
|
pattern = form.items[2]
|
|
72
102
|
body = form.items[3..]
|
|
103
|
+
direct_definition = emit_direct_method_definition(name_sym, pattern, body, env)
|
|
104
|
+
return direct_definition if direct_definition
|
|
105
|
+
|
|
73
106
|
block_header, body_code = emit_method_body(pattern, body, env)
|
|
74
107
|
|
|
75
108
|
if name_sym.name.start_with?('self.')
|
|
@@ -89,6 +122,48 @@ module Kapusta
|
|
|
89
122
|
end
|
|
90
123
|
end
|
|
91
124
|
|
|
125
|
+
def emit_direct_method_definition(name_sym, pattern, body, env)
|
|
126
|
+
return unless simple_parameter_pattern?(pattern)
|
|
127
|
+
|
|
128
|
+
ruby_name = direct_method_definition_name(name_sym)
|
|
129
|
+
return unless ruby_name
|
|
130
|
+
return if captures_outer_binding?(body, env, pattern_names(pattern))
|
|
131
|
+
|
|
132
|
+
body_env = env.child
|
|
133
|
+
params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
|
|
134
|
+
body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
|
|
135
|
+
header = params.empty? ? "def #{ruby_name}" : "def #{ruby_name}(#{params.join(', ')})"
|
|
136
|
+
[
|
|
137
|
+
header,
|
|
138
|
+
indent(body_code),
|
|
139
|
+
'end'
|
|
140
|
+
].join("\n")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def direct_method_definition_name(name_sym)
|
|
144
|
+
source_name = name_sym.name
|
|
145
|
+
if source_name.start_with?('self.')
|
|
146
|
+
method_name = Kapusta.kebab_to_snake(source_name.delete_prefix('self.'))
|
|
147
|
+
return unless direct_method_name?(method_name)
|
|
148
|
+
|
|
149
|
+
"self.#{method_name}"
|
|
150
|
+
else
|
|
151
|
+
method_name = Kapusta.kebab_to_snake(source_name)
|
|
152
|
+
return unless direct_method_name?(method_name)
|
|
153
|
+
|
|
154
|
+
method_name
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def needs_toplevel_method_bridge?(ruby_name)
|
|
159
|
+
%w[context describe example it specify].include?(ruby_name)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def emit_toplevel_method_bridge(ruby_name)
|
|
163
|
+
method_name = ruby_name.to_sym.inspect
|
|
164
|
+
"define_singleton_method(#{method_name}, Object.instance_method(#{method_name}).bind(self))"
|
|
165
|
+
end
|
|
166
|
+
|
|
92
167
|
def emit_method_body(pattern, body, env)
|
|
93
168
|
return emit_simple_method_body(pattern, body, env) if simple_parameter_pattern?(pattern)
|
|
94
169
|
|
|
@@ -102,12 +177,40 @@ module Kapusta
|
|
|
102
177
|
end
|
|
103
178
|
|
|
104
179
|
def emit_simple_method_body(pattern, body, env)
|
|
105
|
-
|
|
106
|
-
params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
|
|
107
|
-
body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
|
|
180
|
+
params, body_code = build_simple_block_parts(pattern, body, env, :toplevel)
|
|
108
181
|
[params.empty? ? 'do' : "do |#{params.join(', ')}|", body_code]
|
|
109
182
|
end
|
|
110
183
|
|
|
184
|
+
def captures_outer_binding?(forms, env, local_names)
|
|
185
|
+
forms.any? { |form| form_captures_outer_binding?(form, env, local_names) }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def form_captures_outer_binding?(form, env, local_names)
|
|
189
|
+
case form
|
|
190
|
+
when Sym
|
|
191
|
+
sym_captures_outer_binding?(form, env, local_names)
|
|
192
|
+
when Vec, List
|
|
193
|
+
form.items.any? { |item| form_captures_outer_binding?(item, env, local_names) }
|
|
194
|
+
when HashLit
|
|
195
|
+
form.pairs.any? do |key, value|
|
|
196
|
+
form_captures_outer_binding?(key, env, local_names) ||
|
|
197
|
+
form_captures_outer_binding?(value, env, local_names)
|
|
198
|
+
end
|
|
199
|
+
else
|
|
200
|
+
false
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def sym_captures_outer_binding?(sym, env, local_names)
|
|
205
|
+
name = sym.dotted? ? sym.segments.first : sym.name
|
|
206
|
+
return false if local_names.include?(name)
|
|
207
|
+
|
|
208
|
+
binding = env.lookup_if_defined(name)
|
|
209
|
+
return false if binding.nil?
|
|
210
|
+
|
|
211
|
+
!method_binding?(binding)
|
|
212
|
+
end
|
|
213
|
+
|
|
111
214
|
def emit_let(args, env, current_scope)
|
|
112
215
|
binding_code, body_code = emit_let_parts(args, env, current_scope, result: true)
|
|
113
216
|
<<~RUBY.chomp
|
|
@@ -169,9 +272,12 @@ module Kapusta
|
|
|
169
272
|
value_code = emit_expr(form.items[2], env, current_scope)
|
|
170
273
|
|
|
171
274
|
if target.is_a?(Sym) && !target.dotted?
|
|
275
|
+
binding = env.lookup_if_defined(target.name)
|
|
172
276
|
ruby_name =
|
|
173
|
-
if
|
|
174
|
-
|
|
277
|
+
if binding
|
|
278
|
+
emit_error!("cannot set method binding: #{target.name}") if method_binding?(binding)
|
|
279
|
+
|
|
280
|
+
binding
|
|
175
281
|
else
|
|
176
282
|
define_local(env, target.name)
|
|
177
283
|
end
|
|
@@ -192,32 +298,39 @@ module Kapusta
|
|
|
192
298
|
when Sym
|
|
193
299
|
if target.dotted?
|
|
194
300
|
base_code, segments = multisym_base(target.segments, env)
|
|
195
|
-
|
|
301
|
+
receiver = emit_method_path(base_code, segments[0...-1])
|
|
302
|
+
last = segments.last
|
|
303
|
+
snake = Kapusta.kebab_to_snake(last)
|
|
304
|
+
if direct_method_name?(last)
|
|
305
|
+
"#{receiver}.#{snake} = #{value_code}"
|
|
306
|
+
else
|
|
307
|
+
"#{receiver}.public_send(:\"#{snake}=\", #{value_code})"
|
|
308
|
+
end
|
|
196
309
|
else
|
|
197
|
-
|
|
310
|
+
binding = env.lookup(target.name)
|
|
311
|
+
emit_error!("cannot set method binding: #{target.name}") if method_binding?(binding)
|
|
312
|
+
|
|
313
|
+
"#{binding} = #{value_code}"
|
|
198
314
|
end
|
|
199
315
|
when List
|
|
200
316
|
head = target.head
|
|
201
317
|
if head.is_a?(Sym) && head.name == '.'
|
|
202
318
|
object_code = emit_expr(target.items[1], env, current_scope)
|
|
203
|
-
|
|
204
|
-
|
|
319
|
+
keys = target.items[2..].map { |item| emit_expr(item, env, current_scope) }
|
|
320
|
+
receiver = simple_expression?(object_code) ? object_code : parenthesize(object_code)
|
|
321
|
+
prefix = keys[0...-1].map { |k| "[#{k}]" }.join
|
|
322
|
+
"#{receiver}#{prefix}[#{keys.last}] = #{value_code}"
|
|
205
323
|
elsif head.is_a?(Sym) && head.name == 'ivar'
|
|
206
|
-
|
|
324
|
+
"@#{Kapusta.kebab_to_snake(target.items[1].name)} = #{value_code}"
|
|
207
325
|
elsif head.is_a?(Sym) && head.name == 'cvar'
|
|
208
|
-
|
|
326
|
+
"@@#{Kapusta.kebab_to_snake(target.items[1].name)} = #{value_code}"
|
|
209
327
|
elsif head.is_a?(Sym) && head.name == 'gvar'
|
|
210
|
-
|
|
211
|
-
if direct_global_name?(ruby_name)
|
|
212
|
-
"$#{ruby_name} = #{value_code}"
|
|
213
|
-
else
|
|
214
|
-
runtime_call(:set_gvar, target.items[1].name.inspect, value_code)
|
|
215
|
-
end
|
|
328
|
+
"$#{global_name(target.items[1].name)} = #{value_code}"
|
|
216
329
|
else
|
|
217
|
-
|
|
330
|
+
emit_error!("bad set target: #{target.inspect}")
|
|
218
331
|
end
|
|
219
332
|
else
|
|
220
|
-
|
|
333
|
+
emit_error!("bad set target: #{target.inspect}")
|
|
221
334
|
end
|
|
222
335
|
end
|
|
223
336
|
end
|
|
@@ -82,12 +82,12 @@ module Kapusta
|
|
|
82
82
|
arm_env = env.child
|
|
83
83
|
assign_code, arm_env = emit_bindings_from_match(plan[:bindings], bindings_var, arm_env)
|
|
84
84
|
body_code = emit_expr(body, arm_env, current_scope)
|
|
85
|
+
arm_body = [assign_code, body_code].reject(&:empty?).join("\n")
|
|
85
86
|
<<~RUBY.chomp
|
|
86
87
|
#{match_var} = #{runtime_call(:match_pattern, plan[:pattern], value_var)}
|
|
87
88
|
if #{match_var}[0]
|
|
88
89
|
#{bindings_var} = #{match_var}[1]
|
|
89
|
-
#{
|
|
90
|
-
#{body_code}
|
|
90
|
+
#{arm_body}
|
|
91
91
|
else
|
|
92
92
|
#{indent(else_code)}
|
|
93
93
|
end
|
|
@@ -104,11 +104,11 @@ module Kapusta
|
|
|
104
104
|
assign_code, arm_env = emit_bindings_from_match(plan[:bindings], bindings_var, arm_env)
|
|
105
105
|
guard_code = emit_case_guards(guards, arm_env, current_scope)
|
|
106
106
|
body_code = emit_expr(body, arm_env, current_scope)
|
|
107
|
+
bindings_line = assign_code.empty? ? '' : "\n #{assign_code}"
|
|
107
108
|
<<~RUBY.chomp
|
|
108
109
|
#{match_var} = #{runtime_call(:match_pattern, plan[:pattern], value_var)}
|
|
109
110
|
if #{match_var}[0]
|
|
110
|
-
#{bindings_var} = #{match_var}[1]
|
|
111
|
-
#{assign_code}
|
|
111
|
+
#{bindings_var} = #{match_var}[1]#{bindings_line}
|
|
112
112
|
if #{guard_code}
|
|
113
113
|
#{body_code}
|
|
114
114
|
else
|