kapusta 0.2.3 → 0.3.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: 1a5fcd922c54b5fa491785e6087c4fc2f8e6de1a8d94deab10fca3f775518b9b
4
- data.tar.gz: d95f4bce9e6673104c44fc0ff281a705e796797490285cc50ef509b6b9fe5e97
3
+ metadata.gz: 179b8b97d775c6bff99bd3d5ac3e1df1f6ab8c17637b6e16f9d688ade7f9dd06
4
+ data.tar.gz: 899899c13c9b1d4f85216df5d3989807d874a9ec862be94d48989f851b2d8111
5
5
  SHA512:
6
- metadata.gz: cb5ba4420166be83a152c22bf56a12563efdb8cc64e54f230f6d299051761c5abc2329ae42dfc5e6127a721f66250b9804d6ec86a9b053dc36282599b2f8c986
7
- data.tar.gz: 334d34e9e47bdecf6a4aa35cc6e905770efb6913fa6c2e9f90b0f3b1a95915b9044c8e3f36c81778a865f91a6d43f675318bf1ae892e19292cb4a2d2de179685
6
+ metadata.gz: 69247136466e119860e5fee232b0bb21fa40b9d23986f09051a4acccda476a9c42b5e3e63e1382367df6c3008876af2f6a69a9b6fd2bfc0e05e1c9e69ca6a8b0
7
+ data.tar.gz: f65709e841e372a1dcc4c9a453d2251d58fb4fffbecd51fd1f0bcfb7c0423dc51109d6533e1b85d9b39bbc82cc994dcf64bdb5aad586c2b6476858db8e320d25
data/README.md CHANGED
@@ -8,6 +8,12 @@ Instead, Kapusta aims to bring some of the simplicity and joy of Lisp to Ruby. W
8
8
 
9
9
  For more information about Kapusta, see the official Fennel documentation and tutorials.
10
10
 
11
+ ## Features
12
+
13
+ 1. Compiles to readable Ruby.
14
+ 2. Compiled `.rb` files don't depend on Kapusta. Run with plain `ruby`, or load `.kap` files at runtime via `require 'kapusta'`.
15
+ 3. Two-way Ruby interop.
16
+
11
17
  ## Usage
12
18
 
