kapusta 0.5.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f58b2e1f81723470a819ba38e3e6e811a3aa3a97ba79443e6ad0c59457d0fa08
4
- data.tar.gz: 9207c1491218f71eeeedf2495a22805b9e2d1a8336200d212437fb813005c1d8
3
+ metadata.gz: 76f35530eebc269dfec62ee1f36f6cc11a252ee4590eeac961c9b47e4e49ca09
4
+ data.tar.gz: a0ee60d21fb9a6066915d916e465bef09d9208c0469712673db35bea83efd7c4
5
5
  SHA512:
6
- metadata.gz: 9e0e2220a388a1a06ed86733e01fbc1eacf3a48536c503e7e8ccbcf85871fcda35cce16ddc9b434f3d1ac3c8dc23cb15af3d78528fc269fae62d25ef49a6fba2
7
- data.tar.gz: 3a0f8628514de972b1def03cfd8f47798b630632fe728a41d46a0c9aade7587c011004695903697b929fd82c3302bf90c7efdc7d5e4edb7f0ab7bd3c9c1c7387
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
- ## Usage
17
+ ## Install
18
18
 
19
19
  ```
20
20
  gem install kapusta
21
- kapfmt --fix examples/fizzbuzz.kap
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
- ## Using from Ruby
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
- ## Formatting
113
+ ## Format
104
114
 
105
115
  ```
106
- exe/kapfmt
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
@@ -12,6 +12,7 @@ KAPFMT = File.join(ROOT, 'exe', 'kapfmt')
12
12
  COMPATIBLE = %w[
13
13
  ackermann.kap
14
14
  anonymous-greeter.kap
15
+ classify-wallet.kap
15
16
  climbing-stairs.kap
16
17
  describe.kap
17
18
  destructure.kap
@@ -31,6 +32,7 @@ COMPATIBLE = %w[
31
32
  match.kap
32
33
  min-max.kap
33
34
  or-patterns.kap
35
+ power-of-three.kap
34
36
  packet-router.kap
35
37
  points.kap
36
38
  primes.kap
@@ -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}))
@@ -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))
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
@@ -28,12 +28,14 @@ module Kapusta
28
28
  end
29
29
 
30
30
  def emit_lambda(pattern, body, env, current_scope)
31
+ validate_fn_params!(pattern)
31
32
  return emit_simple_lambda(pattern, body, env, current_scope) if simple_parameter_pattern?(pattern)
32
33
 
33
34
  args_var = temp('args')
34
35
  body_env = env.child
35
36
  bindings_code, body_env = emit_pattern_bind(pattern, args_var, body_env)
36
- body_code, = emit_sequence(body, body_env, current_scope, allow_method_definitions: false)
37
+ body_code, = emit_sequence(body, body_env, current_scope,
38
+ allow_method_definitions: false, result: false)
37
39
  block_locals = pattern_names(pattern).map { |name| body_env.lookup(name) }.uniq
38
40
  block_locals_clause = block_locals.empty? ? '' : "; #{block_locals.join(', ')}"
