kapusta 0.1.2 → 0.1.4

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.
@@ -16,19 +16,40 @@ module Kapusta
16
16
 
17
17
  cond = emit_expr(args[0], env, current_scope)
18
18
  truthy = emit_expr(args[1], env, current_scope)
19
- falsy = build_if(args[2..], env, current_scope)
20
- <<~RUBY.chomp
21
- if #{cond}
22
- #{truthy}
23
- else
24
- #{indent(falsy)}
25
- end
26
- RUBY
19
+ lines = ["if #{cond}", indent(truthy)]
20
+ append_else_lines(lines, args[2..], env, current_scope)
21
+ lines << 'end'
22
+ lines.join("\n")
23
+ end
24
+
25
+ def append_else_lines(lines, args, env, current_scope)
26
+ return if args.empty?
27
+
28
+ if args.length == 1 && if_form?(args[0])
29
+ append_elsif_lines(lines, args[0].rest, env, current_scope)
30
+ elsif args.length >= 2
31
+ append_elsif_lines(lines, args, env, current_scope)
32
+ else
33
+ lines << 'else'
34
+ lines << indent(emit_expr(args[0], env, current_scope))
35
+ end
36
+ end
37
+
38
+ def append_elsif_lines(lines, args, env, current_scope)
39
+ return append_else_lines(lines, args, env, current_scope) if args.length < 2
40
+
41
+ lines << "elsif #{emit_expr(args[0], env, current_scope)}"
42
+ lines << indent(emit_expr(args[1], env, current_scope))
43
+ append_else_lines(lines, args[2..], env, current_scope)
27
44
  end
28
45
 
29
- def emit_case(args, env, current_scope)
46
+ def if_form?(form)
47
+ form.is_a?(List) && form.head.is_a?(Sym) && form.head.name == 'if'
48
+ end
49
+
50
+ def emit_case(args, env, current_scope, mode)
30
51
  value_var = temp('case_value')
31
- body = build_case_clauses(value_var, args[1..], env, current_scope)
52
+ body = build_case_clauses(value_var, args[1..], env, current_scope, mode)
32
53
  <<~RUBY.chomp
33
54
  (-> do
34
55
  #{value_var} = #{emit_expr(args[0], env, current_scope)}
@@ -37,31 +58,32 @@ module Kapusta
37
58
  RUBY
38
59
  end
39
60
 
40
- def build_case_clauses(value_var, clauses, env, current_scope)
61
+ def build_case_clauses(value_var, clauses, env, current_scope, mode)
41
62
  return 'nil' if clauses.empty?
42
63
 
43
64
  pattern = clauses[0]
44
65
  body = clauses[1]
45
- else_code = build_case_clauses(value_var, clauses[2..], env, current_scope)
46
- emit_case_clause(value_var, pattern, body, else_code, env, current_scope)
66
+ else_code = build_case_clauses(value_var, clauses[2..], env, current_scope, mode)
67
+ emit_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
47
68
  end
48
69
 
49
- def emit_case_clause(value_var, pattern, body, else_code, env, current_scope)
70
+ def emit_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
50
71
  if where_pattern?(pattern)
51
- emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope)
72
+ emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
52
73
  else
53
- emit_simple_case_clause(value_var, pattern, body, else_code, env, current_scope)
74
+ emit_simple_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
54
75
  end
55
76
  end
56
77
 
57
- def emit_simple_case_clause(value_var, pattern, body, else_code, env, current_scope)
78
+ def emit_simple_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
58
79
  match_var = temp('match')
59
80
  bindings_var = temp('bindings')
81
+ plan = pattern_match_plan(pattern, env, mode:, allow_pins: false)
60
82
  arm_env = env.child
61
- assign_code, arm_env = emit_bindings_from_match(pattern, bindings_var, arm_env)
83
+ assign_code, arm_env = emit_bindings_from_match(plan[:bindings], bindings_var, arm_env)
62
84
  body_code = emit_expr(body, arm_env, current_scope)
