kapusta 0.1.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.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +10 -0
  4. data/README.md +58 -0
  5. data/Rakefile +10 -0
  6. data/bin/console +8 -0
  7. data/bin/setup +4 -0
  8. data/examples/accumulator.kap +16 -0
  9. data/examples/ackermann.kap +7 -0
  10. data/examples/anagram.kap +13 -0
  11. data/examples/binary-search.kap +16 -0
  12. data/examples/block-sort.kap +3 -0
  13. data/examples/calc.kap +10 -0
  14. data/examples/counter.kap +19 -0
  15. data/examples/describe.kap +9 -0
  16. data/examples/destructure.kap +4 -0
  17. data/examples/doto.kap +2 -0
  18. data/examples/egg-count.kap +10 -0
  19. data/examples/even-squares.kap +7 -0
  20. data/examples/exceptions.kap +14 -0
  21. data/examples/factorial.kap +8 -0
  22. data/examples/fib.kap +4 -0
  23. data/examples/fizzbuzz.kap +7 -0
  24. data/examples/gcd.kap +5 -0
  25. data/examples/greet.kap +2 -0
  26. data/examples/hashfn.kap +4 -0
  27. data/examples/kwargs.kap +1 -0
  28. data/examples/leap-year.kap +5 -0
  29. data/examples/match.kap +9 -0
  30. data/examples/min-max.kap +11 -0
  31. data/examples/module-header.kap +6 -0
  32. data/examples/palindrome.kap +8 -0
  33. data/examples/pangram.kap +9 -0
  34. data/examples/pcall.kap +9 -0
  35. data/examples/pipeline.kap +6 -0
  36. data/examples/points.kap +9 -0
  37. data/examples/primes.kap +8 -0
  38. data/examples/raindrops.kap +13 -0
  39. data/examples/record.kap +6 -0
  40. data/examples/regex.kap +9 -0
  41. data/examples/ruby-eval.kap +1 -0
  42. data/examples/safe-lookup.kap +6 -0
  43. data/examples/scopes.kap +18 -0
  44. data/examples/shapes.kap +9 -0
  45. data/examples/squares.kap +3 -0
  46. data/examples/stack.kap +19 -0
  47. data/examples/sum.kap +3 -0
  48. data/examples/tset.kap +4 -0
  49. data/examples/two-sum.kap +17 -0
  50. data/exe/kapfmt +6 -0
  51. data/exe/kapusta +6 -0
  52. data/kapfmt +4 -0
  53. data/kapusta.gemspec +25 -0
  54. data/lib/kapusta/ast.rb +76 -0
  55. data/lib/kapusta/cli.rb +61 -0
  56. data/lib/kapusta/compiler/emitter/bindings.rb +178 -0
  57. data/lib/kapusta/compiler/emitter/collections.rb +245 -0
  58. data/lib/kapusta/compiler/emitter/control_flow.rb +168 -0
  59. data/lib/kapusta/compiler/emitter/expressions.rb +107 -0
  60. data/lib/kapusta/compiler/emitter/interop.rb +277 -0
  61. data/lib/kapusta/compiler/emitter/patterns.rb +105 -0
  62. data/lib/kapusta/compiler/emitter/support.rb +169 -0
  63. data/lib/kapusta/compiler/emitter.rb +45 -0
  64. data/lib/kapusta/compiler/normalizer.rb +122 -0
  65. data/lib/kapusta/compiler/runtime.rb +583 -0
  66. data/lib/kapusta/compiler.rb +47 -0
  67. data/lib/kapusta/env.rb +42 -0
  68. data/lib/kapusta/formatter.rb +685 -0
  69. data/lib/kapusta/reader.rb +215 -0
  70. data/lib/kapusta/support.rb +7 -0
  71. data/lib/kapusta/version.rb +5 -0
  72. data/lib/kapusta.rb +30 -0
  73. data/spec/cli_spec.rb +77 -0
  74. data/spec/examples_spec.rb +258 -0
  75. data/spec/formatter_spec.rb +176 -0
  76. data/spec/spec_helper.rb +12 -0
  77. metadata +119 -0
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kapusta
4
+ module Compiler
5
+ module EmitterModules
6
+ module Expressions
7
+ private
8
+
9
+ def emit_expr(form, env, current_scope)
10
+ case form
11
+ when Sym then emit_sym(form, env)
12
+ when Vec then "[#{form.items.map { |item| emit_expr(item, env, current_scope) }.join(', ')}]"
13
+ when HashLit
14
+ "{#{form.pairs.map do |key, value|
15
+ "#{emit_hash_key(key, env, current_scope)} => #{emit_expr(value, env, current_scope)}"
16
+ end.join(', ')}}"
17
+ when List then emit_list(form, env, current_scope)
18
+ when String, Symbol, Numeric, true, false, nil then form.inspect
19
+ else
20
+ raise Error, "cannot emit form: #{form.inspect}"
21
+ end
22
+ end
23
+
24
+ def emit_hash_key(key, env, current_scope)
25
+ case key
26
+ when Sym, Vec, HashLit, List then emit_expr(key, env, current_scope)
27
+ else key.inspect
28
+ end
29
+ end
30
+
31
+ def emit_list(list, env, current_scope)
32
+ return 'nil' if list.empty?
33
+
34
+ head = list.head
35
+ args = list.rest
36
+ if head.is_a?(Sym)
37
+ return emit_special(head.name, args, env, current_scope) if special_form?(head.name)
38
+ return emit_multisym_call(head, args, env, current_scope) if head.dotted?
39
+ return emit_callable_call(env.lookup(head.name), args, env, current_scope) if env.defined?(head.name)
40
+
41
+ return emit_self_call(head.name, args, env, current_scope)
42
+ end
43
+
44
+ emit_callable_call(emit_expr(head, env, current_scope), args, env, current_scope)
45
+ end
46
+
47
+ def emit_special(name, args, env, current_scope)
48
+ case name
49
+ when 'fn', 'lambda', 'λ' then emit_fn(args, env, current_scope)
50
+ when 'let' then emit_let(args, env, current_scope)
51
+ when 'local', 'var' then emit_local_expr(args, env, current_scope)
52
+ when 'set' then emit_set_expr(args, env, current_scope)
53
+ when 'if' then emit_if(args, env, current_scope)
54
+ when 'case', 'match' then emit_case(args, env, current_scope)
55
+ when 'while' then emit_while(args, env, current_scope)
56
+ when 'for' then emit_for(args, env, current_scope)
57
+ when 'each' then emit_each(args, env, current_scope)
58
+ when 'do' then "begin\n#{indent(emit_sequence(args, env, current_scope,
59
+ allow_method_definitions: false).first)}\nend"
60
+ when 'values' then "[#{args.map { |arg| emit_expr(arg, env, current_scope) }.join(', ')}]"
61
+ when 'icollect' then emit_icollect(args, env, current_scope)
62
+ when 'collect' then emit_collect(args, env, current_scope)
63
+ when 'fcollect' then emit_fcollect(args, env, current_scope)
64
+ when 'accumulate' then emit_accumulate(args, env, current_scope)
65
+ when 'faccumulate' then emit_faccumulate(args, env, current_scope)
66
+ when 'hashfn' then emit_hashfn(args, env, current_scope)
67
+ when '.' then emit_lookup(args, env, current_scope)
68
+ when '?.' then emit_safe_lookup(args, env, current_scope)
69
+ when ':' then emit_colon(args, env, current_scope)
70
+ when '..' then runtime_call(:concat, args.map do |arg|
71
+ emit_expr(arg, env, current_scope)
72
+ end)
73
+ when 'length' then "(#{emit_expr(args[0], env, current_scope)}).length"
74
+ when 'require' then emit_require(args[0], env, current_scope)
75
+ when 'module' then emit_module_expr(args, env)
76
+ when 'class' then emit_class_expr(args, env)
77
+ when 'try' then emit_try(args, env, current_scope)
78
+ when 'raise' then emit_raise(args, env, current_scope)
79
+ when 'ivar' then runtime_call(:get_ivar, 'self', args[0].name.inspect)
80
+ 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 'ruby' then "Kernel.eval(#{emit_expr(args[0], env, current_scope)})"
83
+ when 'and' then emit_and(args, env, current_scope)
84
+ when 'or' then emit_or(args, env, current_scope)
85
+ when 'not' then "(!#{emit_expr(args[0], env, current_scope)})"
86
+ when '=' then emit_compare(args, env, current_scope, '==')
87
+ when 'not=' then "(!#{emit_special('=', args, env, current_scope)})"
88
+ when '<' then emit_compare(args, env, current_scope, '<')
89
+ when '<=' then emit_compare(args, env, current_scope, '<=')
90
+ when '>' then emit_compare(args, env, current_scope, '>')
91
+ when '>=' then emit_compare(args, env, current_scope, '>=')
92
+ when '+' then emit_reduce(args, env, current_scope, '0', :+)
93
+ when '-' then emit_minus(args, env, current_scope)
94
+ when '*' then emit_reduce(args, env, current_scope, '1', :*)
95
+ when '/' then emit_div(args, env, current_scope)
96
+ when '%' then args.map { |arg| parenthesize(emit_expr(arg, env, current_scope)) }.join(' % ')
97
+ when 'print' then runtime_call(:print_values, *args.map do |arg|
98
+ emit_expr(arg, env, current_scope)
99
+ end)
100
+ else
101
+ raise Error, "unknown special form: #{name}"
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,277 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kapusta
4
+ module Compiler
5
+ module EmitterModules
6
+ module Interop
7
+ private
8
+
9
+ def emit_lookup(args, env, current_scope)
10
+ object_code = emit_expr(args[0], env, current_scope)
11
+ keys = args[1..].map { |arg| emit_expr(arg, env, current_scope) }.join(', ')
12
+ runtime_call(:get_path, object_code, "[#{keys}]")
13
+ end
14
+
15
+ def emit_safe_lookup(args, env, current_scope)
16
+ object_code = emit_expr(args[0], env, current_scope)
17
+ keys = args[1..].map { |arg| emit_expr(arg, env, current_scope) }.join(', ')
18
+ runtime_call(:qget_path, object_code, "[#{keys}]")
19
+ end
20
+
21
+ def emit_colon(args, env, current_scope)
22
+ receiver = emit_expr(args[0], env, current_scope)
23
+ method_name = emit_method_name(args[1], env, current_scope)
24
+ positional, kwargs, block = split_call_args(args[2..], env, current_scope)
25
+ runtime_call(:send_call, receiver, method_name, positional, kwargs, block)
26
+ end
27
+
28
+ def emit_require(arg, env, current_scope)
29
+ path_code =
30
+ case arg
31
+ when Sym then arg.name.inspect
32
+ when Symbol then arg.to_s.inspect
33
+ when String then arg.inspect
34
+ else "(#{emit_expr(arg, env, current_scope)}).to_s"
35
+ end
36
+ "require #{path_code}"
37
+ end
38
+
39
+ def emit_module_expr(args, env)
40
+ emit_module_wrapper(args[0], emit_sequence(args[1..], env, :module, allow_method_definitions: true).first)
41
+ end
42
+
43
+ def emit_class_expr(args, env)
44
+ name_sym, supers, body_forms = split_class_args(args)
45
+ emit_class_wrapper(name_sym, supers, env,
46
+ emit_sequence(body_forms, env, :class, allow_method_definitions: true).first)
47
+ end
48
+
49
+ def emit_module_wrapper(name_sym, body)
50
+ 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
60
+ end
61
+
62
+ def emit_class_wrapper(name_sym, supers, env, body)
63
+ klass_var = temp('class')
64
+ super_code =
65
+ if supers.is_a?(Vec) && !supers.items.empty?
66
+ emit_expr(supers.items.first, env, :toplevel)
67
+ else
68
+ 'Object'
69
+ 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
79
+ end
80
+
81
+ def emit_try(args, env, current_scope)
82
+ catches = []
83
+ finally_bodies = []
84
+ args[1..].each do |clause|
85
+ head = clause.head
86
+ if head.is_a?(Sym) && head.name == 'catch'
87
+ rest = clause.items[1..]
88
+ if rest[0].is_a?(Sym) && (rest[0].name.match?(/\A[A-Z]/) || rest[0].dotted?)
89
+ klass_form = rest[0]
90
+ bind_sym = rest[1]
91
+ body = rest[2..]
92
+ else
93
+ klass_form = nil
94
+ bind_sym = rest[0]
95
+ body = rest[1..]
96
+ end
97
+ catches << [klass_form, bind_sym, body]
98
+ elsif head.is_a?(Sym) && head.name == 'finally'
99
+ finally_bodies << clause.items[1..]
100
+ end
101
+ end
102
+
103
+ lines = ['begin', indent(emit_expr(args[0], env, current_scope))]
104
+ catches.each do |klass_form, bind_sym, body|
105
+ rescue_env = env.child
106
+ rescue_name = temp(sanitize_local(bind_sym.name))
107
+ rescue_env.define(bind_sym.name, rescue_name)
108
+ body_code, = emit_sequence(body, rescue_env, current_scope, allow_method_definitions: false)
109
+ rescue_line =
110
+ if klass_form
111
+ "rescue #{emit_expr(klass_form, env, current_scope)} => #{rescue_name}"
112
+ else
113
+ "rescue StandardError => #{rescue_name}"
114
+ end
115
+ lines << rescue_line
116
+ lines << indent(body_code)
117
+ end
118
+ unless finally_bodies.empty?
119
+ ensure_code = finally_bodies.map do |body|
120
+ emit_sequence(body, env, current_scope, allow_method_definitions: false).first
121
+ end.join("\n")
122
+ lines << 'ensure'
123
+ lines << indent(ensure_code)
124
+ end
125
+ lines << 'end'
126
+ lines.join("\n")
127
+ end
128
+
129
+ def emit_raise(args, env, current_scope)
130
+ return 'Kernel.raise' if args.empty?
131
+
132
+ "Kernel.raise(#{args.map { |arg| emit_expr(arg, env, current_scope) }.join(', ')})"
133
+ end
134
+
135
+ def emit_and(args, env, current_scope)
136
+ return 'true' if args.empty?
137
+
138
+ args.map { |arg| parenthesize(emit_expr(arg, env, current_scope)) }.join(' && ')
139
+ end
140
+
141
+ def emit_or(args, env, current_scope)
142
+ return 'nil' if args.empty?
143
+
144
+ args.map { |arg| parenthesize(emit_expr(arg, env, current_scope)) }.join(' || ')
145
+ end
146
+
147
+ def emit_compare(args, env, current_scope, operator)
148
+ values = args.map { |arg| emit_expr(arg, env, current_scope) }
149
+ return 'true' if values.length <= 1
150
+
151
+ (0...(values.length - 1)).map do |i|
152
+ "#{parenthesize(values[i])} #{operator} #{parenthesize(values[i + 1])}"
153
+ end.join(' && ')
154
+ end
155
+
156
+ def emit_reduce(args, env, current_scope, empty_value, operator)
157
+ return empty_value if args.empty?
158
+
159
+ args.map { |arg| parenthesize(emit_expr(arg, env, current_scope)) }.join(" #{operator} ")
160
+ end
161
+
162
+ def emit_minus(args, env, current_scope)
163
+ values = args.map { |arg| emit_expr(arg, env, current_scope) }
164
+ return '0' if values.empty?
165
+ return "(-#{values[0]})" if values.length == 1
166
+
167
+ values.map { |value| parenthesize(value) }.join(' - ')
168
+ end
169
+
170
+ def emit_div(args, env, current_scope)
171
+ values = args.map { |arg| emit_expr(arg, env, current_scope) }
172
+ return "(1.0 / #{parenthesize(values[0])})" if values.length == 1
173
+
174
+ values.map { |value| parenthesize(value) }.join(' / ')
175
+ end
176
+
177
+ def emit_callable_call(callee_code, args, env, current_scope)
178
+ positional, kwargs, block = split_call_args(args, env, current_scope)
179
+ runtime_call(:call, callee_code, "[#{positional.join(', ')}]", kwargs, block)
180
+ end
181
+
182
+ def emit_multisym_call(head, args, env, current_scope)
183
+ base_code, segments = multisym_base(head.segments, env)
184
+ if segments.empty?
185
+ emit_callable_call(base_code, args, env, current_scope)
186
+ else
187
+ receiver =
188
+ if segments.length == 1
189
+ base_code
190
+ else
191
+ runtime_call(:method_path_value, base_code, segments[0...-1].inspect)
192
+ end
193
+ method_name = Kapusta.kebab_to_snake(segments.last).to_sym.inspect
194
+ positional, kwargs, block = split_call_args(args, env, current_scope)
195
+ runtime_call(:send_call, receiver, method_name, positional, kwargs, block)
196
+ end
197
+ end
198
+
199
+ def emit_self_call(name, args, env, current_scope)
200
+ positional, kwargs, block = split_call_args(args, env, current_scope)
201
+ method_name = Kapusta.kebab_to_snake(name).to_sym.inspect
202
+ runtime_call(:invoke_self, 'self', method_name, positional, kwargs, block)
203
+ end
204
+
205
+ def split_call_args(args, env, current_scope)
206
+ block = nil
207
+ remaining = args
208
+ if !remaining.empty? && block_form?(remaining.last)
209
+ block = emit_expr(remaining.last, env, current_scope)
210
+ remaining = remaining[0...-1]
211
+ end
212
+
213
+ if !remaining.empty? && remaining.last.is_a?(HashLit) && remaining.last.all_sym_keys?
214
+ kwargs = emit_expr(remaining.last, env, current_scope)
215
+ positional = remaining[0...-1].map { |arg| emit_expr(arg, env, current_scope) }
216
+ else
217
+ kwargs = nil
218
+ positional = remaining.map { |arg| emit_expr(arg, env, current_scope) }
219
+ end
220
+ [positional, kwargs, block]
221
+ end
222
+
223
+ def emit_method_name(form, env, current_scope)
224
+ if form.is_a?(Symbol)
225
+ form.inspect
226
+ elsif form.is_a?(String)
227
+ form.to_sym.inspect
228
+ else
229
+ value = emit_expr(form, env, current_scope)
230
+ "(#{value}.is_a?(Symbol) ? #{value} : #{value}.to_s.to_sym)"
231
+ end
232
+ end
233
+
234
+ def emit_sym(sym, env)
235
+ name = sym.name
236
+ return 'self' if name == 'self'
237
+ return 'Float::INFINITY' if name == 'math.huge'
238
+ return env.lookup(name) if env.defined?(name)
239
+ return emit_multisym_value(sym, env) if sym.dotted?
240
+ return 'ARGV' if name == 'ARGV'
241
+ return name if name.match?(/\A[A-Z]/)
242
+
243
+ raise Error, "undefined symbol: #{name}"
244
+ end
245
+
246
+ def emit_multisym_value(sym, env)
247
+ base_code, segments = multisym_base(sym.segments, env)
248
+ return base_code if segments.empty?
249
+
250
+ runtime_call(:method_path_value, base_code, segments.inspect)
251
+ end
252
+
253
+ def multisym_base(segments, env)
254
+ if segments[0] == 'self'
255
+ ['self', segments[1..]]
256
+ elsif env.defined?(segments[0])
257
+ [env.lookup(segments[0]), segments[1..]]
258
+ else
259
+ idx = 0
260
+ const_path = []
261
+ while idx < segments.length && segments[idx].match?(/\A[A-Z]/)
262
+ const_path << segments[idx]
263
+ idx += 1
264
+ end
265
+ raise Error, "bad multisym: #{segments.join('.')}" if const_path.empty?
266
+
267
+ [const_path.join('::'), segments[idx..]]
268
+ end
269
+ end
270
+
271
+ def parenthesize(code)
272
+ "(#{code})"
273
+ end
274
+ end
275
+ end
276
+ end
277
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kapusta
4
+ module Compiler
5
+ module EmitterModules
6
+ module Patterns
7
+ private
8
+
9
+ def emit_pattern_bind(pattern, value_code, env)
10
+ if pattern.is_a?(Sym)
11
+ return ['nil', env] if pattern.name == '_'
12
+
13
+ ruby_name = temp(sanitize_local(pattern.name))
14
+ env.define(pattern.name, ruby_name)
15
+ ["#{ruby_name} = #{value_code}", env]
16
+ else
17
+ bindings_var = temp('bindings')
18
+ current_env = env
19
+ lines = [
20
+ "#{bindings_var} = #{runtime_call(:destructure, emit_pattern(pattern), value_code)}"
21
+ ]
22
+ pattern_names(pattern).each do |name|
23
+ ruby_name = temp(sanitize_local(name))
24
+ current_env.define(name, ruby_name)
25
+ lines << "#{ruby_name} = #{bindings_var}.fetch(#{name.inspect})"
26
+ end
27
+ [lines.join("\n"), current_env]
28
+ end
29
+ end
30
+
31
+ def emit_bindings_from_match(pattern, bindings_var, env)
32
+ current_env = env
33
+ lines = []
34
+ pattern_names(pattern).each do |name|
35
+ ruby_name = temp(sanitize_local(name))
36
+ current_env.define(name, ruby_name)
37
+ lines << "#{ruby_name} = #{bindings_var}.fetch(#{name.inspect})"
38
+ end
39
+ [lines.join("\n"), current_env]
40
+ end
41
+
42
+ def emit_pattern(pattern)
43
+ case pattern
44
+ when Sym
45
+ pattern.name == '_' ? '[:sym, "_"]' : "[:sym, #{pattern.name.inspect}]"
46
+ when Vec
47
+ parts = []
48
+ items = pattern.items
49
+ i = 0
50
+ while i < items.length
51
+ if items[i].is_a?(Sym) && items[i].name == '&'
52
+ parts << "[:rest, #{emit_pattern(items[i + 1])}]"
53
+ i += 2
54
+ else
55
+ parts << emit_pattern(items[i])
56
+ i += 1
57
+ end
58
+ end
59
+ "[:vec, [#{parts.join(', ')}]]"
60
+ when HashLit
61
+ pairs = pattern.pairs.map { |key, value| "[#{key.inspect}, #{emit_pattern(value)}]" }
62
+ "[:hash, [#{pairs.join(', ')}]]"
63
+ when nil
64
+ '[:nil]'
65
+ when Symbol, String, Numeric, true, false
66
+ "[:lit, #{pattern.inspect}]"
67
+ else
68
+ raise Error, "bad pattern: #{pattern.inspect}"
69
+ end
70
+ end
71
+
72
+ def pattern_names(pattern)
73
+ case pattern
74
+ when Sym
75
+ pattern.name == '_' ? [] : [pattern.name]
76
+ when Vec
77
+ names = []
78
+ items = pattern.items
79
+ i = 0
80
+ while i < items.length
81
+ if items[i].is_a?(Sym) && items[i].name == '&'
82
+ names.concat(pattern_names(items[i + 1]))
83
+ i += 2
84
+ else
85
+ names.concat(pattern_names(items[i]))
86
+ i += 1
87
+ end
88
+ end
89
+ names
90
+ when HashLit
91
+ pattern.pairs.flat_map { |_key, value| pattern_names(value) }
92
+ when List
93
+ where_pattern?(pattern) ? pattern_names(pattern.items[1]) : []
94
+ else
95
+ []
96
+ end
97
+ end
98
+
99
+ def where_pattern?(pattern)
100
+ pattern.is_a?(List) && pattern.head.is_a?(Sym) && pattern.head.name == 'where'
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kapusta
4
+ module Compiler
5
+ module EmitterModules
6
+ module Support
7
+ private
8
+
9
+ def emit_forms_with_headers(forms, env, current_scope)
10
+ i = 0
11
+ codes = []
12
+ while i < forms.length
13
+ form = forms[i]
14
+ if bodyless_header?(form)
15
+ codes << emit_bodyless_header(form, forms[(i + 1)..], env, current_scope)
16
+ break
17
+ else
18
+ code, env = emit_form_in_sequence(form, env, current_scope, allow_method_definitions: true)
19
+ codes << code
20
+ i += 1
21
+ end
22
+ end
23
+ codes.join("\n")
24
+ end
25
+
26
+ def bodyless_header?(form)
27
+ return false unless form.is_a?(List) && !form.empty? && form.head.is_a?(Sym)
28
+
29
+ case form.head.name
30
+ when 'module'
31
+ body = form.items[2..] || []
32
+ return true if body.empty?
33
+
34
+ body.length == 1 && bodyless_header?(body[0])
35
+ when 'class'
36
+ _, _, body = split_class_args(form.items[1..])
37
+ body.empty?
38
+ else
39
+ false
40
+ end
41
+ end
42
+
43
+ def emit_bodyless_header(form, remaining_forms, env, _current_scope)
44
+ head = form.head.name
45
+ name_sym = form.items[1]
46
+
47
+ if head == 'module'
48
+ inner = form.items[2..] || []
49
+ body =
50
+ if inner.length == 1 && bodyless_header?(inner[0])
51
+ emit_bodyless_header(inner[0], remaining_forms, env, :module)
52
+ else
53
+ emit_forms_with_headers(remaining_forms, env, :module)
54
+ end
55
+ emit_module_wrapper(name_sym, body)
56
+ else
57
+ name_sym, supers, = split_class_args(form.items[1..])
58
+ body = emit_forms_with_headers(remaining_forms, env, :class)
59
+ emit_class_wrapper(name_sym, supers, env, body)
60
+ end
61
+ end
62
+
63
+ def emit_form_in_sequence(form, env, current_scope, allow_method_definitions:)
64
+ if allow_method_definitions && method_definition_form?(form) && %i[module class].include?(current_scope)
65
+ [emit_method_definition(form, env), env]
66
+ elsif named_function_form?(form)
67
+ emit_named_fn_assignment(form, env, current_scope)
68
+ elsif local_form?(form)
69
+ emit_local_form(form, env, current_scope)
70
+ elsif do_form?(form)
71
+ emit_do_form(form.rest, env, current_scope)
72
+ elsif set_new_local_form?(form, env)
73
+ emit_set_form(form, env, current_scope)
74
+ else
75
+ [emit_expr(form, env, current_scope), env]
76
+ end
77
+ end
78
+
79
+ def emit_do_form(forms, env, current_scope)
80
+ body, new_env = emit_sequence(forms, env, current_scope, allow_method_definitions: false)
81
+ ["begin\n#{indent(body)}\nend", new_env]
82
+ end
83
+
84
+ def emit_sequence(forms, env, current_scope, allow_method_definitions:)
85
+ current_env = env
86
+ codes = []
87
+ forms.each do |form|
88
+ code, current_env = emit_form_in_sequence(form, current_env, current_scope,
89
+ allow_method_definitions:)
90
+ codes << code
91
+ end
92
+ [codes.join("\n"), current_env]
93
+ end
94
+
95
+ def special_form?(name)
96
+ Compiler::SPECIAL_FORMS.include?(name)
97
+ end
98
+
99
+ def split_class_args(args)
100
+ name_sym = args[0]
101
+ if args[1].is_a?(Vec)
102
+ [name_sym, args[1], args[2..] || []]
103
+ else
104
+ [name_sym, nil, args[1..] || []]
105
+ end
106
+ end
107
+
108
+ def named_function_form?(form)
109
+ form.is_a?(List) && !form.empty? && form.head.is_a?(Sym) &&
110
+ %w[fn lambda λ].include?(form.head.name) && form.items[1].is_a?(Sym)
111
+ end
112
+
113
+ def method_definition_form?(form)
114
+ named_function_form?(form)
115
+ end
116
+
117
+ def block_form?(form)
118
+ form.is_a?(List) && form.head.is_a?(Sym) && %w[fn lambda λ hashfn].include?(form.head.name)
119
+ end
120
+
121
+ def local_form?(form)
122
+ form.is_a?(List) && form.head.is_a?(Sym) && %w[local var].include?(form.head.name)
123
+ end
124
+
125
+ def do_form?(form)
126
+ form.is_a?(List) && form.head.is_a?(Sym) && form.head.name == 'do'
127
+ end
128
+
129
+ def set_new_local_form?(form, env)
130
+ return false unless form.is_a?(List) && form.head.is_a?(Sym) && form.head.name == 'set'
131
+
132
+ target = form.items[1]
133
+ target.is_a?(Sym) && !target.dotted? && !env.defined?(target.name)
134
+ end
135
+
136
+ def indent(text, level = 1)
137
+ prefix = ' ' * level
138
+ text.lines.map { |line| line.strip.empty? ? line : "#{prefix}#{line}" }.join
139
+ end
140
+
141
+ def temp(prefix)
142
+ @temp_index += 1
143
+ "__kap_#{prefix}_#{@temp_index}"
144
+ end
145
+
146
+ def runtime_helper(name)
147
+ helper = name.to_sym
148
+ @runtime_helpers << helper unless @runtime_helpers.include?(helper)
149
+ Runtime.helper_name(helper)
150
+ end
151
+
152
+ def runtime_call(name, *args)
153
+ rendered_args = args.map do |arg|
154
+ arg.is_a?(Array) ? "[#{arg.join(', ')}]" : arg || 'nil'
155
+ end
156
+ "#{runtime_helper(name)}(#{rendered_args.join(', ')})"
157
+ end
158
+
159
+ def sanitize_local(name)
160
+ base = Kapusta.kebab_to_snake(name)
161
+ base = base.gsub('?', '_q').gsub('!', '_bang')
162
+ base = base.gsub(/[^a-zA-Z0-9_]/, '_')
163
+ base = "_#{base}" if base.empty? || base.match?(/\A\d/) || self.class::RUBY_KEYWORDS.include?(base)
164
+ base
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end