39
41
  [
@@ -56,7 +58,8 @@ module Kapusta
56
58
  def build_simple_block_parts(pattern, body, env, current_scope)
57
59
  body_env = env.child
58
60
  params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
59
- body_code, = emit_sequence(body, body_env, current_scope, allow_method_definitions: false)
61
+ body_code, = emit_sequence(body, body_env, current_scope,
62
+ allow_method_definitions: false, result: false)
60
63
  [params, body_code]
61
64
  end
62
65
 
@@ -66,6 +69,17 @@ module Kapusta
66
69
  end
67
70
  end
68
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
81
+ end
82
+
69
83
  def emit_definition_form(form, env, current_scope)
70
84
  return [emit_method_definition(form, env), env] unless current_scope == :toplevel
71
85
 
@@ -127,6 +141,7 @@ module Kapusta
127
141
  end
128
142
 
129
143
  def emit_direct_method_definition(name_sym, pattern, body, env)
144
+ validate_fn_params!(pattern)
130
145
  return unless simple_parameter_pattern?(pattern)
131
146
 
132
147
  ruby_name = direct_method_definition_name(name_sym)
@@ -135,7 +150,8 @@ module Kapusta
135
150
 
136
151
  body_env = env.child
137
152
  params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
138
- body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
153
+ body_code, = emit_sequence(body, body_env, :toplevel,
154
+ allow_method_definitions: false, result: false)
139
155
  header = params.empty? ? "def #{ruby_name}" : "def #{ruby_name}(#{params.join(', ')})"
140
156
  [
141
157
  header,
@@ -169,12 +185,14 @@ module Kapusta
169
185
  end
170
186
 
171
187
  def emit_method_body(pattern, body, env)
188
+ validate_fn_params!(pattern)
172
189
  return emit_simple_method_body(pattern, body, env) if simple_parameter_pattern?(pattern)
173
190
 
174
191
  args_var = temp('args')
175
192
  method_env = env.child
176
193
  bindings_code, body_env = emit_pattern_bind(pattern, args_var, method_env)
177
- body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
194
+ body_code, = emit_sequence(body, body_env, :toplevel,
195
+ allow_method_definitions: false, result: false)
178
196
  block_locals = pattern_names(pattern).map { |name| body_env.lookup(name) }.uniq
179
197
  block_locals_clause = block_locals.empty? ? '' : "; #{block_locals.join(', ')}"
180
198
  ["do |*#{args_var}#{block_locals_clause}|", join_code(bindings_code, body_code)]
@@ -245,6 +263,7 @@ module Kapusta
245
263
  check_destructure_value!(pattern, value_form)
246
264
  value_code = emit_expr(value_form, child_env, current_scope)
247
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) }
248
267
  binding_codes << bind_code
249
268
  i += 2
250
269
  end
@@ -290,6 +309,21 @@ module Kapusta
290
309
  @binding_mutability[ruby_name] = mutable
291
310
  end
292
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
+
293
327
  def mutable_binding?(env, name)
294
328
  ruby_name = env.lookup_if_defined(name)
295
329
  return false unless ruby_name
@@ -4,6 +4,8 @@ module Kapusta
4
4
  module Compiler
5
5
  module EmitterModules
6
6
  module Collections
7
+ include LuaCompat::Emission
8
+
7
9
  private
8
10
 
9
11
  def emit_icollect(args, env, current_scope)
@@ -16,11 +18,44 @@ module Kapusta
16
18
 
17
19
  def emit_collect(args, env, current_scope)
18
20
  result_var = temp('result')
19
- iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
20
- body = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
21
- emit_hash_collection_step(result_var, body)
21
+ values_form = simple_values_call(args[1]) if args.length == 2
22
+ emit_iteration(args[0], env, current_scope,
23
+ method: 'each_with_object({})', extra_block_param: result_var) do |iter_env|
24
+ if values_form
25
+ emit_collect_values_step(result_var, values_form, iter_env, current_scope)
26
+ else
27
+ body = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
28
+ emit_hash_collection_step(result_var, body)
29
+ end
30
+ end
31
+ end
32
+
33
+ def simple_values_call(form)
34
+ return unless form.is_a?(List) && form.items.length == 3
35
+
36
+ head = form.head
37
+ form if head.is_a?(Sym) && head.name == 'values'
38
+ end
39
+
40
+ def emit_collect_values_step(result_var, values_form, iter_env, current_scope)
41
+ key_form = values_form.items[1]
42
+ val_form = values_form.items[2]
43
+ key_code = emit_expr(key_form, iter_env, current_scope)
44
+ val_code = emit_expr(val_form, iter_env, current_scope)
45
+ assignment = "#{result_var}[#{key_code}] = #{val_code}"
46
+ guards = []
47
+ guards << "#{key_code}.nil?" unless definitely_non_nil?(key_form)
48
+ guards << "#{val_code}.nil?" unless definitely_non_nil?(val_form)
49
+ return assignment if guards.empty?
50
+
51
+ "#{assignment} unless #{guards.join(' || ')}"
52
+ end
53
+
54
+ def definitely_non_nil?(form)
55
+ case form
56
+ when Numeric, String, ::Symbol, TrueClass, FalseClass then true
57
+ else false
22
58
  end
23
- emit_collection_result(result_var, '{}', iter_code)
24
59
  end