63
85
  <<~RUBY.chomp
64
- #{match_var} = #{runtime_call(:match_pattern, emit_pattern(pattern), value_var)}
86
+ #{match_var} = #{runtime_call(:match_pattern, plan[:pattern], value_var)}
65
87
  if #{match_var}[0]
66
88
  #{bindings_var} = #{match_var}[1]
67
89
  #{assign_code}
@@ -72,17 +94,18 @@ module Kapusta
72
94
  RUBY
73
95
  end
74
96
 
75
- def emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope)
97
+ def emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
76
98
  inner = pattern.items[1]
77
- guard = pattern.items[2]
99
+ guards = pattern.items[2..]
78
100
  match_var = temp('match')
79
101
  bindings_var = temp('bindings')
102
+ plan = pattern_match_plan(inner, env, mode:, allow_pins: mode == :case)
80
103
  arm_env = env.child
81
- assign_code, arm_env = emit_bindings_from_match(inner, bindings_var, arm_env)
82
- guard_code = emit_expr(guard, arm_env, current_scope)
104
+ assign_code, arm_env = emit_bindings_from_match(plan[:bindings], bindings_var, arm_env)
105
+ guard_code = emit_case_guards(guards, arm_env, current_scope)
83
106
  body_code = emit_expr(body, arm_env, current_scope)
84
107
  <<~RUBY.chomp
85
- #{match_var} = #{runtime_call(:match_pattern, emit_pattern(inner), value_var)}
108
+ #{match_var} = #{runtime_call(:match_pattern, plan[:pattern], value_var)}
86
109
  if #{match_var}[0]
87
110
  #{bindings_var} = #{match_var}[1]
88
111
  #{assign_code}
@@ -97,64 +120,33 @@ module Kapusta
97
120
  RUBY
98
121
  end
99
122
 
123
+ def emit_case_guards(guards, env, current_scope)
124
+ return 'true' if guards.empty?
125
+
126
+ guards.map { |guard| parenthesize(emit_expr(guard, env, current_scope)) }.join(' && ')
127
+ end
128
+
100
129
  def emit_while(args, env, current_scope)
101
- body_code, = emit_sequence(args[1..], env, current_scope, allow_method_definitions: false)
102
130
  <<~RUBY.chomp
103
131
  (-> do
104
- while #{emit_expr(args[0], env, current_scope)}
105
- #{indent(body_code)}
106
- end
132
+ #{indent(emit_while_statement(args, env, current_scope))}
107
133
  nil
108
134
  end).call
109
135
  RUBY
110
136
  end
111
137
 
112
138
  def emit_for(args, env, current_scope)
113
- bindings = args[0].items
114
- name = bindings[0]
115
- start_code = emit_expr(bindings[1], env, current_scope)
116
- finish_code = emit_expr(bindings[2], env, current_scope)
117
- step_code = '1'
118
- until_form = nil
119
- i = 3
120
- while i < bindings.length
121
- if bindings[i].is_a?(Sym) && bindings[i].name == '&until'
122
- until_form = bindings[i + 1]
123
- i += 2
124
- else
125
- step_code = emit_expr(bindings[i], env, current_scope)
126
- i += 1
127
- end
128
- end
129
-
130
- loop_env = env.child
131
- ruby_name = temp(sanitize_local(name.name))
132
- loop_env.define(name.name, ruby_name)
133
- body_code, = emit_sequence(args[1..], loop_env, current_scope, allow_method_definitions: false)
134
- until_code = until_form ? "break if #{emit_expr(until_form, loop_env, current_scope)}" : nil
135
- cmp_var = temp('cmp')
136
- step_var = temp('step')
137
- finish_var = temp('finish')
139
+ loop_code = emit_for_statement(args, env, current_scope)
138
140
  <<~RUBY.chomp
