kapusta 0.2.4 → 0.4.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.
@@ -11,174 +11,257 @@ module Kapusta
11
11
  return ['', env] if pattern.name == '_'
12
12
 
13
13
  ruby_name = define_local(env, pattern)
14
- ["#{ruby_name} = #{value_code}", env]
14
+ return ["#{ruby_name} = #{value_code}", env]
15
+ end
16
+
17
+ native = try_emit_native_pattern_bind(pattern, value_code, env)
18
+ return native if native
19
+
20
+ emit_error!("destructure pattern this compiler cannot translate: #{pattern.inspect}")
21
+ end
22
+
23
+ def try_emit_native_pattern_bind(pattern, value_code, env)
24
+ case pattern
25
+ when Vec
26
+ try_emit_native_vec_bind(pattern, value_code, env)
27
+ when HashLit
28
+ try_emit_native_hash_bind(pattern, value_code, env)
29
+ end
30
+ rescue PatternNotTranslatable
31
+ nil
32
+ end
33
+
34
+ def try_emit_native_vec_bind(pattern, value_code, env)
35
+ parts = []
36
+ deferred = []
37
+ current_env = env
38
+ items = pattern.items
39
+ i = 0
40
+ while i < items.length
41
+ if items[i].is_a?(Sym) && items[i].name == '&'
42
+ sub = items[i + 1]
43
+ raise PatternNotTranslatable unless sub.is_a?(Sym)
44
+
45
+ parts << native_rest_target(sub, current_env)
46
+ i += 2
47
+ else
48
+ code, current_env, follow_up = native_destructure_target(items[i], current_env, allow_follow_up: true)
49
+ parts << code
50
+ deferred << follow_up if follow_up
51
+ i += 1
52
+ end
53
+ end
54
+ if deferred.empty?
55
+ ["#{parts.join(', ')} = #{value_code}", current_env]
15
56
  else
16
- bindings_var = temp('bindings')
17
- current_env = env
18
- lines = [
19
- "#{bindings_var} = #{runtime_call(:destructure, emit_pattern(pattern), value_code)}"
20
- ]
21
- pattern_names(pattern).each do |name|
22
- ruby_name = define_local(current_env, name)
23
- lines << "#{ruby_name} = #{bindings_var}.fetch(#{name.inspect})"
57
+ arr_var = simple_expression?(value_code) ? value_code : temp('array')
58
+ lines = []
59
+ lines << "#{arr_var} = #{value_code}" unless arr_var == value_code
60
+ lines << "#{parts.join(', ')} = #{arr_var}"
61
+ deferred.each do |follow_up|
62
+ sub_code, current_env = follow_up.call(current_env)
63
+ lines << sub_code
24
64
  end
25
65
  [lines.join("\n"), current_env]
26
66
  end
27
67
  end
28
68
 
29
- def emit_bindings_from_match(binding_names, bindings_var, env)
30
- current_env = env
69
+ def try_emit_native_hash_bind(pattern, value_code, env)
70
+ pairs = pattern.pairs
71
+ raise PatternNotTranslatable if pairs.empty?
72
+
73
+ temp_var = simple_expression?(value_code) ? value_code : temp('hash')
31
74
  lines = []
32
- binding_names.each do |name|
33
- ruby_name = define_local(current_env, name)
34
- lines << "#{ruby_name} = #{bindings_var}.fetch(#{name.inspect})"
75
+ lines << "#{temp_var} = #{value_code}" unless temp_var == value_code
76
+ current_env = env
77
+ pairs.each do |key, sub|
78
+ access = "#{temp_var}[#{key.inspect}]"
79
+ if sub.is_a?(Sym)
80
+ raise PatternNotTranslatable if sub.name == '_'
81
+
82
+ ruby_name = define_local(current_env, sub.name)
83
+ lines << "#{ruby_name} = #{access}"
84
+ else
85
+ sub_code, current_env = try_emit_native_pattern_bind(sub, access, current_env) ||
86
+ raise(PatternNotTranslatable)
87
+ lines << sub_code
88
+ end
35
89
  end
