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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c136e4c7cc9c5dde0ced8c828ab80882900b278db86010986eb7271a7b45309e
4
- data.tar.gz: 46ff912715738e99c454727224a5e19f6d1635fbfccb1ade3820a9a51a996d0d
3
+ metadata.gz: 53512e4cdebbb07a50f1bff53338574994140e5b16ddc5c1fd05e27b8cbc8a20
4
+ data.tar.gz: c7907d4901ecfb193c4df0c0b09bca2869babab65f6327d542c59a88a9edff51
5
5
  SHA512:
6
- metadata.gz: 9d36ad79125c72a9e804f10ce9e0e5983e852b428f1a1a3718e21ef9394c66f39e57bec366ed81257bd5e62439429cc1cd93039710404db2c8dc0f3d9c09c44a
7
- data.tar.gz: 0cb236654de0a57184387087c5a7f47dc49d548c1fed0167638439c071b83cda592cd0346f68ffc086c74afccff0b1f08c90ec977238213b9672fa8244052b27
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,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,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 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? "([)]")))
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,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
- body_env = env.child
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 env.defined?(target.name)
174
- env.lookup(target.name)
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
- 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
196
309
  else
197
- "#{env.lookup(target.name)} = #{value_code}"
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
- keys_code = "[#{target.items[2..].map { |item| emit_expr(item, env, current_scope) }.join(', ')}]"
204
- 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}"
205
323
  elsif head.is_a?(Sym) && head.name == 'ivar'
206
- runtime_call(:set_ivar, 'self', target.items[1].name.inspect, value_code)
324
+ "@#{Kapusta.kebab_to_snake(target.items[1].name)} = #{value_code}"
207
325
  elsif head.is_a?(Sym) && head.name == 'cvar'
208
- runtime_call(:set_cvar, 'self', target.items[1].name.inspect, value_code)
326
+ "@@#{Kapusta.kebab_to_snake(target.items[1].name)} = #{value_code}"
209
327
  elsif head.is_a?(Sym) && head.name == 'gvar'
210
- ruby_name = global_name(target.items[1].name)
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
- raise Error, "bad set target: #{target.inspect}"
330
+ emit_error!("bad set target: #{target.inspect}")
218
331
  end
219
332
  else
220
- raise Error, "bad set target: #{target.inspect}"
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
- #{assign_code}
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