139
141
  (-> do
140
- #{ruby_name} = #{start_code}
141
- #{finish_var} = #{finish_code}
142
- #{step_var} = #{step_code}
143
- #{cmp_var} = #{step_var} >= 0 ? :<= : :>=
144
- while #{ruby_name}.public_send(#{cmp_var}, #{finish_var})
145
- #{until_code}
146
- #{indent(body_code)}
147
- #{ruby_name} += #{step_var}
148
- end
142
+ #{indent(loop_code)}
149
143
  nil
150
144
  end).call
151
145
  RUBY
152
146
  end
153
147
 
154
148
  def emit_each(args, env, current_scope)
155
- iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
156
- emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
157
- end
149
+ iter_code = emit_each_statement(args, env, current_scope)
158
150
  <<~RUBY.chomp
159
151
  (-> do
160
152
  #{iter_code}
@@ -162,6 +154,33 @@ module Kapusta
162
154
  end).call
163
155
  RUBY
164
156
  end
157
+
158
+ def emit_while_statement(args, env, current_scope)
159
+ body_code, = emit_sequence(args[1..], env, current_scope,
160
+ allow_method_definitions: false,
161
+ result: false)
162
+ [
163
+ "while #{emit_expr(args[0], env, current_scope)}",
164
+ indent(body_code),
165
+ 'end'
166
+ ].join("\n")
167
+ end
168
+
169
+ def emit_for_statement(args, env, current_scope)
170
+ parsed = parse_counted_for_bindings(args[0].items, env, current_scope)
171
+ body_code, = emit_sequence(args[1..], parsed[:loop_env], current_scope,
172
+ allow_method_definitions: false,
173
+ result: false)
174
+ emit_counted_loop(**parsed, current_scope:, body_code:)
175
+ end
176
+
177
+ def emit_each_statement(args, env, current_scope)
178
+ emit_iteration(args[0], env, current_scope) do |iter_env|
179
+ emit_sequence(args[1..], iter_env, current_scope,
180
+ allow_method_definitions: false,
181
+ result: false).first
182
+ end
183
+ end
165
184
  end
166
185
  end
167
186
  end
@@ -51,7 +51,8 @@ module Kapusta
51
51
  when 'local', 'var' then emit_local_expr(args, env, current_scope)
52
52
  when 'set' then emit_set_expr(args, env, current_scope)
53
53
  when 'if' then emit_if(args, env, current_scope)
54
- when 'case', 'match' then emit_case(args, env, current_scope)
54
+ when 'case' then emit_case(args, env, current_scope, :case)
55
+ when 'match' then emit_case(args, env, current_scope, :match)
55
56
  when 'while' then emit_while(args, env, current_scope)
56
57
  when 'for' then emit_for(args, env, current_scope)
57
58
  when 'each' then emit_each(args, env, current_scope)
@@ -78,7 +79,7 @@ module Kapusta
78
79
  when 'raise' then emit_raise(args, env, current_scope)
79
80
  when 'ivar' then runtime_call(:get_ivar, 'self', args[0].name.inspect)
80
81
  when 'cvar' then runtime_call(:get_cvar, 'self', args[0].name.inspect)
81
- when 'gvar' then runtime_call(:get_gvar, args[0].name.inspect)
82
+ when 'gvar' then emit_gvar(args[0])
82
83
  when 'ruby' then "Kernel.eval(#{emit_expr(args[0], env, current_scope)})"
83
84
  when 'and' then emit_and(args, env, current_scope)
84
85
  when 'or' then emit_or(args, env, current_scope)
@@ -37,26 +37,40 @@ module Kapusta
37
37
  end
38
38
 
39
39
  def emit_module_expr(args, env)
40
- emit_module_wrapper(args[0], emit_sequence(args[1..], env, :module, allow_method_definitions: true).first)
40
+ body = emit_sequence(args[1..], env, :module, allow_method_definitions: true, result: false).first
41
+ emit_module_wrapper(args[0], body)
41
42
  end
42
43
 
43
44
  def emit_class_expr(args, env)
44
45
  name_sym, supers, body_forms = split_class_args(args)
