kapusta 0.1.5 → 0.2.1

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +43 -4
  3. data/examples/bank-account.kap +21 -0
  4. data/examples/baseball-game.kap +11 -0
  5. data/examples/best-time-to-buy-sell-stock.kap +12 -0
  6. data/examples/climbing-stairs.kap +13 -0
  7. data/examples/doto-hygiene.kap +5 -0
  8. data/examples/happy-number.kap +20 -0
  9. data/examples/length-of-last-word.kap +7 -0
  10. data/examples/majority-element.kap +11 -0
  11. data/examples/maximum-subarray.kap +12 -0
  12. data/examples/move-zeroes.kap +13 -0
  13. data/examples/plus-one.kap +14 -0
  14. data/examples/reverse-integer.kap +13 -0
  15. data/examples/roman-to-integer.kap +17 -0
  16. data/examples/stack.kap +27 -10
  17. data/examples/two-sum-hash.kap +17 -0
  18. data/examples/use_bank_account.rb +13 -0
  19. data/examples/valid-parentheses-1.kap +19 -0
  20. data/examples/valid-parentheses-2.kap +8 -0
  21. data/examples/zoo-animal-1.kap +5 -0
  22. data/examples/zoo-animal-inheritance-2.kap +8 -0
  23. data/lib/kapusta/ast.rb +33 -3
  24. data/lib/kapusta/compiler/emitter/bindings.rb +35 -25
  25. data/lib/kapusta/compiler/emitter/control_flow.rb +15 -7
  26. data/lib/kapusta/compiler/emitter/expressions.rb +13 -13
  27. data/lib/kapusta/compiler/emitter/interop.rb +91 -40
  28. data/lib/kapusta/compiler/emitter/patterns.rb +14 -11
  29. data/lib/kapusta/compiler/emitter/support.rb +13 -11
  30. data/lib/kapusta/compiler/emitter.rb +1 -5
  31. data/lib/kapusta/compiler/normalizer.rb +41 -17
  32. data/lib/kapusta/compiler/runtime.rb +0 -152
  33. data/lib/kapusta/env.rb +21 -6
  34. data/lib/kapusta/reader.rb +27 -3
  35. data/lib/kapusta/version.rb +1 -1
  36. data/lib/kapusta.rb +62 -1
  37. data/spec/cli_spec.rb +25 -2
  38. data/spec/examples_spec.rb +257 -81
  39. data/spec/reader_spec.rb +26 -0
  40. metadata +23 -8
  41. data/examples/inheritance.kap +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52369f7a4cd658385e7ddc69f552477d94b55340cb9f796ac635550a7def6e6a
4
- data.tar.gz: 6d673c9c387b52b0e4cd9e55b8ae2cf1d0a7e2c5f741dc7705f405b0ddaf25c6
3
+ metadata.gz: ee9b062de9860ef3b9f3cfcbac8503e69c1549f0f09af35b41e5eb13303c1ddb
4
+ data.tar.gz: daa87e0b3b65dfc82f21ff47e88b41652d05f087e46320ca1c3f49bac2457b71
5
5
  SHA512:
6
- metadata.gz: 53510507ba41a4d67ec73184eaef665fee182220407046eb9db64cf05cae7d28a315b5b8cf4ace6071b359a83801bd85d7e45ce5e4aefd3c608fef614f116fee
7
- data.tar.gz: ff1b88fc11dc10ccffc1459686b17754bd39dc6a941756d7cab304253aec3b63bc36967c9edcf50cf4c29eadee3ae2afb0467383b6428dcb74f41c452173ab4a
6
+ metadata.gz: 564c441ea9395cd4f28daaa631de6022e39125eede47032bf32bbc2a45f18979b7376f264b5302459b78403ef039ef8aca93e6f469cc0666b033f6e6717894a2
7
+ data.tar.gz: 38479cb8d38b81df15697a56c1a14e63d4dda7a0dd793db4e121e06126395339d9c7676a4bb14411a6b43398b64196f877ce3126990eb07d24f2a0c769a577e2
data/README.md CHANGED
@@ -29,6 +29,48 @@ 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
+
44
+ ## Examples
45
+
46
+ See [`examples/`](https://github.com/evmorov/kapusta/tree/main/examples).
47
+
48
+ ```fennel
49
+ (fn ack [m n]
50
+ (if (= m 0) (+ n 1)
51
+ (= n 0) (ack (- m 1) 1)
52
+ (ack (- m 1) (ack m (- n 1)))))
53
+
54
+ (print (ack 2 3))
55
+ (print (ack 3 3))
56
+ ```
57
+
58
+ Compiles to:
59
+
60
+ ```ruby
61
+ def ack(m, n)
62
+ if m == 0
63
+ n + 1
64
+ elsif n == 0
65
+ ack(m - 1, 1)
66
+ else
67
+ ack(m - 1, ack(m, n - 1))
68
+ end
69
+ end
70
+ p(ack(2, 3))
71
+ p(ack(3, 3))
72
+ ```
73
+
32
74
  ## Comparison with Fennel
33
75
 
34
76
  Kapusta keeps most core Fennel forms. The main differences come from Ruby's runtime and object model.
@@ -40,6 +82,7 @@ Kapusta keeps most core Fennel forms. The main differences come from Ruby's runt
40
82
  | `(. xs 1)` is the first element | `(. xs 0)` is the first element |
41
83
  | `string.format`, `table.insert`, etc. | use Ruby methods and stdlib instead |
42
84
  | `values` uses Lua multiple returns | `values` lowers to a Ruby array, usually destructured |
85
+ | `(print x)` is Lua's `print` (bare) | `(print x)` is Ruby's `p` (inspect-style) |
43
86
  | `with-open`, `tail!` | not provided |
44
87
  | macros | not provided for now |
45
88
 
@@ -52,10 +95,6 @@ Kapusta-specific additions:
52
95
  - a trailing symbol-keyed hash is emitted as Ruby keyword arguments
53
96
  - a final function literal argument is emitted as a Ruby block
54
97
 
55
- ## Examples
56
-
57
- See `examples/`.
58
-
59
98
  ## Formatting
60
99
 
61
100
  ```
@@ -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,12 @@
1
+ (fn max-profit [prices]
2
+ (var min-price (. prices 0))
3
+ (var best 0)
4
+ (for [i 1 (- (length prices) 1)]
5
+ (let [p (. prices i)]
6
+ (when (< p min-price) (set min-price p))
7
+ (when (> (- p min-price) best) (set best (- p min-price)))))
8
+ best)
9
+
10
+ (print (max-profit [7 1 5 3 6 4]))
11
+ (print (max-profit [7 6 4 3 1]))
12
+ (print (max-profit [2 4 1]))
@@ -0,0 +1,13 @@
1
+ (fn climb-stairs [n]
2
+ (var prev 1)
3
+ (var curr 1)
4
+ (for [_ 2 n]
5
+ (let [next (+ prev curr)]
6
+ (set prev curr)
7
+ (set curr next)))
8
+ curr)
9
+
10
+ (print (climb-stairs 2))
11
+ (print (climb-stairs 3))
12
+ (print (climb-stairs 5))
13
+ (print (climb-stairs 10))
@@ -0,0 +1,5 @@
1
+ (let [__doto__ 99
2
+ xs []]
3
+ (doto xs
4
+ (: :push __doto__))
5
+ (print (xs.inspect)))
@@ -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,7 @@
1
+ (fn length-of-last-word [s]
2
+ (let [words (s.strip.split)]
3
+ (: (. words -1) :length)))
4
+
5
+ (print (length-of-last-word "Hello World"))
6
+ (print (length-of-last-word " fly me to the moon "))
7
+ (print (length-of-last-word "luffy is still joyboy"))
@@ -0,0 +1,11 @@
1
+ (fn majority [nums]
2
+ (var candidate nil)
3
+ (var count 0)
4
+ (each [n nums]
5
+ (when (= count 0) (set candidate n))
6
+ (if (= n candidate) (set count (+ count 1)) (set count (- count 1))))
7
+ candidate)
8
+
9
+ (print (majority [3 2 3]))
10
+ (print (majority [2 2 1 1 1 2 2]))
11
+ (print (majority [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]))
@@ -0,0 +1,14 @@
1
+ (fn plus-one [digits]
2
+ (var i (- (length digits) 1))
3
+ (var carry 1)
4
+ (while (and (>= i 0) (> carry 0))
5
+ (let [total (+ (. digits i) carry)]
6
+ (tset digits i (% total 10))
7
+ (set carry (: (/ total 10) :floor)))
8
+ (set i (- i 1)))
9
+ (if (> carry 0) (: digits :unshift carry) digits))
10
+
11
+ (print (plus-one [1 2 3]))
12
+ (print (plus-one [4 3 2 1]))
13
+ (print (plus-one [9]))
14
+ (print (plus-one [9 9]))
@@ -0,0 +1,13 @@
1
+ (fn reverse-integer [x]
2
+ (let [sign (if (< x 0) -1 1)]
3
+ (var remaining (* x sign))
4
+ (var result 0)
5
+ (while (> remaining 0)
6
+ (set result (+ (* result 10) (% remaining 10)))
7
+ (set remaining (: (/ remaining 10) :floor)))
8
+ (* result sign)))
9
+
10
+ (print (reverse-integer 123))
11
+ (print (reverse-integer -123))
12
+ (print (reverse-integer 120))
13
+ (print (reverse-integer 0))
@@ -0,0 +1,17 @@
1
+ (fn roman-to-integer [s]
2
+ (let [values {"I" 1 "V" 5 "X" 10 "L" 50 "C" 100 "D" 500 "M" 1000}
3
+ chars (s.chars)
4
+ n (length chars)]
5
+ (var total 0)
6
+ (var i 0)
7
+ (while (< i n)
8
+ (let [curr (. values (. chars i))
9
+ ahead (if (< (+ i 1) n) (. values (. chars (+ i 1))) 0)
10
+ subtract? (< curr ahead)]
11
+ (set total (+ total (if subtract? (- ahead curr) curr)))
12
+ (set i (+ i (if subtract? 2 1)))))
13
+ total))
14
+
15
+ (print (roman-to-integer "III"))
16
+ (print (roman-to-integer "LVIII"))
17
+ (print (roman-to-integer "MCMXCIV"))
data/examples/stack.kap CHANGED
@@ -1,19 +1,36 @@
1
- (class Stack)
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! [x]
7
- (let [xs (ivar xs)]
8
- (xs.push x))
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 empty? []
16
- (= 0 (length (ivar xs))))
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
- (fn size []
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?))))
@@ -0,0 +1,8 @@
1
+ (require "./valid-parentheses-1")
2
+
3
+ (let [solution (ValidParenthesesSolution.new)]
4
+ (print (solution.valid? "()"))
5
+ (print (solution.valid? "()[]{}"))
6
+ (print (solution.valid? "([])"))
7
+ (print (solution.valid? "(]"))
8
+ (print (solution.valid? "([)]")))
@@ -0,0 +1,5 @@
1
+ (class Zoo.Animal
2
+ (fn initialize [name] (set (ivar name) name))
3
+ (fn name [] (ivar name))
4
+ (fn kingdom [] "animalia")
5
+ (fn label [] (.. (self.name) " the animal")))
@@ -0,0 +1,8 @@
1
+ (require "./zoo-animal-1")
2
+
3
+ (class Zoo.Dog [Zoo.Animal]
4
+ (fn label [] (.. (self.name) " the dog"))
5
+ (fn bark [] "woof"))
6
+
7
+ (let [dog (Zoo.Dog.new "Poppy")]
8
+ (print (= Zoo.Dog.superclass Zoo.Animal) (dog.kingdom) (dog.label) (dog.bark)))
data/lib/kapusta/ast.rb CHANGED
@@ -25,12 +25,16 @@ module Kapusta
25
25
  end
26
26
 
27
27
  def ==(other)
28
- other.is_a?(Sym) && other.name == @name
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
- body_env = env.child
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,6 +51,13 @@ 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
@@ -172,9 +177,7 @@ module Kapusta
172
177
  end
173
178
 
174
179
  def emit_simple_method_body(pattern, body, env)
175
- body_env = env.child
176
- params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
177
- body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
180
+ params, body_code = build_simple_block_parts(pattern, body, env, :toplevel)
178
181
  [params.empty? ? 'do' : "do |#{params.join(', ')}|", body_code]
179
182
  end
180
183
 
@@ -200,9 +203,12 @@ module Kapusta
200
203
 
201
204
  def sym_captures_outer_binding?(sym, env, local_names)
202
205
  name = sym.dotted? ? sym.segments.first : sym.name
203
- return false if local_names.include?(name) || !env.defined?(name)
206
+ return false if local_names.include?(name)
207
+
208
+ binding = env.lookup_if_defined(name)
209
+ return false if binding.nil?
204
210
 
205
- !method_binding?(env.lookup(name))
211
+ !method_binding?(binding)
206
212
  end
207
213
 
208
214
  def emit_let(args, env, current_scope)
@@ -266,10 +272,10 @@ module Kapusta
266
272
  value_code = emit_expr(form.items[2], env, current_scope)
267
273
 
268
274
  if target.is_a?(Sym) && !target.dotted?
275
+ binding = env.lookup_if_defined(target.name)
269
276
  ruby_name =
270
- if env.defined?(target.name)
271
- binding = env.lookup(target.name)
272
- raise Error, "cannot set method binding: #{target.name}" if method_binding?(binding)
277
+ if binding
278
+ emit_error!("cannot set method binding: #{target.name}") if method_binding?(binding)
273
279
 
274
280
  binding
275
281
  else
@@ -292,10 +298,17 @@ module Kapusta
292
298
  when Sym
293
299
  if target.dotted?
294
300
  base_code, segments = multisym_base(target.segments, env)
295
- runtime_call(:set_method_path, base_code, segments.inspect, value_code)
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
296
309
  else
297
310
  binding = env.lookup(target.name)
298
- raise Error, "cannot set method binding: #{target.name}" if method_binding?(binding)
311
+ emit_error!("cannot set method binding: #{target.name}") if method_binding?(binding)
299
312
 
300
313
  "#{binding} = #{value_code}"
301
314
  end
@@ -303,24 +316,21 @@ module Kapusta
303
316
  head = target.head
304
317
  if head.is_a?(Sym) && head.name == '.'
305
318
  object_code = emit_expr(target.items[1], env, current_scope)
306
- keys_code = "[#{target.items[2..].map { |item| emit_expr(item, env, current_scope) }.join(', ')}]"
307
- runtime_call(:set_path, object_code, keys_code, value_code)
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}"
308
323
  elsif head.is_a?(Sym) && head.name == 'ivar'
309
- runtime_call(:set_ivar, 'self', target.items[1].name.inspect, value_code)
324
+ "@#{Kapusta.kebab_to_snake(target.items[1].name)} = #{value_code}"
310
325
  elsif head.is_a?(Sym) && head.name == 'cvar'
311
- runtime_call(:set_cvar, 'self', target.items[1].name.inspect, value_code)
326
+ "@@#{Kapusta.kebab_to_snake(target.items[1].name)} = #{value_code}"
312
327
  elsif head.is_a?(Sym) && head.name == 'gvar'
313
- ruby_name = global_name(target.items[1].name)
314
- if direct_global_name?(ruby_name)
315
- "$#{ruby_name} = #{value_code}"
316
- else
317
- runtime_call(:set_gvar, target.items[1].name.inspect, value_code)
318
- end
328
+ "$#{global_name(target.items[1].name)} = #{value_code}"
319
329
  else
320
- raise Error, "bad set target: #{target.inspect}"
330
+ emit_error!("bad set target: #{target.inspect}")
321
331
  end
322
332
  else
323
- raise Error, "bad set target: #{target.inspect}"
333
+ emit_error!("bad set target: #{target.inspect}")
324
334
  end
325
335
  end
326
336
  end
@@ -15,7 +15,7 @@ module Kapusta
15
15
  return emit_expr(args[0], env, current_scope) if args.length == 1
16
16
 
17
17
  cond = emit_expr(args[0], env, current_scope)
18
- truthy = emit_expr(args[1], env, current_scope)
18
+ truthy = emit_if_branch(args[1], env, current_scope)
19
19
  lines = ["if #{cond}", indent(truthy)]
20
20
  append_else_lines(lines, args[2..], env, current_scope)
21
21
  lines << 'end'
@@ -31,7 +31,7 @@ module Kapusta
31
31
  append_elsif_lines(lines, args, env, current_scope)
32
32
  else
33
33
  lines << 'else'
34
- lines << indent(emit_expr(args[0], env, current_scope))
34
+ lines << indent(emit_if_branch(args[0], env, current_scope))
35
35
  end
36
36
  end
37
37
 
@@ -39,10 +39,18 @@ module Kapusta
39
39
  return append_else_lines(lines, args, env, current_scope) if args.length < 2
40
40
 
41
41
  lines << "elsif #{emit_expr(args[0], env, current_scope)}"
42
- lines << indent(emit_expr(args[1], env, current_scope))
42
+ lines << indent(emit_if_branch(args[1], env, current_scope))
43
43
  append_else_lines(lines, args[2..], env, current_scope)
44
44
  end
45
45
 
46
+ def emit_if_branch(form, env, current_scope)
47
+ return emit_expr(form, env, current_scope) unless do_form?(form)
48
+
49
+ emit_sequence(form.rest, env, current_scope,
50
+ allow_method_definitions: false,
51
+ result: true).first
52
+ end
53
+
46
54
  def if_form?(form)
47
55
  form.is_a?(List) && form.head.is_a?(Sym) && form.head.name == 'if'
48
56
  end
@@ -82,12 +90,12 @@ module Kapusta
82
90
  arm_env = env.child
83
91
  assign_code, arm_env = emit_bindings_from_match(plan[:bindings], bindings_var, arm_env)
84
92
  body_code = emit_expr(body, arm_env, current_scope)
93
+ arm_body = [assign_code, body_code].reject(&:empty?).join("\n")
85
94
  <<~RUBY.chomp
86
95
  #{match_var} = #{runtime_call(:match_pattern, plan[:pattern], value_var)}
87
96
  if #{match_var}[0]
88
97
  #{bindings_var} = #{match_var}[1]
89
- #{assign_code}
90
- #{body_code}
98
+ #{arm_body}
91
99
  else
92
100
  #{indent(else_code)}
93
101
  end
@@ -104,11 +112,11 @@ module Kapusta
104
112
  assign_code, arm_env = emit_bindings_from_match(plan[:bindings], bindings_var, arm_env)
105
113
  guard_code = emit_case_guards(guards, arm_env, current_scope)
106
114
  body_code = emit_expr(body, arm_env, current_scope)
115
+ bindings_line = assign_code.empty? ? '' : "\n #{assign_code}"
107
116
  <<~RUBY.chomp
108
117
  #{match_var} = #{runtime_call(:match_pattern, plan[:pattern], value_var)}
109
118
  if #{match_var}[0]
110
- #{bindings_var} = #{match_var}[1]
111
- #{assign_code}
119
+ #{bindings_var} = #{match_var}[1]#{bindings_line}
112
120
  if #{guard_code}
113
121
  #{body_code}
114
122
  else