25
60
 
26
61
  def emit_fcollect(args, env, current_scope)
@@ -63,29 +98,8 @@ module Kapusta
63
98
  end
64
99
 
65
100
  def try_emit_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var, init_code, body_forms)
66
- return unless iter_expr.is_a?(List) && iter_expr.head.is_a?(Sym)
67
-
68
- coll_code = emit_expr(iter_expr.items[1], env, current_scope)
69
- case iter_expr.head.name
70
- when 'ipairs'
71
- value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
72
- if ignored_pattern?(binding_pats[0])
73
- body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
74
- return inject_block(coll_code, "#{acc_var}, #{value_var}", init_code, value_bind || '', body_code)
75
- end
76
- index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
77
- bind_code = [index_bind, value_bind].compact.join("\n")
78
- body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
79
- inject_block("#{coll_code}.each_with_index", "#{acc_var}, (#{value_var}, #{index_var})",
80
- init_code, bind_code, body_code)
81
- when 'pairs'
82
- key_var, key_bind = bind_iteration_param(binding_pats[0], 'key', body_env)
83
- value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
84
- bind_code = [key_bind, value_bind].compact.join("\n")
85
- body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
86
- inject_block(coll_code, "#{acc_var}, (#{key_var}, #{value_var})",
87
- init_code, bind_code, body_code)
88
- end
101
+ emit_lua_compat_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var,
102
+ init_code, body_forms)
89
103
  end
90
104
 
91
105
  def inject_block(receiver, params, init_code, bind_code, body_code)
@@ -153,54 +167,32 @@ module Kapusta
153
167
  end
154
168
  end
155
169
 
156
- def emit_iteration(bindings_vec, env, current_scope, method: 'each')
170
+ def emit_iteration(bindings_vec, env, current_scope, method: 'each', extra_block_param: nil, &block)
157
171
  emit_error!(:each_no_binding) unless bindings_vec.is_a?(Vec)
158
172
 
159
173
  items = bindings_vec.items
160
174
  iter_expr = items.last
161
175
  binding_pats = items[0...-1]
162
176
 
163
- if iter_expr.is_a?(List) && iter_expr.head.is_a?(Sym)
164
- case iter_expr.head.name
165
- when 'ipairs'
166
- body_env = env.child
167
- value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
168
- coll_code = emit_expr(iter_expr.items[1], env, current_scope)
169
- if ignored_pattern?(binding_pats[0])
170
- bind_code = value_bind || ''
171
- body_code = yield(body_env)
172
- return iteration_block("#{coll_code}.#{method} do |#{value_var}|", bind_code, body_code)
173
- end
174
- index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
175
- bind_code = [index_bind, value_bind].compact.join("\n")
176
- body_code = yield(body_env)
177
- receiver = method == 'each' ? "#{coll_code}.each_with_index" : "#{coll_code}.each_with_index.#{method}"
178
- header = "#{receiver} do |#{value_var}, #{index_var}|"
179
- return iteration_block(header, bind_code, body_code)
180
- when 'pairs'
181
- body_env = env.child
182
- key_var, key_bind = bind_iteration_param(binding_pats[0], 'key', body_env)
183
- value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
184
- bind_code = [key_bind, value_bind].compact.join("\n")
185
- body_code = yield(body_env)
186
- header = "#{emit_expr(iter_expr.items[1], env, current_scope)}.#{method} do |#{key_var}, #{value_var}|"
187
- return iteration_block(header, bind_code, body_code)
188
- end
189
- end
177
+ lua_iteration = emit_lua_compat_iteration(iter_expr, binding_pats, env, current_scope,
178
+ method:, extra_block_param:, &block)
179
+ return lua_iteration if lua_iteration
190
180
 
191
181
  coll_code = emit_expr(iter_expr, env, current_scope)
192
182
  if binding_pats.length == 1
193
183
  body_env = env.child
194
184
  value_var, bind_code = bind_iteration_param(binding_pats[0], 'value', body_env)