46
+ body = emit_sequence(body_forms, env, :class, allow_method_definitions: true, result: false).first
45
47
  emit_class_wrapper(name_sym, supers, env,
46
- emit_sequence(body_forms, env, :class, allow_method_definitions: true).first)
48
+ body)
47
49
  end
48
50
 
49
51
  def emit_module_wrapper(name_sym, body)
50
52
  mod_var = temp('module')
51
- <<~RUBY.chomp
52
- (-> do
53
- #{mod_var} = #{runtime_call(:ensure_module, 'self', name_sym.name.inspect)}
54
- #{mod_var}.module_eval do
55
- #{indent(body)}
56
- end
57
- #{mod_var}
58
- end).call
59
- RUBY
53
+ [
54
+ '(-> do',
55
+ indent("#{mod_var} = #{runtime_call(:ensure_module, 'self', name_sym.name.inspect)}"),
56
+ indent("#{mod_var}.module_eval do"),
57
+ indent(body, 2),
58
+ indent('end'),
59
+ indent(mod_var),
60
+ 'end).call'
61
+ ].join("\n")
62
+ end
63
+
64
+ def emit_direct_module_header(name_sym, body)
65
+ const_name = simple_constant_name(name_sym)
66
+ return nil unless const_name
67
+
68
+ [
69
+ "module #{const_name}",
70
+ indent(body),
71
+ 'end',
72
+ const_name
73
+ ].join("\n")
60
74
  end
61
75
 
62
76
  def emit_class_wrapper(name_sym, supers, env, body)
@@ -67,15 +81,33 @@ module Kapusta
67
81
  else
68
82
  'Object'
69
83
  end
70
- <<~RUBY.chomp
71
- (-> do
72
- #{klass_var} = #{runtime_call(:ensure_class, 'self', name_sym.name.inspect, super_code)}
73
- #{klass_var}.class_eval do
74
- #{indent(body)}
75
- end
76
- #{klass_var}
77
- end).call
78
- RUBY
84
+ [
85
+ '(-> do',
86
+ indent("#{klass_var} = #{runtime_call(:ensure_class, 'self', name_sym.name.inspect, super_code)}"),
87
+ indent("#{klass_var}.class_eval do"),
88
+ indent(body, 2),
89
+ indent('end'),
90
+ indent(klass_var),
91
+ 'end).call'
92
+ ].join("\n")
93
+ end
94
+
95
+ def emit_direct_class_header(name_sym, supers, body)
96
+ const_name = simple_constant_name(name_sym)
97
+ return nil unless const_name && supers.nil?
98
+
99
+ [
100
+ "class #{const_name}",
101
+ indent(body),
102
+ 'end',
103
+ const_name
104
+ ].join("\n")
105
+ end
106
+
107
+ def simple_constant_name(name_sym)
108
+ return nil unless name_sym.is_a?(Sym) && name_sym.name.match?(/\A[A-Z]\w*\z/)
109
+
110
+ name_sym.name
79
111
  end
80
112
 
81
113
  def emit_try(args, env, current_scope)
@@ -103,8 +135,7 @@ module Kapusta
103
135
  lines = ['begin', indent(emit_expr(args[0], env, current_scope))]
104
136
  catches.each do |klass_form, bind_sym, body|
105
137
  rescue_env = env.child
106
- rescue_name = temp(sanitize_local(bind_sym.name))
107
- rescue_env.define(bind_sym.name, rescue_name)
138
+ rescue_name = define_local(rescue_env, bind_sym.name)
108
139
  body_code, = emit_sequence(body, rescue_env, current_scope, allow_method_definitions: false)
109
140
  rescue_line =
110
141
  if klass_form
@@ -176,9 +207,17 @@ module Kapusta
176
207
 
177
208
  def emit_callable_call(callee_code, args, env, current_scope)
178
209
  positional, kwargs, block = split_call_args(args, env, current_scope)
210
+ return emit_direct_callable_call(callee_code, positional) unless kwargs || block
211
+
179
212
  runtime_call(:call, callee_code, "[#{positional.join(', ')}]", kwargs, block)