36
90
  [lines.join("\n"), current_env]
37
91
  end
38
92
 
39
- def pattern_match_plan(pattern, env, mode:, allow_pins:)
40
- state = { bound_names: {}, binding_names: [] }
93
+ def native_destructure_target(pattern, env, allow_follow_up: false)
94
+ case pattern
95
+ when Sym
96
+ return ['_', env, nil] if pattern.name == '_'
97
+
98
+ ruby_name = define_local(env, pattern.name)
99
+ [ruby_name, env, nil]
100
+ when Vec
101
+ inner = []
102
+ current = env
103
+ deferred = []
104
+ items = pattern.items
105
+ i = 0
106
+ while i < items.length
107
+ if items[i].is_a?(Sym) && items[i].name == '&'
108
+ inner << native_rest_target(items[i + 1], current)
109
+ i += 2
110
+ else
111
+ code, current, follow_up = native_destructure_target(items[i], current)
112
+ inner << code
113
+ deferred << follow_up if follow_up
114
+ i += 1
115
+ end
116
+ end
117
+ raise PatternNotTranslatable unless deferred.empty?
118
+
119
+ ["(#{inner.join(', ')})", current, nil]
120
+ when HashLit
121
+ raise PatternNotTranslatable unless allow_follow_up
122
+
123
+ slot = temp('slot')
124
+ follow_up = lambda do |outer_env|
125
+ try_emit_native_hash_bind(pattern, slot, outer_env) || raise(PatternNotTranslatable)
126
+ end
127
+ [slot, env, follow_up]
128
+ else
129
+ raise PatternNotTranslatable
130
+ end
131
+ end
132
+
133
+ def native_rest_target(sym, env)
134
+ raise PatternNotTranslatable unless sym.is_a?(Sym)
135
+
136
+ return '*' if sym.name == '_'
137
+
138
+ "*#{define_local(env, sym.name)}"
139
+ end
140
+
141
+ class PatternNotTranslatable < StandardError; end
142
+
143
+ def native_pattern_plan(pattern, env, mode:, allow_pins:)
144
+ state = { bound_names: {}, binding_names: [], guards: [] }
145
+ ruby_pattern = compile_native_pattern(pattern, env, mode:, allow_pins:, state:)
41
146
  {
42
- pattern: emit_match_pattern(pattern, env, mode:, allow_pins:, state:),
147
+ pattern: ruby_pattern,
148
+ guards: state[:guards],
43
149
  bindings: state[:binding_names]
44
150
  }
151
+ rescue PatternNotTranslatable
152
+ nil
45
153
  end
46
154
 
47
- def emit_match_pattern(pattern, env, mode:, allow_pins:, state:)
155
+ def compile_native_pattern(pattern, env, mode:, allow_pins:, state:)
48
156
  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(', ')}]]"
157
+ when Sym then compile_native_symbol(pattern, env, mode:, state:)
158
+ when Vec then compile_native_sequence(pattern.items, env, mode:, allow_pins:, state:)
159
+ when HashLit then compile_native_hash(pattern, env, mode:, allow_pins:, state:)
58
160
  when List
59
161
  if pin_pattern?(pattern)
60
- emit_pin_match_pattern(pattern, env, mode:, allow_pins:)
162
+ compile_native_pin(pattern, env, mode:, allow_pins:)
61
163
  elsif or_pattern?(pattern)
62
- emit_or_match_pattern(pattern, env, mode:, allow_pins:, state:)
63
- elsif where_pattern?(pattern)
64
- emit_error!('`where` is only valid as a case/match clause head')
164
+ compile_native_or(pattern, env, mode:, allow_pins:, state:)
65
165
  else
66
- emit_sequence_match_pattern(pattern.items, env, mode:, allow_pins:, state:)
166
+ compile_native_sequence(pattern.items, env, mode:, allow_pins:, state:)
67
167
  end