195
- body_code = yield(body_env)
196
- iteration_block("#{coll_code}.#{method} do |#{value_var}|", bind_code || '', body_code)
185
+ body_code = block.call(body_env)
186
+ params = extra_block_param ? "#{value_var}, #{extra_block_param}" : value_var
187
+ iteration_block("#{coll_code}.#{method} do |#{params}|", bind_code || '', body_code)
197
188
  else
198
189
  parts_var = temp('parts')
199
190
  body_env = env.child
200
191
  pairs = binding_pats.each_with_index.map { |pattern, i| [pattern, "#{parts_var}[#{i}]"] }
201
192
  bind_code, body_env = emit_iteration_bindings(pairs, body_env)
202
- body_code = yield(body_env)
203
- iteration_block("#{coll_code}.#{method} do |*#{parts_var}|", bind_code, body_code)
193
+ body_code = block.call(body_env)
194
+ params = extra_block_param ? "#{parts_var}, #{extra_block_param}" : "*#{parts_var}"
195
+ iteration_block("#{coll_code}.#{method} do |#{params}|", bind_code, body_code)
204
196
  end
205
197
  end
206
198
 
@@ -57,21 +57,38 @@ module Kapusta
57
57
  end
58
58
 
59
59
  def emit_case(args, env, current_scope, mode)
60
+ emit_error!(:case_no_subject) if args.empty?
61
+
60
62
  clauses = args[1..]
61
63
  emit_error!(:case_no_patterns) if clauses.empty?
62
64
  emit_error!(:case_odd_patterns) if clauses.length.odd?
63
65
 
66
+ value_code = emit_expr(args[0], env, current_scope)
67
+ if simple_case_subject?(args[0]) && simple_expression?(value_code)
68
+ body = try_emit_native_case(value_code, clauses, env, current_scope, mode)
69
+ emit_error!(:case_unsupported) unless body
70
+ return body
71
+ end
72
+
64
73
  value_var = temp('case_value')
65
74
  body = try_emit_native_case(value_var, clauses, env, current_scope, mode)
66
75
  emit_error!(:case_unsupported) unless body
67
76
  [
68
77
  '(-> do',
69
- indent("#{value_var} = #{emit_expr(args[0], env, current_scope)}"),
78
+ indent("#{value_var} = #{value_code}"),
70
79
  indent(body),
71
80
  'end).call'
72
81
  ].join("\n")
73
82
  end
74
83
 
84
+ def simple_case_subject?(form)
85
+ case form
86
+ when Sym then !form.dotted?
87
+ when Numeric, String, Symbol, true, false, nil then true
88
+ else false
89
+ end
90
+ end
91
+
75
92
  def try_emit_native_case(value_var, clauses, env, current_scope, mode)
76
93
  arms = []
77
94
  i = 0
@@ -92,10 +109,15 @@ module Kapusta
92
109
  arms.concat(sub_arms)
93
110
  i += 2
94
111
  end
95
- arms << ['else', indent('nil')].join("\n")
112
+ arms << ['else', indent('nil')].join("\n") unless wildcard_last?(clauses)
96
113
  ["case #{value_var}", *arms, 'end'].join("\n")
97
114
  end
98
115
 
116
+ def wildcard_last?(clauses)
117
+ last_pattern = clauses[-2]
118
+ last_pattern.is_a?(Sym) && last_pattern.name == '_'
119
+ end
120
+
99
121
  def try_native_arm(pattern, body, where_guards, env, current_scope, mode)
100
122
  allow_pins = !where_guards.empty? && mode == :case
101
123
  plan = native_pattern_plan(pattern, env, mode:, allow_pins:)
@@ -111,8 +111,6 @@ module Kapusta
111
111
  when 'quasi-vec-tail' then emit_quasi_vec_tail(args, env, current_scope)
112
112
  when 'quasi-hash' then emit_quasi_hash(args, env, current_scope)
113
113
  when 'quasi-gensym' then emit_quasi_gensym(args[0], env, current_scope)
114
- when 'macro', 'macros', 'import-macros'
115
- emit_error!(:special_must_be_toplevel, name:)
116
114
  else
117
115
  emit_error!(:unknown_special_form, name:)
118
116
  end
@@ -427,11 +427,12 @@ module Kapusta
427
427
  def emit_sym(sym, env)
428
428
  name = sym.name
