kapusta 0.2.4 → 0.3.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 +4 -4
- data/README.md +6 -0
- data/examples/manhattan-distance.kap +9 -0
- data/examples/subtract-product-sum.kap +14 -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/interop.rb +65 -72
- data/lib/kapusta/compiler/emitter/patterns.rb +196 -109
- data/lib/kapusta/compiler/emitter/support.rb +2 -15
- data/lib/kapusta/compiler/emitter.rb +1 -3
- data/lib/kapusta/compiler/normalizer.rb +2 -2
- data/lib/kapusta/compiler.rb +0 -1
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +28 -4
- data/spec/examples_spec.rb +8 -0
- metadata +3 -2
- data/lib/kapusta/compiler/runtime.rb +0 -226
|
@@ -11,174 +11,261 @@ 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
|
-
|
|
17
|
-
|
|
18
|
-
lines =
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
+
bind_name = sub.name.start_with?('?') ? sub.name.delete_prefix('?') : sub.name
|
|
83
|
+
ruby_name = define_local(current_env, bind_name)
|
|
84
|
+
lines << "#{ruby_name} = #{access}"
|
|
85
|
+
else
|
|
86
|
+
sub_code, current_env = try_emit_native_pattern_bind(sub, access, current_env) ||
|
|
87
|
+
raise(PatternNotTranslatable)
|
|
88
|
+
lines << sub_code
|
|
89
|
+
end
|
|
35
90
|
end
|
|
36
91
|
[lines.join("\n"), current_env]
|
|
37
92
|
end
|
|
38
93
|
|
|
39
|
-
def
|
|
40
|
-
|
|
94
|
+
def native_destructure_target(pattern, env, allow_follow_up: false)
|
|
95
|
+
case pattern
|
|
96
|
+
when Sym
|
|
97
|
+
return ['_', env, nil] if pattern.name == '_'
|
|
98
|
+
|
|
99
|
+
bind_name = pattern.name.start_with?('?') ? pattern.name.delete_prefix('?') : pattern.name
|
|
100
|
+
ruby_name = define_local(env, bind_name)
|
|
101
|
+
[ruby_name, env, nil]
|
|
102
|
+
when Vec
|
|
103
|
+
inner = []
|
|
104
|
+
current = env
|
|
105
|
+
deferred = []
|
|
106
|
+
items = pattern.items
|
|
107
|
+
i = 0
|
|
108
|
+
while i < items.length
|
|
109
|
+
if items[i].is_a?(Sym) && items[i].name == '&'
|
|
110
|
+
inner << native_rest_target(items[i + 1], current)
|
|
111
|
+
i += 2
|
|
112
|
+
else
|
|
113
|
+
code, current, follow_up = native_destructure_target(items[i], current)
|
|
114
|
+
inner << code
|
|
115
|
+
deferred << follow_up if follow_up
|
|
116
|
+
i += 1
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
raise PatternNotTranslatable unless deferred.empty?
|
|
120
|
+
|
|
121
|
+
["(#{inner.join(', ')})", current, nil]
|
|
122
|
+
when HashLit
|
|
123
|
+
raise PatternNotTranslatable unless allow_follow_up
|
|
124
|
+
|
|
125
|
+
slot = temp('slot')
|
|
126
|
+
follow_up = lambda do |outer_env|
|
|
127
|
+
try_emit_native_hash_bind(pattern, slot, outer_env) || raise(PatternNotTranslatable)
|
|
128
|
+
end
|
|
129
|
+
[slot, env, follow_up]
|
|
130
|
+
else
|
|
131
|
+
raise PatternNotTranslatable
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def native_rest_target(sym, env)
|
|
136
|
+
raise PatternNotTranslatable unless sym.is_a?(Sym)
|
|
137
|
+
|
|
138
|
+
return '*' if sym.name == '_'
|
|
139
|
+
|
|
140
|
+
bind_name = sym.name.start_with?('?') ? sym.name.delete_prefix('?') : sym.name
|
|
141
|
+
"*#{define_local(env, bind_name)}"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
class PatternNotTranslatable < StandardError; end
|
|
145
|
+
|
|
146
|
+
def native_pattern_plan(pattern, env, mode:, allow_pins:)
|
|
147
|
+
state = { bound_names: {}, binding_names: [], guards: [] }
|
|
148
|
+
ruby_pattern = compile_native_pattern(pattern, env, mode:, allow_pins:, state:)
|
|
41
149
|
{
|
|
42
|
-
pattern:
|
|
150
|
+
pattern: ruby_pattern,
|
|
151
|
+
guards: state[:guards],
|
|
43
152
|
bindings: state[:binding_names]
|
|
44
153
|
}
|
|
154
|
+
rescue PatternNotTranslatable
|
|
155
|
+
nil
|
|
45
156
|
end
|
|
46
157
|
|
|
47
|
-
def
|
|
158
|
+
def compile_native_pattern(pattern, env, mode:, allow_pins:, state:)
|
|
48
159
|
case pattern
|
|
49
|
-
when Sym
|
|
50
|
-
|
|
51
|
-
when
|
|
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(', ')}]]"
|
|
160
|
+
when Sym then compile_native_symbol(pattern, env, mode:, state:)
|
|
161
|
+
when Vec then compile_native_sequence(pattern.items, env, mode:, allow_pins:, state:)
|
|
162
|
+
when HashLit then compile_native_hash(pattern, env, mode:, allow_pins:, state:)
|
|
58
163
|
when List
|
|
59
164
|
if pin_pattern?(pattern)
|
|
60
|
-
|
|
165
|
+
compile_native_pin(pattern, env, mode:, allow_pins:)
|
|
61
166
|
elsif or_pattern?(pattern)
|
|
62
|
-
|
|
63
|
-
elsif where_pattern?(pattern)
|
|
64
|
-
emit_error!('`where` is only valid as a case/match clause head')
|
|
167
|
+
compile_native_or(pattern, env, mode:, allow_pins:, state:)
|
|
65
168
|
else
|
|
66
|
-
|
|
169
|
+
compile_native_sequence(pattern.items, env, mode:, allow_pins:, state:)
|
|
67
170
|
end
|
|
68
|
-
when nil
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"[:lit, #{pattern.inspect}]"
|
|
72
|
-
else
|
|
73
|
-
emit_error!("bad pattern: #{pattern.inspect}")
|
|
171
|
+
when nil then 'nil'
|
|
172
|
+
when Symbol, String, Numeric, true, false then pattern.inspect
|
|
173
|
+
else raise PatternNotTranslatable
|
|
74
174
|
end
|
|
75
175
|
end
|
|
76
176
|
|
|
77
|
-
def
|
|
177
|
+
def compile_native_symbol(pattern, env, mode:, state:)
|
|
78
178
|
name = pattern.name
|
|
179
|
+
return '_' if name == '_'
|
|
79
180
|
|
|
80
|
-
if name
|
|
81
|
-
'[:wild]'
|
|
82
|
-
elsif nil_allowing_pattern_name?(name)
|
|
181
|
+
if nil_allowing_pattern_name?(name)
|
|
83
182
|
bind_name = name.start_with?('?') ? name.delete_prefix('?') : name
|
|
84
|
-
|
|
85
|
-
else
|
|
86
|
-
emit_named_match_pattern(name, env, mode:, state:, allow_nil: false, prefer_pin: true)
|
|
87
|
-
end
|
|
88
|
-
end
|
|
183
|
+
raise PatternNotTranslatable if state[:bound_names].key?(bind_name)
|
|
89
184
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
"[:ref, #{name.inspect}]"
|
|
94
|
-
elsif binding
|
|
95
|
-
"[:pin, #{binding_value_code(binding)}]"
|
|
185
|
+
state[:bound_names][bind_name] = true
|
|
186
|
+
state[:binding_names] << bind_name
|
|
187
|
+
sanitize_local(bind_name)
|
|
96
188
|
else
|
|
97
|
-
|
|
98
|
-
state[:
|
|
99
|
-
|
|
189
|
+
binding = mode == :match ? env.lookup_if_defined(name) : nil
|
|
190
|
+
if state[:bound_names].key?(name)
|
|
191
|
+
raise PatternNotTranslatable
|
|
192
|
+
elsif binding
|
|
193
|
+
"^(#{binding_value_code(binding)})"
|
|
194
|
+
else
|
|
195
|
+
state[:bound_names][name] = true
|
|
196
|
+
state[:binding_names] << name
|
|
197
|
+
ruby = sanitize_local(name)
|
|
198
|
+
state[:guards] << "!#{ruby}.nil?"
|
|
199
|
+
ruby
|
|
200
|
+
end
|
|
100
201
|
end
|
|
101
202
|
end
|
|
102
203
|
|
|
103
|
-
def
|
|
204
|
+
def compile_native_sequence(items, env, mode:, allow_pins:, state:)
|
|
104
205
|
parts = []
|
|
206
|
+
has_rest = false
|
|
105
207
|
i = 0
|
|
106
208
|
while i < items.length
|
|
107
209
|
if rest_pattern_marker?(items, i)
|
|
108
|
-
|
|
210
|
+
has_rest = true
|
|
211
|
+
sub = items[i + 1]
|
|
212
|
+
raise PatternNotTranslatable unless sub.is_a?(Sym)
|
|
213
|
+
|
|
214
|
+
if sub.name == '_'
|
|
215
|
+
parts << '*'
|
|
216
|
+
else
|
|
217
|
+
state[:bound_names][sub.name] = true
|
|
218
|
+
state[:binding_names] << sub.name
|
|
219
|
+
parts << "*#{sanitize_local(sub.name)}"
|
|
220
|
+
end
|
|
109
221
|
i += 2
|
|
110
222
|
else
|
|
111
|
-
parts <<
|
|
223
|
+
parts << compile_native_pattern(items[i], env, mode:, allow_pins:, state:)
|
|
112
224
|
i += 1
|
|
113
225
|
end
|
|
114
226
|
end
|
|
115
|
-
|
|
227
|
+
parts << '*' unless has_rest
|
|
228
|
+
"[#{parts.join(', ')}]"
|
|
116
229
|
end
|
|
117
230
|
|
|
118
|
-
def
|
|
119
|
-
|
|
231
|
+
def compile_native_hash(pattern, env, mode:, allow_pins:, state:)
|
|
232
|
+
pairs = pattern.pairs.map do |key, value|
|
|
233
|
+
raise PatternNotTranslatable unless key.is_a?(Symbol)
|
|
234
|
+
|
|
235
|
+
"#{key}: #{compile_native_pattern(value, env, mode:, allow_pins:, state:)}"
|
|
236
|
+
end
|
|
237
|
+
"{#{pairs.join(', ')}}"
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def compile_native_pin(pattern, env, mode:, allow_pins:)
|
|
241
|
+
raise PatternNotTranslatable unless allow_pins && mode == :case
|
|
120
242
|
|
|
121
243
|
name_sym = pattern.items[1]
|
|
122
|
-
|
|
244
|
+
raise PatternNotTranslatable unless name_sym.is_a?(Sym)
|
|
123
245
|
|
|
124
246
|
binding = env.lookup_if_defined(name_sym.name)
|
|
125
|
-
|
|
247
|
+
raise PatternNotTranslatable unless binding
|
|
126
248
|
|
|
127
|
-
"
|
|
249
|
+
"^(#{binding_value_code(binding)})"
|
|
128
250
|
end
|
|
129
251
|
|
|
130
|
-
def
|
|
131
|
-
initial_names = state[:binding_names].length
|
|
252
|
+
def compile_native_or(pattern, env, mode:, allow_pins:, state:)
|
|
132
253
|
initial_bound = state[:bound_names].dup
|
|
133
|
-
|
|
254
|
+
initial_names = state[:binding_names].length
|
|
255
|
+
initial_guards = state[:guards].length
|
|
134
256
|
variants = pattern.items[1..].map do |subpattern|
|
|
135
257
|
alt_state = {
|
|
136
258
|
bound_names: initial_bound.dup,
|
|
137
|
-
binding_names: state[:binding_names].dup
|
|
259
|
+
binding_names: state[:binding_names].dup,
|
|
260
|
+
guards: state[:guards].dup
|
|
138
261
|
}
|
|
139
|
-
compiled =
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
emit_error!('all `or` patterns must bind the same names') if canonical_names.sort != alt_names.sort
|
|
262
|
+
compiled = compile_native_pattern(subpattern, env, mode:, allow_pins:, state: alt_state)
|
|
263
|
+
raise PatternNotTranslatable if alt_state[:binding_names].length > initial_names
|
|
264
|
+
raise PatternNotTranslatable if alt_state[:guards].length > initial_guards
|
|
143
265
|
|
|
144
266
|
compiled
|
|
145
267
|
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
|
|
268
|
+
"(#{variants.join(' | ')})"
|
|
182
269
|
end
|
|
183
270
|
|
|
184
271
|
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
|
|
@@ -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
|
-
|
|
37
|
-
[helpers, body].reject(&:empty?).join("\n\n") << "\n"
|
|
35
|
+
"#{body}\n"
|
|
38
36
|
end
|
|
39
37
|
end
|
|
40
38
|
end
|
|
@@ -121,12 +121,12 @@ module Kapusta
|
|
|
121
121
|
end
|
|
122
122
|
|
|
123
123
|
def thread_temp
|
|
124
|
-
gensym('
|
|
124
|
+
gensym('thread')
|
|
125
125
|
end
|
|
126
126
|
|
|
127
127
|
def doto(forms)
|
|
128
128
|
value = forms.first
|
|
129
|
-
temp = gensym('
|
|
129
|
+
temp = gensym('doto')
|
|
130
130
|
body = forms[1..].map do |form|
|
|
131
131
|
if form.is_a?(List)
|
|
132
132
|
List.new([form.items[0], temp, *form.items[1..]])
|
data/lib/kapusta/compiler.rb
CHANGED
data/lib/kapusta/version.rb
CHANGED
data/lib/kapusta.rb
CHANGED
|
@@ -12,10 +12,12 @@ module Kapusta
|
|
|
12
12
|
@loaded_kapusta_features = {}
|
|
13
13
|
|
|
14
14
|
def self.eval(source, path: '(eval)', **_opts)
|
|
15
|
+
install!
|
|
15
16
|
Compiler.run(source, path:)
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def self.dofile(path, **_opts)
|
|
20
|
+
install!
|
|
19
21
|
source = File.read(path)
|
|
20
22
|
self.eval(source, path:)
|
|
21
23
|
end
|
|
@@ -25,6 +27,7 @@ module Kapusta
|
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def self.require(feature, relative_to: nil)
|
|
30
|
+
install!
|
|
28
31
|
feature = feature.to_s
|
|
29
32
|
local_path = resolve_require_path(feature, relative_to:)
|
|
30
33
|
|
|
@@ -35,12 +38,33 @@ module Kapusta
|
|
|
35
38
|
end
|
|
36
39
|
|
|
37
40
|
def self.install!
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
return if @installed
|
|
42
|
+
|
|
43
|
+
@installed = true
|
|
44
|
+
Kernel.module_eval do
|
|
45
|
+
alias_method :__kapusta_original_require_relative, :require_relative
|
|
46
|
+
private :__kapusta_original_require_relative
|
|
47
|
+
|
|
48
|
+
def require_relative(path)
|
|
49
|
+
kap_path = Kapusta.send(:resolve_kap_relative, path, caller_locations(1, 1).first)
|
|
50
|
+
return Kapusta.send(:require_kapusta_file, kap_path) if kap_path
|
|
51
|
+
|
|
52
|
+
__kapusta_original_require_relative(path)
|
|
53
|
+
end
|
|
41
54
|
end
|
|
42
55
|
end
|
|
43
56
|
|
|
57
|
+
def self.resolve_kap_relative(path, location)
|
|
58
|
+
return unless path.is_a?(String) && location
|
|
59
|
+
|
|
60
|
+
base_file = location.absolute_path || location.path
|
|
61
|
+
return unless base_file
|
|
62
|
+
|
|
63
|
+
full = File.expand_path(path, File.dirname(File.expand_path(base_file)))
|
|
64
|
+
candidates = full.end_with?('.kap') ? [full] : ["#{full}.kap"]
|
|
65
|
+
candidates.find { |c| File.file?(c) }
|
|
66
|
+
end
|
|
67
|
+
|
|
44
68
|
def self.resolve_require_path(feature, relative_to:)
|
|
45
69
|
return unless local_feature?(feature)
|
|
46
70
|
|
|
@@ -88,5 +112,5 @@ module Kapusta
|
|
|
88
112
|
end
|
|
89
113
|
|
|
90
114
|
private_class_method :resolve_require_path, :local_feature?, :require_base_dir,
|
|
91
|
-
:existing_feature_path, :require_kapusta_file
|
|
115
|
+
:existing_feature_path, :require_kapusta_file, :resolve_kap_relative
|
|
92
116
|
end
|
data/spec/examples_spec.rb
CHANGED
|
@@ -484,6 +484,10 @@ RSpec.describe 'examples' do
|
|
|
484
484
|
expect(run_example('majority-element.kap')).to eq("3\n2\n1\n")
|
|
485
485
|
end
|
|
486
486
|
|
|
487
|
+
it 'manhattan-distance.kap' do
|
|
488
|
+
expect(run_example('manhattan-distance.kap')).to eq("14\n")
|
|
489
|
+
end
|
|
490
|
+
|
|
487
491
|
it 'plus-one.kap' do
|
|
488
492
|
expect(run_example('plus-one.kap')).to eq(<<~OUT)
|
|
489
493
|
[1, 2, 4]
|
|
@@ -492,4 +496,8 @@ RSpec.describe 'examples' do
|
|
|
492
496
|
[1, 0, 0]
|
|
493
497
|
OUT
|
|
494
498
|
end
|
|
499
|
+
|
|
500
|
+
it 'subtract-product-sum.kap' do
|
|
501
|
+
expect(run_example('subtract-product-sum.kap')).to eq("15\n21\n0\n")
|
|
502
|
+
end
|
|
495
503
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kapusta
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Evgenii Morozov
|
|
@@ -57,6 +57,7 @@ files:
|
|
|
57
57
|
- examples/leap-year.kap
|
|
58
58
|
- examples/length-of-last-word.kap
|
|
59
59
|
- examples/majority-element.kap
|
|
60
|
+
- examples/manhattan-distance.kap
|
|
60
61
|
- examples/match.kap
|
|
61
62
|
- examples/maximum-subarray.kap
|
|
62
63
|
- examples/min-max.kap
|
|
@@ -84,6 +85,7 @@ files:
|
|
|
84
85
|
- examples/single-number.kap
|
|
85
86
|
- examples/squares.kap
|
|
86
87
|
- examples/stack.kap
|
|
88
|
+
- examples/subtract-product-sum.kap
|
|
87
89
|
- examples/sum.kap
|
|
88
90
|
- examples/threading.kap
|
|
89
91
|
- examples/tic-tac-toe.kap
|
|
@@ -112,7 +114,6 @@ files:
|
|
|
112
114
|
- lib/kapusta/compiler/emitter/patterns.rb
|
|
113
115
|
- lib/kapusta/compiler/emitter/support.rb
|
|
114
116
|
- lib/kapusta/compiler/normalizer.rb
|
|
115
|
-
- lib/kapusta/compiler/runtime.rb
|
|
116
117
|
- lib/kapusta/env.rb
|
|
117
118
|
- lib/kapusta/error.rb
|
|
118
119
|
- lib/kapusta/formatter.rb
|