68
- when nil
69
- '[:lit, nil]'
70
- when Symbol, String, Numeric, true, false
71
- "[:lit, #{pattern.inspect}]"
72
- else
73
- emit_error!("bad pattern: #{pattern.inspect}")
168
+ when nil then 'nil'
169
+ when Symbol, String, Numeric, true, false then pattern.inspect
170
+ else raise PatternNotTranslatable
74
171
  end
75
172
  end
76
173
 
77
- def emit_symbol_match_pattern(pattern, env, mode:, state:)
174
+ def compile_native_symbol(pattern, env, mode:, state:)
78
175
  name = pattern.name
176
+ return '_' if name == '_'
79
177
 
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
178
+ if nil_allowing_pattern_name?(name)
179
+ raise PatternNotTranslatable if state[:bound_names].key?(name)
89
180
 
90
- def emit_named_match_pattern(name, env, mode:, state:, allow_nil:, prefer_pin:)
91
- binding = prefer_pin && mode == :match ? env.lookup_if_defined(name) : nil
92
- if state[:bound_names].key?(name)
93
- "[:ref, #{name.inspect}]"
94
- elsif binding
95
- "[:pin, #{binding_value_code(binding)}]"
96
- else
97
181
  state[:bound_names][name] = true
98
182
  state[:binding_names] << name
99
- "[:bind, #{name.inspect}, #{allow_nil}]"
183
+ sanitize_local(name)
184
+ else
185
+ binding = mode == :match ? env.lookup_if_defined(name) : nil
186
+ if state[:bound_names].key?(name)
187
+ raise PatternNotTranslatable
188
+ elsif binding
189
+ "^(#{binding_value_code(binding)})"
190
+ else
191
+ state[:bound_names][name] = true
192
+ state[:binding_names] << name
193
+ ruby = sanitize_local(name)
194
+ state[:guards] << "!#{ruby}.nil?"
195
+ ruby
196
+ end
100
197
  end
101
198
  end
102
199
 
103
- def emit_sequence_match_pattern(items, env, mode:, allow_pins:, state:)
200
+ def compile_native_sequence(items, env, mode:, allow_pins:, state:)
104
201
  parts = []
202
+ has_rest = false
105
203
  i = 0
106
204
  while i < items.length
107
205
  if rest_pattern_marker?(items, i)
108
- parts << "[:rest, #{emit_match_pattern(items[i + 1], env, mode:, allow_pins:, state:)}]"
206
+ has_rest = true
207
+ sub = items[i + 1]
208
+ raise PatternNotTranslatable unless sub.is_a?(Sym)
209
+
210
+ if sub.name == '_'
211
+ parts << '*'
212
+ else
213
+ state[:bound_names][sub.name] = true
214
+ state[:binding_names] << sub.name
215
+ parts << "*#{sanitize_local(sub.name)}"
216
+ end
109
217
  i += 2
110
218
  else
111
- parts << emit_match_pattern(items[i], env, mode:, allow_pins:, state:)
219
+ parts << compile_native_pattern(items[i], env, mode:, allow_pins:, state:)
112
220
  i += 1
113
221
  end
114
222
  end
115
- "[:vec, [#{parts.join(', ')}]]"
223
+ parts << '*' unless has_rest
224
+ "[#{parts.join(', ')}]"
225
+ end
226
+
227
+ def compile_native_hash(pattern, env, mode:, allow_pins:, state:)
228
+ pairs = pattern.pairs.map do |key, value|
229
+ raise PatternNotTranslatable unless key.is_a?(Symbol)
230
+
231
+ "#{key}: #{compile_native_pattern(value, env, mode:, allow_pins:, state:)}"
232
+ end
233
+ "{#{pairs.join(', ')}}"
116
234
  end
117
235
 
118
- def emit_pin_match_pattern(pattern, env, mode:, allow_pins:)
119
- emit_error!('pin patterns are only supported inside `case` guards') unless allow_pins && mode == :case
236
+ def compile_native_pin(pattern, env, mode:, allow_pins:)
237
+ raise PatternNotTranslatable unless allow_pins && mode == :case
120
238
 