429
429
  return 'self' if name == 'self'
430
- return 'Float::INFINITY' if name == 'math.huge'
431
430
 
432
431
  if (binding = env.lookup_if_defined(sym))
433
432
  return binding_value_code(binding)
434
433
  end
434
+
435
+ emit_error!(:unexpected_vararg) if name == '...'
435
436
  return emit_multisym_value(sym, env) if sym.dotted?
436
437
  return 'ARGV' if name == 'ARGV'
437
438
  return name if name.match?(/\A[A-Z]/)
@@ -246,12 +246,60 @@ module Kapusta
246
246
  end
247
247
 
248
248
  def compile_native_hash(pattern, env, mode:, allow_pins:, state:)
249
- pairs = pattern.pairs.map do |key, value|
250
- raise PatternNotTranslatable unless key.is_a?(Symbol)
251
-
249
+ sym_pairs, other_pairs = pattern.pairs.partition { |key, _| key.is_a?(Symbol) }
250
+ sym_parts = sym_pairs.map do |key, value|
252
251
  "#{key}: #{compile_native_pattern(value, env, mode:, allow_pins:, state:)}"
253
252
  end
254
- "{#{pairs.join(', ')}}"
253
+ return "{#{sym_parts.join(', ')}}" if other_pairs.empty?
254
+
255
+ capture = temp('hash')
256
+ sym_pattern = sym_parts.empty? ? 'Hash' : "{#{sym_parts.join(', ')}}"
257
+ other_pairs.each do |key, value|
258
+ compile_native_hash_value(value, capture, compile_native_hash_key(key), env, mode:, state:)
259
+ end
260
+ "#{sym_pattern} => #{capture}"
261
+ end
262
+
263
+ def compile_native_hash_key(key)
264
+ case key
265
+ when Symbol, String, Numeric, true, false then key.inspect
266
+ when nil then 'nil'
267
+ else raise PatternNotTranslatable
268
+ end
269
+ end
270
+
271
+ def compile_native_hash_value(value, capture, key_code, env, mode:, state:)
272
+ lookup = "#{capture}[#{key_code}]"
273
+ case value
274
+ when Sym
275
+ name = value.name
276
+ return if name == '_'
277
+
278
+ if nil_allowing_pattern_name?(name)
279
+ raise PatternNotTranslatable if state[:bound_names].key?(name)
280
+
281
+ state[:bound_names][name] = true
282
+ state[:binding_names] << name
283
+ state[:guards] << "(#{sanitize_local(name)} = #{lookup}; true)"
284
+ else
285
+ binding = mode == :match ? env.lookup_if_defined(name) : nil
286
+ if state[:bound_names].key?(name)
287
+ raise PatternNotTranslatable
288
+ elsif binding
289
+ state[:guards] << "#{lookup} == #{binding_value_code(binding)}"
290
+ else
291
+ state[:bound_names][name] = true
292
+ state[:binding_names] << name
293
+ state[:guards] << "!(#{sanitize_local(name)} = #{lookup}).nil?"
294
+ end
295
+ end
296
+ when nil
297
+ state[:guards] << "#{lookup}.nil?"
298
+ when Symbol, String, Numeric, true, false
299
+ state[:guards] << "#{lookup} == #{value.inspect}"
300
+ else
301
+ raise PatternNotTranslatable
302
+ end
255
303
  end
256
304
 
257
305
  def compile_native_pin(pattern, env, mode:, allow_pins:)
@@ -128,7 +128,7 @@ module Kapusta
128
128
  code, current_env = emit_form_in_sequence(form, current_env, current_scope,
129
129
  allow_method_definitions:,
130
130
  result_needed: result && index == forms.length - 1)
131
- codes << code
131
+ codes << code unless code.empty?
132
132
  end
133
133
  [codes.join("\n"), current_env]
134
134
  end
@@ -31,7 +31,7 @@ module Kapusta
31
31
 
32
32
  def emit_file(forms)
33
33
  env = Env.new
34
- body = emit_forms_with_headers(forms, env, :toplevel)
34
+ body = emit_forms_with_headers(forms, env, :toplevel, result: false)
35
35
  "#{body}\n"
36
36
  end
37
37
  end