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.
- checksums.yaml +4 -4
- data/README.md +7 -2
- data/bin/check-all +19 -0
- data/bin/fennel-parity +157 -0
- data/examples/macros-dbg.kap +9 -0
- data/examples/macros-multi.kap +12 -0
- data/examples/macros-swap.kap +9 -0
- data/examples/macros-thrice-if.kap +18 -0
- data/examples/macros-unless.kap +7 -0
- data/examples/macros-when-let.kap +7 -0
- data/examples/manhattan-distance.kap +9 -0
- data/examples/packet-router.kap +2 -5
- data/examples/subtract-product-sum.kap +14 -0
- data/examples/tic-tac-toe.kap +4 -9
- data/examples/ugly-number.kap +22 -0
- data/lib/kapusta/ast.rb +42 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +18 -7
- data/lib/kapusta/compiler/emitter/collections.rb +69 -45
- data/lib/kapusta/compiler/emitter/control_flow.rb +39 -66
- data/lib/kapusta/compiler/emitter/expressions.rb +34 -0
- data/lib/kapusta/compiler/emitter/interop.rb +65 -72
- data/lib/kapusta/compiler/emitter/patterns.rb +192 -109
- data/lib/kapusta/compiler/emitter/support.rb +4 -16
- data/lib/kapusta/compiler/emitter.rb +1 -3
- data/lib/kapusta/compiler/macro_expander.rb +256 -0
- data/lib/kapusta/compiler/normalizer.rb +2 -2
- data/lib/kapusta/compiler.rb +8 -1
- data/lib/kapusta/formatter.rb +216 -87
- data/lib/kapusta/reader.rb +46 -10
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +28 -4
- data/spec/examples_spec.rb +59 -0
- data/spec/formatter_spec.rb +8 -10
- metadata +13 -2
- data/lib/kapusta/compiler/runtime.rb +0 -226
|
@@ -45,13 +45,13 @@ module Kapusta
|
|
|
45
45
|
emit_sequence_value_assignment(acc_var, body)
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
|
-
|
|
49
|
-
(-> do
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
end).call
|
|
54
|
-
|
|
48
|
+
[
|
|
49
|
+
'(-> do',
|
|
50
|
+
indent("#{acc_var} = #{emit_expr(bindings[1], env, current_scope)}"),
|
|
51
|
+
indent(iter_code),
|
|
52
|
+
indent(acc_var),
|
|
53
|
+
'end).call'
|
|
54
|
+
].join("\n")
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def emit_faccumulate(args, env, current_scope)
|
|
@@ -73,29 +73,43 @@ module Kapusta
|
|
|
73
73
|
current_scope:,
|
|
74
74
|
body_code: accumulating_body
|
|
75
75
|
)
|
|
76
|
-
|
|
77
|
-
(-> do
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
end).call
|
|
82
|
-
|
|
76
|
+
[
|
|
77
|
+
'(-> do',
|
|
78
|
+
indent("#{acc_var} = #{emit_expr(bindings[1], env, current_scope)}"),
|
|
79
|
+
indent(loop_code),
|
|
80
|
+
indent(acc_var),
|
|
81
|
+
'end).call'
|
|
82
|
+
].join("\n")
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
def emit_hashfn(args, env, current_scope)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
hash_env.define("$#{
|
|
86
|
+
if needs_explicit_args?(args[0])
|
|
87
|
+
args_var = temp('args')
|
|
88
|
+
hash_env = env.child
|
|
89
|
+
hash_env.define('$', "#{args_var}[0]")
|
|
90
|
+
(1..9).each { |i| hash_env.define("$#{i}", "#{args_var}[#{i - 1}]") }
|
|
91
|
+
hash_env.define('$...', args_var)
|
|
92
|
+
body_code = emit_expr(args[0], hash_env, current_scope)
|
|
93
|
+
["->(*#{args_var}) do", indent(body_code), 'end'].join("\n")
|
|
94
|
+
else
|
|
95
|
+
hash_env = env.child
|
|
96
|
+
hash_env.define('$', '_1')
|
|
97
|
+
(1..9).each { |i| hash_env.define("$#{i}", "_#{i}") }
|
|
98
|
+
body_code = emit_expr(args[0], hash_env, current_scope)
|
|
99
|
+
['proc do', indent(body_code), 'end'].join("\n")
|
|
91
100
|
end
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def needs_explicit_args?(form)
|
|
104
|
+
case form
|
|
105
|
+
when Sym then form.name == '$...'
|
|
106
|
+
when List, Vec then form.items.any? { |item| needs_explicit_args?(item) }
|
|
107
|
+
when HashLit
|
|
108
|
+
form.entries.any? do |entry|
|
|
109
|
+
entry.is_a?(Array) ? entry.any? { |item| needs_explicit_args?(item) } : needs_explicit_args?(entry)
|
|
97
110
|
end
|
|
98
|
-
|
|
111
|
+
else false
|
|
112
|
+
end
|
|
99
113
|
end
|
|
100
114
|
|
|
101
115
|
def emit_iteration(bindings_vec, env, current_scope)
|
|
@@ -108,11 +122,16 @@ module Kapusta
|
|
|
108
122
|
when 'ipairs'
|
|
109
123
|
body_env = env.child
|
|
110
124
|
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
125
|
+
coll_code = emit_expr(iter_expr.items[1], env, current_scope)
|
|
126
|
+
if ignored_pattern?(binding_pats[0])
|
|
127
|
+
bind_code = value_bind || ''
|
|
128
|
+
body_code = yield(body_env)
|
|
129
|
+
return iteration_block("#{coll_code}.each do |#{value_var}|", bind_code, body_code)
|
|
130
|
+
end
|
|
111
131
|
index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
|
|
112
132
|
bind_code = [index_bind, value_bind].compact.join("\n")
|
|
113
133
|
body_code = yield(body_env)
|
|
114
|
-
header = "#{
|
|
115
|
-
".each_with_index do |#{value_var}, #{index_var}|"
|
|
134
|
+
header = "#{coll_code}.each_with_index do |#{value_var}, #{index_var}|"
|
|
116
135
|
return iteration_block(header, bind_code, body_code)
|
|
117
136
|
when 'pairs'
|
|
118
137
|
body_env = env.child
|
|
@@ -141,6 +160,10 @@ module Kapusta
|
|
|
141
160
|
end
|
|
142
161
|
end
|
|
143
162
|
|
|
163
|
+
def ignored_pattern?(pattern)
|
|
164
|
+
pattern.is_a?(Sym) && !pattern.dotted? && pattern.name == '_'
|
|
165
|
+
end
|
|
166
|
+
|
|
144
167
|
def bind_iteration_param(pattern, fallback_name, env)
|
|
145
168
|
if pattern.is_a?(Sym) && !pattern.dotted?
|
|
146
169
|
ruby_name = pattern.name == '_' ? '_' : define_local(env, pattern.name)
|
|
@@ -168,35 +191,36 @@ module Kapusta
|
|
|
168
191
|
end
|
|
169
192
|
|
|
170
193
|
def emit_collection_result(result_var, initial_code, iter_code)
|
|
171
|
-
|
|
172
|
-
(-> do
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
end).call
|
|
177
|
-
|
|
194
|
+
[
|
|
195
|
+
'(-> do',
|
|
196
|
+
indent("#{result_var} = #{initial_code}"),
|
|
197
|
+
indent(iter_code),
|
|
198
|
+
indent(result_var),
|
|
199
|
+
'end).call'
|
|
200
|
+
].join("\n")
|
|
178
201
|
end
|
|
179
202
|
|
|
180
203
|
def emit_array_collection_step(result_var, body_code)
|
|
181
204
|
value_var = temp('value')
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
#{result_var} << #{value_var} unless #{value_var}.nil?
|
|
185
|
-
|
|
205
|
+
[
|
|
206
|
+
emit_sequence_value_assignment(value_var, body_code),
|
|
207
|
+
"#{result_var} << #{value_var} unless #{value_var}.nil?"
|
|
208
|
+
].join("\n")
|
|
186
209
|
end
|
|
187
210
|
|
|
188
211
|
def emit_hash_collection_step(result_var, body_code)
|
|
189
212
|
pair_var = temp('pair')
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if #{pair_var}.is_a?(Array) && #{pair_var}.length == 2 &&
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
213
|
+
[
|
|
214
|
+
emit_sequence_value_assignment(pair_var, body_code),
|
|
215
|
+
"if #{pair_var}.is_a?(Array) && #{pair_var}.length == 2 && " \
|
|
216
|
+
"!#{pair_var}[0].nil? && !#{pair_var}[1].nil?",
|
|
217
|
+
indent("#{result_var}[#{pair_var}[0]] = #{pair_var}[1]"),
|
|
218
|
+
'end'
|
|
219
|
+
].join("\n")
|
|
196
220
|
end
|
|
197
221
|
|
|
198
222
|
def emit_sequence_value_assignment(target_var, body_code)
|
|
199
|
-
return
|
|
223
|
+
return emit_assignment(target_var, body_code) unless body_code.include?("\n")
|
|
200
224
|
|
|
201
225
|
["#{target_var} = begin", indent(body_code), 'end'].join("\n")
|
|
202
226
|
end
|
|
@@ -16,6 +16,8 @@ module Kapusta
|
|
|
16
16
|
|
|
17
17
|
cond = emit_expr(args[0], env, current_scope)
|
|
18
18
|
truthy = emit_if_branch(args[1], env, current_scope)
|
|
19
|
+
return "#{truthy} if #{cond}" if args.length == 2 && !truthy.include?("\n") && !cond.include?("\n")
|
|
20
|
+
|
|
19
21
|
lines = ["if #{cond}", indent(truthy)]
|
|
20
22
|
append_else_lines(lines, args[2..], env, current_scope)
|
|
21
23
|
lines << 'end'
|
|
@@ -57,81 +59,52 @@ module Kapusta
|
|
|
57
59
|
|
|
58
60
|
def emit_case(args, env, current_scope, mode)
|
|
59
61
|
value_var = temp('case_value')
|
|
60
|
-
body =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
body = try_emit_native_case(value_var, args[1..], env, current_scope, mode)
|
|
63
|
+
emit_error!('case/match clauses use patterns this compiler cannot translate') unless body
|
|
64
|
+
[
|
|
65
|
+
'(-> do',
|
|
66
|
+
indent("#{value_var} = #{emit_expr(args[0], env, current_scope)}"),
|
|
67
|
+
indent(body),
|
|
68
|
+
'end).call'
|
|
69
|
+
].join("\n")
|
|
67
70
|
end
|
|
68
71
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
def try_emit_native_case(value_var, clauses, env, current_scope, mode)
|
|
73
|
+
arms = []
|
|
74
|
+
i = 0
|
|
75
|
+
while i < clauses.length
|
|
76
|
+
pattern = clauses[i]
|
|
77
|
+
body = clauses[i + 1]
|
|
78
|
+
inner, where_guards = if where_pattern?(pattern)
|
|
79
|
+
[pattern.items[1], pattern.items[2..]]
|
|
80
|
+
else
|
|
81
|
+
[pattern, []]
|
|
82
|
+
end
|
|
83
|
+
sub_patterns = or_pattern?(inner) ? inner.items[1..] : [inner]
|
|
84
|
+
sub_arms = sub_patterns.map do |sub|
|
|
85
|
+
try_native_arm(sub, body, where_guards, env, current_scope, mode)
|
|
86
|
+
end
|
|
87
|
+
return if sub_arms.any?(&:nil?)
|
|
77
88
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
|
|
81
|
-
else
|
|
82
|
-
emit_simple_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
|
|
89
|
+
arms.concat(sub_arms)
|
|
90
|
+
i += 2
|
|
83
91
|
end
|
|
92
|
+
arms << ['else', indent('nil')].join("\n")
|
|
93
|
+
["case #{value_var}", *arms, 'end'].join("\n")
|
|
84
94
|
end
|
|
85
95
|
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
arm_env = env.child
|
|
91
|
-
assign_code, arm_env = emit_bindings_from_match(plan[:bindings], bindings_var, arm_env)
|
|
92
|
-
body_code = emit_expr(body, arm_env, current_scope)
|
|
93
|
-
arm_body = [assign_code, body_code].reject(&:empty?).join("\n")
|
|
94
|
-
<<~RUBY.chomp
|
|
95
|
-
#{match_var} = #{runtime_call(:match_pattern, plan[:pattern], value_var)}
|
|
96
|
-
if #{match_var}[0]
|
|
97
|
-
#{bindings_var} = #{match_var}[1]
|
|
98
|
-
#{arm_body}
|
|
99
|
-
else
|
|
100
|
-
#{indent(else_code)}
|
|
101
|
-
end
|
|
102
|
-
RUBY
|
|
103
|
-
end
|
|
96
|
+
def try_native_arm(pattern, body, where_guards, env, current_scope, mode)
|
|
97
|
+
allow_pins = !where_guards.empty? && mode == :case
|
|
98
|
+
plan = native_pattern_plan(pattern, env, mode:, allow_pins:)
|
|
99
|
+
return unless plan
|
|
104
100
|
|
|
105
|
-
def emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
|
|
106
|
-
inner = pattern.items[1]
|
|
107
|
-
guards = pattern.items[2..]
|
|
108
|
-
match_var = temp('match')
|
|
109
|
-
bindings_var = temp('bindings')
|
|
110
|
-
plan = pattern_match_plan(inner, env, mode:, allow_pins: mode == :case)
|
|
111
101
|
arm_env = env.child
|
|
112
|
-
|
|
113
|
-
|
|
102
|
+
plan[:bindings].each { |name| arm_env.define(name, sanitize_local(name)) }
|
|
103
|
+
guard_codes = plan[:guards] +
|
|
104
|
+
where_guards.map { |g| emit_expr(g, arm_env, current_scope) }
|
|
105
|
+
guard_clause = guard_codes.empty? ? '' : " if #{guard_codes.join(' && ')}"
|
|
114
106
|
body_code = emit_expr(body, arm_env, current_scope)
|
|
115
|
-
|
|
116
|
-
<<~RUBY.chomp
|
|
117
|
-
#{match_var} = #{runtime_call(:match_pattern, plan[:pattern], value_var)}
|
|
118
|
-
if #{match_var}[0]
|
|
119
|
-
#{bindings_var} = #{match_var}[1]#{bindings_line}
|
|
120
|
-
if #{guard_code}
|
|
121
|
-
#{body_code}
|
|
122
|
-
else
|
|
123
|
-
#{indent(else_code, 2)}
|
|
124
|
-
end
|
|
125
|
-
else
|
|
126
|
-
#{indent(else_code)}
|
|
127
|
-
end
|
|
128
|
-
RUBY
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def emit_case_guards(guards, env, current_scope)
|
|
132
|
-
return 'true' if guards.empty?
|
|
133
|
-
|
|
134
|
-
guards.map { |guard| parenthesize(emit_expr(guard, env, current_scope)) }.join(' && ')
|
|
107
|
+
["in #{plan[:pattern]}#{guard_clause}", indent(body_code)].join("\n")
|
|
135
108
|
end
|
|
136
109
|
|
|
137
110
|
def emit_while(args, env, current_scope)
|
|
@@ -96,11 +96,45 @@ module Kapusta
|
|
|
96
96
|
when '/' then emit_div(args, env, current_scope)
|
|
97
97
|
when '%' then args.map { |arg| parenthesize(emit_expr(arg, env, current_scope)) }.join(' % ')
|
|
98
98
|
when 'print' then emit_print(args, env, current_scope)
|
|
99
|
+
when 'quasi-sym' then "Kapusta::Sym.new(#{emit_expr(args[0], env, current_scope)})"
|
|
100
|
+
when 'quasi-list' then "Kapusta::List.new([#{args.map { |a| emit_expr(a, env, current_scope) }.join(', ')}])"
|
|
101
|
+
when 'quasi-list-tail' then emit_quasi_list_tail(args, env, current_scope)
|
|
102
|
+
when 'quasi-vec' then "Kapusta::Vec.new([#{args.map { |a| emit_expr(a, env, current_scope) }.join(', ')}])"
|
|
103
|
+
when 'quasi-vec-tail' then emit_quasi_vec_tail(args, env, current_scope)
|
|
104
|
+
when 'quasi-hash' then emit_quasi_hash(args, env, current_scope)
|
|
105
|
+
when 'quasi-gensym' then emit_quasi_gensym(args[0], env, current_scope)
|
|
106
|
+
when 'macro', 'macros', 'import-macros'
|
|
107
|
+
emit_error!("#{name} must appear at the top level and is consumed by the macro expander")
|
|
99
108
|
else
|
|
100
109
|
emit_error!("unknown special form: #{name}")
|
|
101
110
|
end
|
|
102
111
|
end
|
|
103
112
|
|
|
113
|
+
def emit_quasi_list_tail(args, env, current_scope)
|
|
114
|
+
head_items = args[0]
|
|
115
|
+
tail_expr = emit_expr(args[1], env, current_scope)
|
|
116
|
+
head_code = head_items.items.map { |item| emit_expr(item, env, current_scope) }.join(', ')
|
|
117
|
+
"Kapusta::List.new([#{head_code}, *#{parenthesize(tail_expr)}])"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def emit_quasi_vec_tail(args, env, current_scope)
|
|
121
|
+
head_items = args[0]
|
|
122
|
+
tail_expr = emit_expr(args[1], env, current_scope)
|
|
123
|
+
head_code = head_items.items.map { |item| emit_expr(item, env, current_scope) }.join(', ')
|
|
124
|
+
"Kapusta::Vec.new([#{head_code}, *#{parenthesize(tail_expr)}])"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def emit_quasi_gensym(arg, env, current_scope)
|
|
128
|
+
"Kapusta::Compiler::MacroExpander.fresh_gensym(#{emit_expr(arg, env, current_scope)})"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def emit_quasi_hash(args, env, current_scope)
|
|
132
|
+
pairs = args.each_slice(2).map do |key, value|
|
|
133
|
+
"[#{emit_expr(key, env, current_scope)}, #{emit_expr(value, env, current_scope)}]"
|
|
134
|
+
end
|
|
135
|
+
"Kapusta::HashLit.new([#{pairs.join(', ')}])"
|
|
136
|
+
end
|
|
137
|
+
|
|
104
138
|
def emit_concat(args, env, current_scope)
|
|
105
139
|
return '""' if args.empty?
|
|
106
140
|
|
|
@@ -17,8 +17,9 @@ module Kapusta
|
|
|
17
17
|
|
|
18
18
|
def emit_safe_lookup(args, env, current_scope)
|
|
19
19
|
object_code = emit_expr(args[0], env, current_scope)
|
|
20
|
-
keys = args[1..].map { |arg| emit_expr(arg, env, current_scope) }
|
|
21
|
-
|
|
20
|
+
keys = args[1..].map { |arg| emit_expr(arg, env, current_scope) }
|
|
21
|
+
receiver = simple_expression?(object_code) ? object_code : parenthesize(object_code)
|
|
22
|
+
keys.reduce(receiver) { |acc, key| "#{acc}&.[](#{key})" }
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
BINARY_OPERATOR_METHODS = %w[<=> ** << >> & | ^ === =~].freeze
|
|
@@ -53,35 +54,27 @@ module Kapusta
|
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
def emit_require(arg, env, current_scope)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
when String then arg.inspect
|
|
61
|
-
else "(#{emit_expr(arg, env, current_scope)}).to_s"
|
|
62
|
-
end
|
|
63
|
-
if kapusta_require?(arg)
|
|
64
|
-
return [
|
|
65
|
-
'unless defined?(Kapusta)',
|
|
66
|
-
indent('require "kapusta"'),
|
|
67
|
-
'end',
|
|
68
|
-
"Kapusta.require(#{path_code}, relative_to: #{@path.inspect})"
|
|
69
|
-
].join("\n")
|
|
57
|
+
literal = require_path_literal(arg)
|
|
58
|
+
if literal&.match?(%r{\A\.\.?/})
|
|
59
|
+
cleaned = literal.delete_suffix('.kap').sub(%r{\A\./}, '')
|
|
60
|
+
return "require_relative #{cleaned.inspect}"
|
|
70
61
|
end
|
|
71
62
|
|
|
63
|
+
path_code =
|
|
64
|
+
if literal
|
|
65
|
+
literal.inspect
|
|
66
|
+
else
|
|
67
|
+
"(#{emit_expr(arg, env, current_scope)}).to_s"
|
|
68
|
+
end
|
|
72
69
|
"require #{path_code}"
|
|
73
70
|
end
|
|
74
71
|
|
|
75
|
-
def
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
end
|
|
82
|
-
return false unless path
|
|
83
|
-
|
|
84
|
-
path.end_with?('.kap') || path.start_with?('./', '../') || File.absolute_path?(path)
|
|
72
|
+
def require_path_literal(arg)
|
|
73
|
+
case arg
|
|
74
|
+
when Sym then arg.name
|
|
75
|
+
when Symbol then arg.to_s
|
|
76
|
+
when String then arg
|
|
77
|
+
end
|
|
85
78
|
end
|
|
86
79
|
|
|
87
80
|
def emit_module_expr(args, env)
|
|
@@ -97,65 +90,65 @@ module Kapusta
|
|
|
97
90
|
end
|
|
98
91
|
|
|
99
92
|
def emit_module_wrapper(name_sym, body)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
indent("#{mod_var}.module_eval do"),
|
|
105
|
-
indent(body, 2),
|
|
106
|
-
indent('end'),
|
|
107
|
-
indent(mod_var),
|
|
108
|
-
'end).call'
|
|
109
|
-
].join("\n")
|
|
93
|
+
segments = constant_segments(name_sym)
|
|
94
|
+
emit_error!("invalid module name: #{name_sym.name}") unless segments
|
|
95
|
+
inner = build_nested_module(segments, body)
|
|
96
|
+
['(-> do', indent(inner), indent(segments.join('::')), 'end).call'].join("\n")
|
|
110
97
|
end
|
|
111
98
|
|
|
112
99
|
def emit_direct_module_header(name_sym, body)
|
|
113
|
-
|
|
114
|
-
return unless
|
|
100
|
+
segments = constant_segments(name_sym)
|
|
101
|
+
return unless segments
|
|
115
102
|
|
|
116
|
-
[
|
|
117
|
-
"module #{const_name}",
|
|
118
|
-
indent(body),
|
|
119
|
-
'end',
|
|
120
|
-
const_name
|
|
121
|
-
].join("\n")
|
|
103
|
+
[build_nested_module(segments, body), segments.join('::')].join("\n")
|
|
122
104
|
end
|
|
123
105
|
|
|
124
106
|
def emit_class_wrapper(name_sym, supers, env, body)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
'Object'
|
|
131
|
-
end
|
|
132
|
-
[
|
|
133
|
-
'(-> do',
|
|
134
|
-
indent("#{klass_var} = #{runtime_call(:ensure_class, 'self', name_sym.name.inspect, super_code)}"),
|
|
135
|
-
indent("#{klass_var}.class_eval do"),
|
|
136
|
-
indent(body, 2),
|
|
137
|
-
indent('end'),
|
|
138
|
-
indent(klass_var),
|
|
139
|
-
'end).call'
|
|
140
|
-
].join("\n")
|
|
107
|
+
segments = constant_segments(name_sym)
|
|
108
|
+
emit_error!("invalid class name: #{name_sym.name}") unless segments
|
|
109
|
+
super_code = class_super_code(supers, env)
|
|
110
|
+
inner = build_nested_class(segments, super_code, body)
|
|
111
|
+
['(-> do', indent(inner), indent(segments.join('::')), 'end).call'].join("\n")
|
|
141
112
|
end
|
|
142
113
|
|
|
143
|
-
def emit_direct_class_header(name_sym, supers, body)
|
|
144
|
-
|
|
145
|
-
return unless
|
|
114
|
+
def emit_direct_class_header(name_sym, supers, body, env)
|
|
115
|
+
segments = constant_segments(name_sym)
|
|
116
|
+
return unless segments
|
|
146
117
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
indent(body),
|
|
150
|
-
'end',
|
|
151
|
-
const_name
|
|
152
|
-
].join("\n")
|
|
118
|
+
super_code = class_super_code(supers, env)
|
|
119
|
+
[build_nested_class(segments, super_code, body), segments.join('::')].join("\n")
|
|
153
120
|
end
|
|
154
121
|
|
|
155
|
-
def
|
|
156
|
-
return unless name_sym.is_a?(Sym)
|
|
122
|
+
def constant_segments(name_sym)
|
|
123
|
+
return unless name_sym.is_a?(Sym)
|
|
157
124
|
|
|
158
|
-
name_sym.name
|
|
125
|
+
segments = name_sym.dotted? ? name_sym.segments : [name_sym.name]
|
|
126
|
+
return unless segments.all? { |s| s.match?(/\A[A-Z]\w*\z/) }
|
|
127
|
+
|
|
128
|
+
segments
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def class_super_code(supers, env)
|
|
132
|
+
return unless supers.is_a?(Vec) && !supers.items.empty?
|
|
133
|
+
|
|
134
|
+
emit_expr(supers.items.first, env, :toplevel)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def build_nested_module(segments, body)
|
|
138
|
+
inner = ["module #{segments.last}", indent(body), 'end'].join("\n")
|
|
139
|
+
wrap_in_modules(segments[0...-1], inner)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def build_nested_class(segments, super_code, body)
|
|
143
|
+
header = super_code ? "class #{segments.last} < #{super_code}" : "class #{segments.last}"
|
|
144
|
+
inner = [header, indent(body), 'end'].join("\n")
|
|
145
|
+
wrap_in_modules(segments[0...-1], inner)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def wrap_in_modules(parents, inner)
|
|
149
|
+
parents.reverse.reduce(inner) do |acc, mod_name|
|
|
150
|
+
["module #{mod_name}", indent(acc), 'end'].join("\n")
|
|
151
|
+
end
|
|
159
152
|
end
|
|
160
153
|
|
|
161
154
|
def emit_try(args, env, current_scope)
|