121
239
  name_sym = pattern.items[1]
122
- emit_error!("bad pin pattern: #{pattern.inspect}") unless name_sym.is_a?(Sym)
240
+ raise PatternNotTranslatable unless name_sym.is_a?(Sym)
123
241
 
124
242
  binding = env.lookup_if_defined(name_sym.name)
125
- emit_error!("cannot pin undefined name: #{name_sym.name}") unless binding
243
+ raise PatternNotTranslatable unless binding
126
244
 
127
- "[:pin, #{binding_value_code(binding)}]"
245
+ "^(#{binding_value_code(binding)})"
128
246
  end
129
247
 
130
- def emit_or_match_pattern(pattern, env, mode:, allow_pins:, state:)
131
- initial_names = state[:binding_names].length
248
+ def compile_native_or(pattern, env, mode:, allow_pins:, state:)
132
249
  initial_bound = state[:bound_names].dup
133
- canonical_names = nil
250
+ initial_names = state[:binding_names].length
251
+ initial_guards = state[:guards].length
134
252
  variants = pattern.items[1..].map do |subpattern|
135
253
  alt_state = {
136
254
  bound_names: initial_bound.dup,
137
- binding_names: state[:binding_names].dup
255
+ binding_names: state[:binding_names].dup,
256
+ guards: state[:guards].dup
138
257
  }
139
- compiled = emit_match_pattern(subpattern, env, mode:, allow_pins:, state: alt_state)
140
- alt_names = alt_state[:binding_names][initial_names..]
141
- canonical_names ||= alt_names
142
- emit_error!('all `or` patterns must bind the same names') if canonical_names.sort != alt_names.sort
258
+ compiled = compile_native_pattern(subpattern, env, mode:, allow_pins:, state: alt_state)
259
+ raise PatternNotTranslatable if alt_state[:binding_names].length > initial_names
260
+ raise PatternNotTranslatable if alt_state[:guards].length > initial_guards
143
261
 
144
262
  compiled
145
263
  end
146
-
147
- canonical_names.each do |name|
148
- state[:bound_names][name] = true
149
- state[:binding_names] << name
150
- end
151
- "[:or, [#{variants.join(', ')}]]"
152
- end
153
-
154
- def emit_pattern(pattern)
155
- case pattern
156
- when Sym
157
- pattern.name == '_' ? '[:sym, "_"]' : "[:sym, #{pattern.name.inspect}]"
158
- when Vec
159
- parts = []
160
- items = pattern.items
161
- i = 0
162
- while i < items.length
163
- if items[i].is_a?(Sym) && items[i].name == '&'
164
- parts << "[:rest, #{emit_pattern(items[i + 1])}]"
165
- i += 2
166
- else
167
- parts << emit_pattern(items[i])
168
- i += 1
169
- end
170
- end
171
- "[:vec, [#{parts.join(', ')}]]"
172
- when HashLit
173
- pairs = pattern.pairs.map { |key, value| "[#{key.inspect}, #{emit_pattern(value)}]" }
174
- "[:hash, [#{pairs.join(', ')}]]"
175
- when nil
176
- '[:nil]'
177
- when Symbol, String, Numeric, true, false
178
- "[:lit, #{pattern.inspect}]"
179
- else
180
- emit_error!("bad pattern: #{pattern.inspect}")
181
- end
264
+ "(#{variants.join(' | ')})"
182
265
  end
183
266
 
184
267
  def pattern_names(pattern)
@@ -62,7 +62,7 @@ module Kapusta
62
62
  else
63
63
  name_sym, supers, = split_class_args(form.items[1..])
64
64
  body = emit_forms_with_headers(remaining_forms, env, :class, result: false)
65
- emit_direct_class_header(name_sym, supers, body) || emit_class_wrapper(name_sym, supers, env, body)
65
+ emit_direct_class_header(name_sym, supers, body, env) || emit_class_wrapper(name_sym, supers, env, body)
66
66
  end
67
67
  end