180
213
  end
181
214
 
215
+ def emit_direct_callable_call(callee_code, positional)
216
+ rendered_args = positional.join(', ')
217
+ suffix = rendered_args.empty? ? '.call' : ".call(#{rendered_args})"
218
+ "#{parenthesize(callee_code)}#{suffix}"
219
+ end
220
+
182
221
  def emit_multisym_call(head, args, env, current_scope)
183
222
  base_code, segments = multisym_base(head.segments, env)
184
223
  if segments.empty?
@@ -192,10 +231,21 @@ module Kapusta
192
231
  end
193
232
  method_name = Kapusta.kebab_to_snake(segments.last).to_sym.inspect
194
233
  positional, kwargs, block = split_call_args(args, env, current_scope)
234
+ if segments.length == 1 && !kwargs && !block && direct_method_name?(segments.last)
235
+ return emit_direct_method_call(receiver, Kapusta.kebab_to_snake(segments.last), positional)
236
+ end
237
+
195
238
  runtime_call(:send_call, receiver, method_name, positional, kwargs, block)
196
239
  end
197
240
  end
198
241
 
242
+ def emit_direct_method_call(receiver, method_name, positional)
243
+ args = positional.join(', ')
244
+ rendered_receiver = simple_expression?(receiver) ? receiver : parenthesize(receiver)
245
+ suffix = args.empty? ? method_name : "#{method_name}(#{args})"
246
+ "#{rendered_receiver}.#{suffix}"
247
+ end
248
+
199
249
  def emit_self_call(name, args, env, current_scope)
200
250
  positional, kwargs, block = split_call_args(args, env, current_scope)
201
251
  method_name = Kapusta.kebab_to_snake(name).to_sym.inspect
@@ -243,6 +293,13 @@ module Kapusta
243
293
  raise Error, "undefined symbol: #{name}"
244
294
  end
245
295
 
296
+ def emit_gvar(sym)
297
+ ruby_name = global_name(sym.name)
298
+ return "$#{ruby_name}" if direct_global_name?(ruby_name)
299
+
300
+ runtime_call(:get_gvar, sym.name.inspect)
301
+ end
302
+
246
303
  def emit_multisym_value(sym, env)
247
304
  base_code, segments = multisym_base(sym.segments, env)
248
305
  return base_code if segments.empty?
@@ -269,8 +326,34 @@ module Kapusta
269
326
  end
270
327
 
271
328
  def parenthesize(code)
329
+ return code if simple_expression?(code)
330
+
272
331
  "(#{code})"
273
332
  end
333
+
334
+ def direct_method_name?(name)
335
+ Kapusta.kebab_to_snake(name).match?(/\A[a-z_]\w*[!?=]?\z/)
336
+ end
337
+
338
+ def direct_global_name?(name)
339
+ name.match?(/\A[a-z_]\w*\z/)
340
+ end
341
+
342
+ def global_name(name)
343
+ Kapusta.kebab_to_snake(name).gsub(/[^a-zA-Z0-9_]/, '_')
344
+ end
345
+
346
+ def simple_expression?(code)
347
+ code.match?(/\A[a-z_]\w*\z/) ||
348
+ code.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*\z/) ||
349
+ code.match?(/\A[a-z_]\w*[!?=]?\([^()\n]*\)\z/) ||
350
+ code.match?(/\A\d+(?:\.\d+)?\z/) ||
351
+ code.match?(/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?)+\z/) ||
352
+ code.match?(/\A:[a-zA-Z_]\w*[!?=]?\z/) ||
353
+ code.match?(/\A"(?:[^"\\]|\\.)*"\z/) ||
354
+ code.match?(/\A'(?:[^'\\]|\\.)*'\z/) ||
355
+ %w[nil true false self].include?(code)
356
+ end
274
357
  end
275
358
  end
276
359
  end
@@ -10,8 +10,7 @@ module Kapusta
10
10
  if pattern.is_a?(Sym)