13
19
  ```
data/examples/counter.kap CHANGED
@@ -4,8 +4,7 @@
4
4
  (set (ivar n) start))
5
5
 
6
6
  (fn tick []
7
- (set (ivar n) (+ (ivar n) 1))
8
- (ivar n))
7
+ (set (ivar n) (+ (ivar n) 1)))
9
8
 
10
9
  (fn value []
11
10
  (ivar n))
@@ -1,6 +1,9 @@
1
1
  (fn length-of-last-word [s]
2
- (let [words (s.strip.split)]
3
- (: (. words -1) :length)))
2
+ (-> s
3
+ (: :strip)
4
+ (: :split)
5
+ (. -1)
6
+ (: :length)))
4
7
 
5
8
  (print (length-of-last-word "Hello World"))
6
9
  (print (length-of-last-word " fly me to the moon "))
@@ -0,0 +1,9 @@
1
+ (fn manhattan [edge]
2
+ (let [{:from [x1 y1] :to [x2 y2]} edge]
3
+ (+ (: (- x1 x2) :abs) (: (- y1 y2) :abs))))
4
+
5
+ (fn total-distance [edges]
6
+ (accumulate [total 0 _ edge (ipairs edges)]
7
+ (+ total (manhattan edge))))
8
+
9
+ (print (total-distance [{:from [0 0] :to [3 4]} {:from [1 1] :to [4 5]}]))
@@ -1,7 +1,6 @@
1
1
  (fn palindrome? [s]
2
- (let [lower (s.downcase)
3
- normalized (lower.gsub (ruby "/[^a-z]/") "")]
4
- (= normalized (normalized.reverse))))
2
+ (let [normalized (-> s (: :downcase) (: :gsub (ruby "/[^a-z]/") ""))]
3
+ (= normalized (: normalized :reverse))))
5
4
 
6
5
  (print (palindrome? "racecar"))
7
6
  (print (palindrome? "A man, a plan, a canal: Panama"))
data/examples/pangram.kap CHANGED
@@ -1,9 +1,10 @@
1
1
  (fn pangram? [s]
2
- (let [lower (s.downcase)
3
- letters (lower.gsub (ruby "/[^a-z]/") "")
4
- chars (letters.chars)
5
- uniq (chars.uniq)]
6
- (= (uniq.length) 26)))
2
+ (= 26
3
+ (length (-> s
4
+ (: :downcase)
5
+ (: :gsub (ruby "/[^a-z]/") "")
6
+ (: :chars)
7
+ (: :uniq)))))
7
8
 
8
9
  (print (pangram? "The quick brown fox jumps over the lazy dog"))
9
10
  (print (pangram? "Hello, world"))
@@ -0,0 +1,13 @@
1
+ (fn pivot-index [nums]
2
+ (var total 0)
3
+ (each [n nums] (set total (+ total n)))
4
+ (var left 0)
5
+ (var found -1)
6
+ (each [i n (ipairs nums)]
7
+ (when (and (= found -1) (= left (- total left n))) (set found i))
8
+ (set left (+ left n)))
9
+ found)
10
+
11
+ (print (pivot-index [1 7 3 6 5 6]))
12
+ (print (pivot-index [1 2 3]))
13
+ (print (pivot-index [2 1 -1]))
@@ -0,0 +1,14 @@
1
+ (fn subtract-product-sum [n]
2
+ (var x n)
3
+ (var product 1)
4
+ (var sum 0)
5
+ (while (> x 0)
6
+ (let [d (% x 10)]
7
+ (set product (* product d))
8
+ (set sum (+ sum d))
9
+ (set x (: (/ x 10) :floor))))
10
+ (- product sum))
11
+
12
+ (print (subtract-product-sum 234))
13
+ (print (subtract-product-sum 4421))
14
+ (print (subtract-product-sum 1))
@@ -281,12 +281,23 @@ module Kapusta
281
281
  else
282
282
  define_local(env, target.name)
283
283
  end
284
- ["#{ruby_name} = #{value_code}", env]
284
+ [emit_assignment(ruby_name, value_code), env]
285
285
  else
286
286
  [emit_set_target(target, value_code, env, current_scope), env]
287
287
  end
288
288
  end
289
289
 
290
+ def emit_assignment(lhs, value_code)
291
+ prefix = "#{lhs} "
292
+ if value_code.start_with?(prefix) &&
293
+ (m = value_code[prefix.length..].match(/\A(\S+) (.*)\z/m)) &&
294
+ !m[1].include?('=')
295
+ "#{lhs} #{m[1]}= #{m[2]}"
296
+ else
297
+ "#{lhs} = #{value_code}"
298
+ end
299
+ end
300
+
290
301
  def emit_set_expr(args, env, current_scope)
291
302
  target = args[0]
292
303
  value_code = emit_expr(args[1], env, current_scope)
@@ -302,7 +313,7 @@ module Kapusta
302
313
  last = segments.last
303
314
  snake = Kapusta.kebab_to_snake(last)
304
315
  if direct_method_name?(last)
305
- "#{receiver}.#{snake} = #{value_code}"
316
+ emit_assignment("#{receiver}.#{snake}", value_code)
306
317
  else
307
318
  "#{receiver}.public_send(:\"#{snake}=\", #{value_code})"
308
319
  end
@@ -310,7 +321,7 @@ module Kapusta
310
321
  binding = env.lookup(target.name)
311
322
  emit_error!("cannot set method binding: #{target.name}") if method_binding?(binding)
312
323
 
313
- "#{binding} = #{value_code}"
324
+ emit_assignment(binding, value_code)
314
325
  end
315
326
  when List
316
327
  head = target.head
@@ -319,13 +330,13 @@ module Kapusta
319
330
  keys = target.items[2..].map { |item| emit_expr(item, env, current_scope) }
320
331
  receiver = simple_expression?(object_code) ? object_code : parenthesize(object_code)
321
332
  prefix = keys[0...-1].map { |k| "[#{k}]" }.join
322
- "#{receiver}#{prefix}[#{keys.last}] = #{value_code}"
333
+ emit_assignment("#{receiver}#{prefix}[#{keys.last}]", value_code)
323
334
  elsif head.is_a?(Sym) && head.name == 'ivar'
324
- "@#{Kapusta.kebab_to_snake(target.items[1].name)} = #{value_code}"
335
+ emit_assignment("@#{Kapusta.kebab_to_snake(target.items[1].name)}", value_code)
325
336
  elsif head.is_a?(Sym) && head.name == 'cvar'
326
- "@@#{Kapusta.kebab_to_snake(target.items[1].name)} = #{value_code}"
337
+ emit_assignment("@@#{Kapusta.kebab_to_snake(target.items[1].name)}", value_code)
327
338
  elsif head.is_a?(Sym) && head.name == 'gvar'
328
- "$#{global_name(target.items[1].name)} = #{value_code}"
339
+ emit_assignment("$#{global_name(target.items[1].name)}", value_code)
329
340
  else
330
341
  emit_error!("bad set target: #{target.inspect}")
331
342
  end
@@ -45,13 +45,13 @@ module Kapusta
45
45
  emit_sequence_value_assignment(acc_var, body)
46
46
  end
47
47
  end
48
- <<~RUBY.chomp
49
- (-> do
50
- #{acc_var} = #{emit_expr(bindings[1], env, current_scope)}
51
- #{iter_code}
52
- #{acc_var}
53
- end).call
54
- RUBY
48
+ [
49
+ '(-> do',
50
+ indent("#{acc_var} = #{emit_expr(bindings[1], env, current_scope)}"),
51
+ indent(iter_code),
52
+ indent(acc_var),
53
+ 'end).call'
54
+ ].join("\n")
55
55
  end
56
56
 
57
57
  def emit_faccumulate(args, env, current_scope)
@@ -73,29 +73,43 @@ module Kapusta
73
73
  current_scope:,
74
74
  body_code: accumulating_body
75
75
  )
76
- <<~RUBY.chomp
77
- (-> do
78
- #{acc_var} = #{emit_expr(bindings[1], env, current_scope)}
79
- #{indent(loop_code)}
80
- #{acc_var}
81
- end).call
82
- RUBY
76
+ [
77
+ '(-> do',
78
+ indent("#{acc_var} = #{emit_expr(bindings[1], env, current_scope)}"),
79
+ indent(loop_code),
80
+ indent(acc_var),
81
+ 'end).call'
82
+ ].join("\n")
83
83
  end
84
84
 
85
85
  def emit_hashfn(args, env, current_scope)
86
- args_var = temp('args')
87
- hash_env = env.child
88
- hash_env.define('$', "#{args_var}[0]")
89
- (1..9).each do |index|
90
- hash_env.define("$#{index}", "#{args_var}[#{index - 1}]")
86
+ if needs_explicit_args?(args[0])
87
+ args_var = temp('args')
88
+ hash_env = env.child
89
+ hash_env.define('$', "#{args_var}[0]")
90
+ (1..9).each { |i| hash_env.define("$#{i}", "#{args_var}[#{i - 1}]") }
91
+ hash_env.define('$...', args_var)
92
+ body_code = emit_expr(args[0], hash_env, current_scope)
93
+ ["->(*#{args_var}) do", indent(body_code), 'end'].join("\n")
94
+ else
95
+ hash_env = env.child
96
+ hash_env.define('$', '_1')
97
+ (1..9).each { |i| hash_env.define("$#{i}", "_#{i}") }
98
+ body_code = emit_expr(args[0], hash_env, current_scope)
99
+ ['proc do', indent(body_code), 'end'].join("\n")
91
100
  end
92
- hash_env.define('$...', args_var)
93
- body_code = emit_expr(args[0], hash_env, current_scope)
94
- <<~RUBY.chomp
95
- ->(*#{args_var}) do
96
- #{body_code}
101
+ end
102
+
103
+ def needs_explicit_args?(form)
104
+ case form
105
+ when Sym then form.name == '$...'
106
+ when List, Vec then form.items.any? { |item| needs_explicit_args?(item) }
107
+ when HashLit
108
+ form.entries.any? do |entry|
109
+ entry.is_a?(Array) ? entry.any? { |item| needs_explicit_args?(item) } : needs_explicit_args?(entry)
97
110
  end
98
- RUBY
111
+ else false
112
+ end
99
113
  end
100
114
 
101
115
  def emit_iteration(bindings_vec, env, current_scope)
@@ -106,22 +120,24 @@ module Kapusta
106
120
  if iter_expr.is_a?(List) && iter_expr.head.is_a?(Sym)
107
121
  case iter_expr.head.name
108
122
  when 'ipairs'
109
- value_var = temp('value')
110
- index_var = temp('index')
111
123
  body_env = env.child
112
- bind_code, body_env = emit_iteration_bindings(
113
- [[binding_pats[0], index_var], [binding_pats[1], value_var]], body_env
114
- )
124
+ value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
125
+ coll_code = emit_expr(iter_expr.items[1], env, current_scope)
126
+ if ignored_pattern?(binding_pats[0])
127
+ bind_code = value_bind || ''
128
+ body_code = yield(body_env)
129
+ return iteration_block("#{coll_code}.each do |#{value_var}|", bind_code, body_code)
130
+ end
131
+ index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
132
+ bind_code = [index_bind, value_bind].compact.join("\n")
115
133
  body_code = yield(body_env)
116
- header = "#{emit_expr(iter_expr.items[1], env, current_scope)}" \
117
- ".each_with_index do |#{value_var}, #{index_var}|"
134
+ header = "#{coll_code}.each_with_index do |#{value_var}, #{index_var}|"
118
135
  return iteration_block(header, bind_code, body_code)
119
136
  when 'pairs'
120
- key_var = temp('key')
121
- value_var = temp('value')
122
137
  body_env = env.child
123
- bind_code, body_env = emit_iteration_bindings([[binding_pats[0], key_var], [binding_pats[1], value_var]],
124
- body_env)
138
+ key_var, key_bind = bind_iteration_param(binding_pats[0], 'key', body_env)
139
+ value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
140
+ bind_code = [key_bind, value_bind].compact.join("\n")
125
141
  body_code = yield(body_env)
126
142
  header = "#{emit_expr(iter_expr.items[1], env, current_scope)}.each do |#{key_var}, #{value_var}|"
127
143
  return iteration_block(header, bind_code, body_code)
@@ -130,11 +146,10 @@ module Kapusta
130
146
 
131
147
  coll_code = emit_expr(iter_expr, env, current_scope)
132
148
  if binding_pats.length == 1
133
- value_var = temp('value')
134
149
  body_env = env.child
135
- bind_code, body_env = emit_iteration_bindings([[binding_pats[0], value_var]], body_env)
150
+ value_var, bind_code = bind_iteration_param(binding_pats[0], 'value', body_env)
136
151
  body_code = yield(body_env)
137
- iteration_block("#{coll_code}.each do |#{value_var}|", bind_code, body_code)
152
+ iteration_block("#{coll_code}.each do |#{value_var}|", bind_code || '', body_code)
138
153
  else
139
154
  parts_var = temp('parts')
140
155
  body_env = env.child
@@ -145,6 +160,21 @@ module Kapusta
145
160
  end
146
161
  end
147
162
 
163
+ def ignored_pattern?(pattern)
164
+ pattern.is_a?(Sym) && !pattern.dotted? && pattern.name == '_'
165
+ end
166
+
167
+ def bind_iteration_param(pattern, fallback_name, env)
168
+ if pattern.is_a?(Sym) && !pattern.dotted?
169
+ ruby_name = pattern.name == '_' ? '_' : define_local(env, pattern.name)
170
+ [ruby_name, nil]
171
+ else
172
+ tmp = temp(fallback_name)
173
+ bind_code, _new_env = emit_iteration_bindings([[pattern, tmp]], env)
174
+ [tmp, bind_code.empty? ? nil : bind_code]
175
+ end
176
+ end
177
+
148
178
  def iteration_block(header, bind_code, body_code)
149
179
  [header, indent(join_code(bind_code, body_code)), 'end'].join("\n")
150
180
  end
@@ -161,39 +191,38 @@ module Kapusta
161
191
  end
162
192
 
163
193
  def emit_collection_result(result_var, initial_code, iter_code)
164
- <<~RUBY.chomp
165
- (-> do
166
- #{result_var} = #{initial_code}
167
- #{indent(iter_code)}
168
- #{result_var}
169
- end).call
170
- RUBY
194
+ [
195
+ '(-> do',
196
+ indent("#{result_var} = #{initial_code}"),
197
+ indent(iter_code),
198
+ indent(result_var),
199
+ 'end).call'
200
+ ].join("\n")
171
201
  end
172
202
 
173
203
  def emit_array_collection_step(result_var, body_code)
174
204
  value_var = temp('value')
175
- <<~RUBY.chomp
176
- #{emit_sequence_value_assignment(value_var, body_code)}
177
- #{result_var} << #{value_var} unless #{value_var}.nil?
178
- RUBY
205
+ [
206
+ emit_sequence_value_assignment(value_var, body_code),
207
+ "#{result_var} << #{value_var} unless #{value_var}.nil?"
208
+ ].join("\n")
179
209
  end
180
210
 
181
211
  def emit_hash_collection_step(result_var, body_code)
182
212
  pair_var = temp('pair')
183
- <<~RUBY.chomp
184
- #{emit_sequence_value_assignment(pair_var, body_code)}
185
- if #{pair_var}.is_a?(Array) && #{pair_var}.length == 2 && !#{pair_var}[0].nil? && !#{pair_var}[1].nil?
186
- #{result_var}[#{pair_var}[0]] = #{pair_var}[1]
187
- end
188
- RUBY
213
+ [
214
+ emit_sequence_value_assignment(pair_var, body_code),
215
+ "if #{pair_var}.is_a?(Array) && #{pair_var}.length == 2 && " \
216
+ "!#{pair_var}[0].nil? && !#{pair_var}[1].nil?",
217
+ indent("#{result_var}[#{pair_var}[0]] = #{pair_var}[1]"),
218
+ 'end'
219
+ ].join("\n")
189
220
  end
190
221
 
191
222
  def emit_sequence_value_assignment(target_var, body_code)
192
- <<~RUBY.chomp
193
- #{target_var} = begin
194
- #{indent(body_code)}
195
- end
196
- RUBY
223
+ return emit_assignment(target_var, body_code) unless body_code.include?("\n")
224
+
225
+ ["#{target_var} = begin", indent(body_code), 'end'].join("\n")
197
226
  end
198
227
  end
199
228
  end
@@ -16,6 +16,8 @@ module Kapusta
16
16
 
17
17
  cond = emit_expr(args[0], env, current_scope)
18
18
  truthy = emit_if_branch(args[1], env, current_scope)
19
+ return "#{truthy} if #{cond}" if args.length == 2 && !truthy.include?("\n") && !cond.include?("\n")
20
+
19
21
  lines = ["if #{cond}", indent(truthy)]
20
22
  append_else_lines(lines, args[2..], env, current_scope)
21
23
  lines << 'end'
@@ -57,81 +59,52 @@ module Kapusta
57
59
 
58
60
  def emit_case(args, env, current_scope, mode)
59
61
  value_var = temp('case_value')
60
- body = build_case_clauses(value_var, args[1..], env, current_scope, mode)
61
- <<~RUBY.chomp
62
- (-> do
63
- #{value_var} = #{emit_expr(args[0], env, current_scope)}
64
- #{body}
65
- end).call
66
- RUBY
62
+ body = try_emit_native_case(value_var, args[1..], env, current_scope, mode)
63
+ emit_error!('case/match clauses use patterns this compiler cannot translate') unless body
64
+ [
65
+ '(-> do',
66
+ indent("#{value_var} = #{emit_expr(args[0], env, current_scope)}"),
67
+ indent(body),
68
+ 'end).call'
69
+ ].join("\n")
67
70
  end
68
71
 
69
- def build_case_clauses(value_var, clauses, env, current_scope, mode)
70
- return 'nil' if clauses.empty?
71
-
72
- pattern = clauses[0]
73
- body = clauses[1]
74
- else_code = build_case_clauses(value_var, clauses[2..], env, current_scope, mode)
75
- emit_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
76
- end
72
+ def try_emit_native_case(value_var, clauses, env, current_scope, mode)
73
+ arms = []
74
+ i = 0
75
+ while i < clauses.length
76
+ pattern = clauses[i]
77
+ body = clauses[i + 1]
78
+ inner, where_guards = if where_pattern?(pattern)
79
+ [pattern.items[1], pattern.items[2..]]
80
+ else
81
+ [pattern, []]
82
+ end
83
+ sub_patterns = or_pattern?(inner) ? inner.items[1..] : [inner]
84
+ sub_arms = sub_patterns.map do |sub|
85
+ try_native_arm(sub, body, where_guards, env, current_scope, mode)
86
+ end
87
+ return if sub_arms.any?(&:nil?)
77
88
 
78
- def emit_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
79
- if where_pattern?(pattern)
80
- emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
81
- else
82
- emit_simple_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
89
+ arms.concat(sub_arms)
90
+ i += 2
83
91
  end
92
+ arms << ['else', indent('nil')].join("\n")
93
+ ["case #{value_var}", *arms, 'end'].join("\n")
84
94
  end
85
95
 
86
- def emit_simple_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
87
- match_var = temp('match')
88
- bindings_var = temp('bindings')
89
- plan = pattern_match_plan(pattern, env, mode:, allow_pins: false)
90
- arm_env = env.child
91
- assign_code, arm_env = emit_bindings_from_match(plan[:bindings], bindings_var, arm_env)
92
- body_code = emit_expr(body, arm_env, current_scope)
93
- arm_body = [assign_code, body_code].reject(&:empty?).join("\n")
94
- <<~RUBY.chomp
95
- #{match_var} = #{runtime_call(:match_pattern, plan[:pattern], value_var)}
96
- if #{match_var}[0]
97
- #{bindings_var} = #{match_var}[1]
98
- #{arm_body}
99
- else
100
- #{indent(else_code)}
101
- end
102
- RUBY
103
- end
96
+ def try_native_arm(pattern, body, where_guards, env, current_scope, mode)
97
+ allow_pins = !where_guards.empty? && mode == :case
98
+ plan = native_pattern_plan(pattern, env, mode:, allow_pins:)
99
+ return unless plan
104
100
 
105
- def emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
106
- inner = pattern.items[1]
107
- guards = pattern.items[2..]
108
- match_var = temp('match')
109
- bindings_var = temp('bindings')
110
- plan = pattern_match_plan(inner, env, mode:, allow_pins: mode == :case)
111
101
  arm_env = env.child
112
- assign_code, arm_env = emit_bindings_from_match(plan[:bindings], bindings_var, arm_env)
113
- guard_code = emit_case_guards(guards, arm_env, current_scope)
102
+ plan[:bindings].each { |name| arm_env.define(name, sanitize_local(name)) }
103
+ guard_codes = plan[:guards] +
104
+ where_guards.map { |g| emit_expr(g, arm_env, current_scope) }
105
+ guard_clause = guard_codes.empty? ? '' : " if #{guard_codes.join(' && ')}"
114
106
  body_code = emit_expr(body, arm_env, current_scope)
115
- bindings_line = assign_code.empty? ? '' : "\n #{assign_code}"
116
- <<~RUBY.chomp
117
- #{match_var} = #{runtime_call(:match_pattern, plan[:pattern], value_var)}
118
- if #{match_var}[0]
119
- #{bindings_var} = #{match_var}[1]#{bindings_line}
120
- if #{guard_code}
121
- #{body_code}
122
- else
123
- #{indent(else_code, 2)}
124
- end
125
- else
126
- #{indent(else_code)}
127
- end
128
- RUBY
129
- end
130
-
131
- def emit_case_guards(guards, env, current_scope)
132
- return 'true' if guards.empty?
133
-
134
- guards.map { |guard| parenthesize(emit_expr(guard, env, current_scope)) }.join(' && ')
107
+ ["in #{plan[:pattern]}#{guard_clause}", indent(body_code)].join("\n")
135
108
  end
136
109
 
137
110
  def emit_while(args, env, current_scope)