68
68
 
@@ -128,7 +128,7 @@ module Kapusta
128
128
  return [emit_for_statement(form.rest, env, current_scope), env]
129
129
  when 'each'
130
130
  code = emit_each_statement(form.rest, env, current_scope)
131
- return ["#{code}\nnil", env] if result_needed
131
+ return ["#{code}\nnil", env] if result_needed && current_scope != :toplevel
132
132
 
133
133
  return [code, env]
134
134
  end
@@ -219,19 +219,6 @@ module Kapusta
219
219
  source_name.is_a?(GeneratedSym)
220
220
  end
221
221
 
222
- def runtime_helper(name)
223
- helper = name.to_sym
224
- @runtime_helpers << helper unless @runtime_helpers.include?(helper)
225
- Runtime.helper_name(helper)
226
- end
227
-
228
- def runtime_call(name, *args)
229
- rendered_args = args.map do |arg|
230
- arg.is_a?(Array) ? "[#{arg.join(', ')}]" : arg || 'nil'
231
- end
232
- "#{runtime_helper(name)}(#{rendered_args.join(', ')})"
233
- end
234
-
235
222
  def method_binding?(binding)
236
223
  binding.is_a?(Env::MethodBinding)
237
224
  end
@@ -276,7 +263,8 @@ module Kapusta
276
263
 
277
264
  def sanitize_local(name)
278
265
  base = Kapusta.kebab_to_snake(name.respond_to?(:name) ? name.name : name)
279
- base = base.gsub('?', '_q').gsub('!', '_bang')
266
+ base = base.sub(/\A\?/, 'q_').gsub('?', '_q')
267
+ base = base.sub(/\A!/, 'bang_').gsub('!', '_bang')
280
268
  base = base.gsub(/[^a-zA-Z0-9_]/, '_')
281
269
  if base.empty? || base.match?(/\A\d/) || base.match?(/\A[A-Z]/) || self.class::RUBY_KEYWORDS.include?(base)
282
270
  base = "_#{base}"
@@ -27,14 +27,12 @@ module Kapusta
27
27
  def initialize(path:)
28
28
  @path = path
29
29
  @temp_index = 0
30
- @runtime_helpers = []
31
30
  end
32
31
 
33
32
  def emit_file(forms)
34
33
  env = Env.new
35
34
  body = emit_forms_with_headers(forms, env, :toplevel)
36
- helpers = Runtime.helper_source(@runtime_helpers)
37
- [helpers, body].reject(&:empty?).join("\n\n") << "\n"
35
+ "#{body}\n"
38
36
  end
39
37
  end
40
38
  end