11
11
  return ['nil', env] if pattern.name == '_'
12
12
 
13
- ruby_name = temp(sanitize_local(pattern.name))
14
- env.define(pattern.name, ruby_name)
13
+ ruby_name = define_local(env, pattern.name)
15
14
  ["#{ruby_name} = #{value_code}", env]
16
15
  else
17
16
  bindings_var = temp('bindings')
@@ -20,25 +19,135 @@ module Kapusta
20
19
  "#{bindings_var} = #{runtime_call(:destructure, emit_pattern(pattern), value_code)}"
21
20
  ]
22
21
  pattern_names(pattern).each do |name|
23
- ruby_name = temp(sanitize_local(name))
24
- current_env.define(name, ruby_name)
22
+ ruby_name = define_local(current_env, name)
25
23
  lines << "#{ruby_name} = #{bindings_var}.fetch(#{name.inspect})"
26
24
  end
27
25
  [lines.join("\n"), current_env]
28
26
  end
29
27
  end
30
28
 
31
- def emit_bindings_from_match(pattern, bindings_var, env)
29
+ def emit_bindings_from_match(binding_names, bindings_var, env)
32
30
  current_env = env
33
31
  lines = []
34
- pattern_names(pattern).each do |name|
35
- ruby_name = temp(sanitize_local(name))
36
- current_env.define(name, ruby_name)
32
+ binding_names.each do |name|
33
+ ruby_name = define_local(current_env, name)
37
34
  lines << "#{ruby_name} = #{bindings_var}.fetch(#{name.inspect})"
38
35
  end
39
36
  [lines.join("\n"), current_env]
40
37
  end
41
38
 
