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.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/Gemfile +10 -0
- data/README.md +58 -0
- data/Rakefile +10 -0
- data/bin/console +8 -0
- data/bin/setup +4 -0
- data/examples/accumulator.kap +16 -0
- data/examples/ackermann.kap +7 -0
- data/examples/anagram.kap +13 -0
- data/examples/binary-search.kap +16 -0
- data/examples/block-sort.kap +3 -0
- data/examples/calc.kap +10 -0
- data/examples/counter.kap +19 -0
- data/examples/describe.kap +9 -0
- data/examples/destructure.kap +4 -0
- data/examples/doto.kap +2 -0
- data/examples/egg-count.kap +10 -0
- data/examples/even-squares.kap +7 -0
- data/examples/exceptions.kap +14 -0
- data/examples/factorial.kap +8 -0
- data/examples/fib.kap +4 -0
- data/examples/fizzbuzz.kap +7 -0
- data/examples/gcd.kap +5 -0
- data/examples/greet.kap +2 -0
- data/examples/hashfn.kap +4 -0
- data/examples/kwargs.kap +1 -0
- data/examples/leap-year.kap +5 -0
- data/examples/match.kap +9 -0
- data/examples/min-max.kap +11 -0
- data/examples/module-header.kap +6 -0
- data/examples/palindrome.kap +8 -0
- data/examples/pangram.kap +9 -0
- data/examples/pcall.kap +9 -0
- data/examples/pipeline.kap +6 -0
- data/examples/points.kap +9 -0
- data/examples/primes.kap +8 -0
- data/examples/raindrops.kap +13 -0
- data/examples/record.kap +6 -0
- data/examples/regex.kap +9 -0
- data/examples/ruby-eval.kap +1 -0
- data/examples/safe-lookup.kap +6 -0
- data/examples/scopes.kap +18 -0
- data/examples/shapes.kap +9 -0
- data/examples/squares.kap +3 -0
- data/examples/stack.kap +19 -0
- data/examples/sum.kap +3 -0
- data/examples/tset.kap +4 -0
- data/examples/two-sum.kap +17 -0
- data/exe/kapfmt +6 -0
- data/exe/kapusta +6 -0
- data/kapfmt +4 -0
- data/kapusta.gemspec +25 -0
- data/lib/kapusta/ast.rb +76 -0
- data/lib/kapusta/cli.rb +61 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +178 -0
- data/lib/kapusta/compiler/emitter/collections.rb +245 -0
- data/lib/kapusta/compiler/emitter/control_flow.rb +168 -0
- data/lib/kapusta/compiler/emitter/expressions.rb +107 -0
- data/lib/kapusta/compiler/emitter/interop.rb +277 -0
- data/lib/kapusta/compiler/emitter/patterns.rb +105 -0
- data/lib/kapusta/compiler/emitter/support.rb +169 -0
- data/lib/kapusta/compiler/emitter.rb +45 -0
- data/lib/kapusta/compiler/normalizer.rb +122 -0
- data/lib/kapusta/compiler/runtime.rb +583 -0
- data/lib/kapusta/compiler.rb +47 -0
- data/lib/kapusta/env.rb +42 -0
- data/lib/kapusta/formatter.rb +685 -0
- data/lib/kapusta/reader.rb +215 -0
- data/lib/kapusta/support.rb +7 -0
- data/lib/kapusta/version.rb +5 -0
- data/lib/kapusta.rb +30 -0
- data/spec/cli_spec.rb +77 -0
- data/spec/examples_spec.rb +258 -0
- data/spec/formatter_spec.rb +176 -0
- data/spec/spec_helper.rb +12 -0
- 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
|