@@ -0,0 +1,256 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kapusta
4
+ module Compiler
5
+ class MacroExpander
6
+ class Error < Kapusta::Error; end
7
+
8
+ @gensym_counter = 0
9
+
10
+ class << self
11
+ def fresh_gensym(prefix)
12
+ @gensym_counter += 1
13
+ GeneratedSym.new("#{prefix}_g#{@gensym_counter}", @gensym_counter)
14
+ end
15
+
16
+ def fresh_local_gensym(prefix)
17
+ @gensym_counter += 1
18
+ GeneratedSym.new("#{prefix}_local_#{@gensym_counter}", @gensym_counter)
19
+ end
20
+ end
21
+
22
+ def initialize
23
+ @macros = {}
24
+ end
25
+
26
+ def expand_all(forms)
27
+ forms.flat_map { |form| expand_top(form) }
28
+ end
29
+
30
+ private
31
+
32
+ def expand_top(form)
33
+ if form.is_a?(List) && form.head.is_a?(Sym)
34
+ case form.head.name
35
+ when 'macro'
36
+ register_macro_form(form.rest)
37
+ return []
38
+ when 'macros'
39
+ register_macros_form(form.rest)
40
+ return []
41
+ when 'import-macros'
42
+ raise Error, 'import-macros is not yet supported'
43
+ end
44
+ end
45
+ [expand(form)]
46
+ end
47
+
48
+ def expand(form)
49
+ case form
50
+ when List then expand_list(form)
51
+ when Vec then Vec.new(form.items.map { |item| expand(item) })
52
+ when HashLit
53
+ HashLit.new(form.entries.map do |entry|
54
+ entry.is_a?(Array) ? [expand(entry[0]), expand(entry[1])] : entry
55
+ end)
56
+ else
57
+ form
58
+ end
59
+ end
60
+
61
+ def expand_list(list)
62
+ return list if list.empty?
63
+
64
+ head = list.head
65
+ if head.is_a?(Sym) && !head.is_a?(AutoGensym)
66
+ name = head.name
67
+ case name
68
+ when 'macro'
69
+ register_macro_form(list.rest)
70
+ return List.new([Sym.new('do')])
71
+ when 'macros'
72
+ register_macros_form(list.rest)
73
+ return List.new([Sym.new('do')])
74
+ end
75
+
76
+ key = lookup_key(name)
77
+ if @macros.key?(key)
78
+ args = list.rest
79
+ result = invoke_macro(key, args)
80
+ return expand(result)
81
+ end
82
+ end
83
+
84
+ List.new(list.items.map { |item| expand(item) })
85
+ end
86
+
87
+ def lookup_key(name)
88
+ Kapusta.kebab_to_snake(name).to_sym
89
+ end
90
+
91
+ def register_macro_form(args)
92
+ name_sym, params, *body = args
93
+ raise Error, 'macro name must be a symbol' unless name_sym.is_a?(Sym)
94
+ raise Error, 'macro params must be a vector' unless params.is_a?(Vec)
95
+
96
+ register(name_sym.name, params, body)
97
+ end
98
+
99
+ def register_macros_form(args)
100
+ hash_lit = args[0]
101
+ raise Error, 'macros expects a hash literal' unless hash_lit.is_a?(HashLit)
102
+
103
+ hash_lit.pairs.each do |key, value|
104
+ raise Error, "macros entry value must be a fn form, got #{value.inspect}" unless fn_form?(value)
105
+
106
+ name = key.to_s
107
+ params = value.items[1]
108
+ body = value.items[2..]
109
+ raise Error, 'macros entry params must be a vector' unless params.is_a?(Vec)
110
+
111
+ register(name, params, body)
112
+ end
113
+ end
114
+
115
+ def fn_form?(value)
116
+ value.is_a?(List) && value.head.is_a?(Sym) && %w[fn lambda λ].include?(value.head.name)
117
+ end
118
+
119
+ def register(source_name, params, body)
120
+ proc_obj = compile_macro(source_name, params, body)
121
+ @macros[lookup_key(source_name)] = proc_obj
122
+ end
123
+
124
+ def compile_macro(name, params, body)
125
+ lowering = Lowering.new
126
+ lowered_body = body.map { |form| lowering.lower(form) }
127
+ gensym_locals = lowering.collected_gensyms
128
+
129
+ wrapped =
130
+ if gensym_locals.empty?
131
+ List.new([Sym.new('fn'), params, *lowered_body])
132
+ else
133
+ let_bindings = gensym_locals.flat_map do |gensym_sym, prefix|
134
+ [gensym_sym, List.new([Sym.new('quasi-gensym'), prefix])]
135
+ end
136
+ inner = lowered_body.length == 1 ? lowered_body.first : List.new([Sym.new('do'), *lowered_body])
137
+ List.new([Sym.new('fn'), params, List.new([Sym.new('let'), Vec.new(let_bindings), inner])])
138
+ end
139
+
140
+ ruby = Compiler.compile_forms([wrapped], path: "(macro #{name})")
141
+ TOPLEVEL_BINDING.eval(ruby, "(macro #{name})", 1)
142
+ end
143
+
144
+ def invoke_macro(key, args)
145
+ proc_obj = @macros[key]
146
+ proc_obj.call(*args)
147
+ end
148
+
149
+ class Lowering
150
+ def initialize
151
+ @gensyms = {}
152
+ end
153
+
154
+ def collected_gensyms
155
+ @gensyms.map { |prefix, sym| [sym, prefix] }
156
+ end
157
+
158
+ def lower(form)
159
+ case form
160
+ when Quasiquote then lower_quasi(form.form)
161
+ when Unquote, UnquoteSplice
162
+ raise Error, 'unquote outside quasiquote'
163
+ when AutoGensym
164
+ raise Error, "auto-gensym #{form.name}# outside quasiquote"
165
+ when List then List.new(form.items.map { |item| lower(item) })
166
+ when Vec then Vec.new(form.items.map { |item| lower(item) })
167
+ when HashLit
168
+ HashLit.new(form.entries.map do |entry|
169
+ entry.is_a?(Array) ? [lower(entry[0]), lower(entry[1])] : entry
170
+ end)
171
+ else
172
+ form
173
+ end
174
+ end
175
+
176
+ def lower_quasi(form)
177
+ case form
178
+ when AutoGensym then gensym_local_for(form.name)
179
+ when Sym then List.new([Sym.new('quasi-sym'), form.name])
180
+ when List then lower_quasi_list(form)
181
+ when Vec then lower_quasi_vec(form)
182
+ when HashLit then lower_quasi_hash(form)
183
+ when Unquote then lower(form.form)
184
+ when UnquoteSplice
185
+ raise Error, 'unquote-splice must appear inside a quoted list/vec'
186
+ when Quasiquote
187
+ raise Error, 'nested quasiquote is not supported'
188
+ else
189
+ form
190
+ end
191
+ end
192
+
193
+ def lower_quasi_list(list)
194
+ items = list.items
195
+ return List.new([Sym.new('quasi-list')]) if items.empty?
196
+
197
+ if (tail_expr = splice_tail(items))
198
+ head_items = items[0...-1].map { |item| lower_quasi(item) }
199
+ return List.new([Sym.new('quasi-list-tail'), Vec.new(head_items), tail_expr])
200
+ end
201
+
202
+ lowered_items = items.map { |item| lower_quasi_item(item) }
203
+ List.new([Sym.new('quasi-list'), *lowered_items])
204
+ end
205
+
206
+ def lower_quasi_vec(vec)
207
+ items = vec.items
208
+ if (tail_expr = splice_tail(items))
209
+ head_items = items[0...-1].map { |item| lower_quasi(item) }
210
+ return List.new([Sym.new('quasi-vec-tail'), Vec.new(head_items), tail_expr])
211
+ end
212
+
213
+ lowered_items = items.map { |item| lower_quasi_item(item) }
214
+ List.new([Sym.new('quasi-vec'), *lowered_items])
215
+ end
216
+
217
+ def lower_quasi_hash(hash)
218
+ parts = []
219
+ hash.entries.each do |entry|
220
+ next unless entry.is_a?(Array)
221
+
222
+ key, value = entry
223
+ parts << lower_quasi(key) << lower_quasi(value)
224
+ end
225
+ List.new([Sym.new('quasi-hash'), *parts])
226
+ end
227
+
228
+ def lower_quasi_item(item)
229
+ if item.is_a?(Unquote) && unpack_call?(item.form)
230
+ inner = lower(item.form.items[1])
231
+ List.new([Sym.new('.'), inner, 0])
232
+ else
233
+ lower_quasi(item)
234
+ end
235
+ end
236
+
237
+ def splice_tail(items)
238
+ last = items.last
239
+ return unless last
240
+ return lower(last.form) if last.is_a?(UnquoteSplice)
241
+ return lower(last.form.items[1]) if last.is_a?(Unquote) && unpack_call?(last.form)
242
+
243
+ nil
244
+ end
245
+
246
+ def unpack_call?(form)
247
+ form.is_a?(List) && form.head.is_a?(Sym) && form.head.name == 'unpack'
248
+ end
249
+
250
+ def gensym_local_for(prefix)
251
+ @gensyms[prefix] ||= MacroExpander.fresh_local_gensym(prefix)
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end