39
+ def pattern_match_plan(pattern, env, mode:, allow_pins:)
40
+ state = { bound_names: {}, binding_names: [] }
41
+ {
42
+ pattern: emit_match_pattern(pattern, env, mode:, allow_pins:, state:),
43
+ bindings: state[:binding_names]
44
+ }
45
+ end
46
+
47
+ def emit_match_pattern(pattern, env, mode:, allow_pins:, state:)
48
+ case pattern
49
+ when Sym
50
+ emit_symbol_match_pattern(pattern, env, mode:, state:)
51
+ when Vec
52
+ emit_sequence_match_pattern(pattern.items, env, mode:, allow_pins:, state:)
53
+ when HashLit
54
+ pairs = pattern.pairs.map do |key, value|
55
+ "[#{key.inspect}, #{emit_match_pattern(value, env, mode:, allow_pins:, state:)}]"
56
+ end
57
+ "[:hash, [#{pairs.join(', ')}]]"
58
+ when List
59
+ if pin_pattern?(pattern)
60
+ emit_pin_match_pattern(pattern, env, mode:, allow_pins:)
61
+ elsif or_pattern?(pattern)
62
+ emit_or_match_pattern(pattern, env, mode:, allow_pins:, state:)
63
+ elsif where_pattern?(pattern)
64
+ raise Error, '`where` is only valid as a case/match clause head'
65
+ else
66
+ emit_sequence_match_pattern(pattern.items, env, mode:, allow_pins:, state:)
67
+ end
68
+ when nil
69
+ '[:lit, nil]'
70
+ when Symbol, String, Numeric, true, false
71
+ "[:lit, #{pattern.inspect}]"
72
+ else
73
+ raise Error, "bad pattern: #{pattern.inspect}"
74
+ end
75
+ end
76
+
77
+ def emit_symbol_match_pattern(pattern, env, mode:, state:)
78
+ name = pattern.name
79
+
80
+ if name == '_'
81
+ '[:wild]'
82
+ elsif nil_allowing_pattern_name?(name)
83
+ bind_name = name.start_with?('?') ? name.delete_prefix('?') : name
84
+ emit_named_match_pattern(bind_name, env, mode:, state:, allow_nil: true, prefer_pin: false)
85
+ else
86
+ emit_named_match_pattern(name, env, mode:, state:, allow_nil: false, prefer_pin: true)
87
+ end
88
+ end
89
+
90
+ def emit_named_match_pattern(name, env, mode:, state:, allow_nil:, prefer_pin:)
91
+ if state[:bound_names].key?(name)
92
+ "[:ref, #{name.inspect}]"
93
+ elsif prefer_pin && mode == :match && env.defined?(name)
94
+ "[:pin, #{env.lookup(name)}]"
95
+ else
96
+ state[:bound_names][name] = true
97
+ state[:binding_names] << name
98
+ "[:bind, #{name.inspect}, #{allow_nil}]"
99
+ end
100
+ end
101
+
102
+ def emit_sequence_match_pattern(items, env, mode:, allow_pins:, state:)
103
+ parts = []
104
+ i = 0
105
+ while i < items.length
106
+ if rest_pattern_marker?(items, i)
107
+ parts << "[:rest, #{emit_match_pattern(items[i + 1], env, mode:, allow_pins:, state:)}]"
108
+ i += 2
109
+ else
110
+ parts << emit_match_pattern(items[i], env, mode:, allow_pins:, state:)
111
+ i += 1
112
+ end
113
+ end
114
+ "[:vec, [#{parts.join(', ')}]]"
115
+ end
116
+
117
+ def emit_pin_match_pattern(pattern, env, mode:, allow_pins:)
118
+ raise Error, 'pin patterns are only supported inside `case` guards' unless allow_pins && mode == :case
119
+
120
+ name_sym = pattern.items[1]
121
+ raise Error, "bad pin pattern: #{pattern.inspect}" unless name_sym.is_a?(Sym)
122
+ raise Error, "cannot pin undefined name: #{name_sym.name}" unless env.defined?(name_sym.name)
123
+
124
+ "[:pin, #{env.lookup(name_sym.name)}]"
125
+ end
126
+
127
+ def emit_or_match_pattern(pattern, env, mode:, allow_pins:, state:)
128
+ initial_names = state[:binding_names].length
129
+ initial_bound = state[:bound_names].dup
130
+ canonical_names = nil
131
+ variants = pattern.items[1..].map do |subpattern|
132
+ alt_state = {
133
+ bound_names: initial_bound.dup,
134
+ binding_names: state[:binding_names].dup
135
+ }
136
+ compiled = emit_match_pattern(subpattern, env, mode:, allow_pins:, state: alt_state)
137
+ alt_names = alt_state[:binding_names][initial_names..]
138
+ canonical_names ||= alt_names
139
+ raise Error, 'all `or` patterns must bind the same names' if canonical_names.sort != alt_names.sort
140
+
141
+ compiled
142
+ end
143
+
144
+ canonical_names.each do |name|
145
+ state[:bound_names][name] = true
146
+ state[:binding_names] << name
147
+ end
148
+ "[:or, [#{variants.join(', ')}]]"
149
+ end
150
+
42
151
  def emit_pattern(pattern)
43
152
  case pattern
44
153
  when Sym
@@ -99,6 +208,25 @@ module Kapusta
99
208
  def where_pattern?(pattern)
100
209
  pattern.is_a?(List) && pattern.head.is_a?(Sym) && pattern.head.name == 'where'
101
210
  end
211
+
212
+ def pin_pattern?(pattern)
213
+ pattern.is_a?(List) &&
214
+ pattern.items.length == 2 &&
215
+ pattern.head.is_a?(Sym) &&
216
+ pattern.head.name == '='
217
+ end
218
+
219
+ def or_pattern?(pattern)
220
+ pattern.is_a?(List) && pattern.head.is_a?(Sym) && pattern.head.name == 'or'
221
+ end
222
+
223
+ def nil_allowing_pattern_name?(name)
224
+ name.length > 1 && (name.start_with?('?') || name.start_with?('_'))
225
+ end
226
+
227
+ def rest_pattern_marker?(items, index)
228
+ items[index].is_a?(Sym) && items[index].name == '&'
229
+ end
102
230
  end
103
231
  end
104
232
  end