kapusta 0.1.2 → 0.1.4
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 +1 -0
- data/examples/anonymous-greeter.kap +4 -0
- data/examples/binary-to-decimal.kap +7 -0
- data/examples/contains-duplicate.kap +7 -0
- data/examples/or-patterns.kap +8 -0
- data/examples/packet-router.kap +26 -0
- data/examples/tic-tac-toe.kap +18 -0
- data/examples/underscore-patterns.kap +14 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +87 -39
- data/lib/kapusta/compiler/emitter/collections.rb +61 -92
- data/lib/kapusta/compiler/emitter/control_flow.rb +84 -65
- data/lib/kapusta/compiler/emitter/expressions.rb +3 -2
- data/lib/kapusta/compiler/emitter/interop.rb +105 -22
- data/lib/kapusta/compiler/emitter/patterns.rb +136 -8
- data/lib/kapusta/compiler/emitter/support.rb +120 -16
- data/lib/kapusta/compiler/runtime.rb +68 -53
- data/lib/kapusta/compiler.rb +2 -1
- data/lib/kapusta/env.rb +8 -0
- data/lib/kapusta/error.rb +5 -0
- data/lib/kapusta/formatter.rb +10 -17
- data/lib/kapusta/reader.rb +13 -9
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +1 -0
- data/spec/cli_spec.rb +12 -21
- data/spec/examples_spec.rb +29 -1
- data/spec/formatter_spec.rb +20 -0
- metadata +10 -3
- data/kapfmt +0 -4
|
@@ -6,7 +6,7 @@ module Kapusta
|
|
|
6
6
|
module Support
|
|
7
7
|
private
|
|
8
8
|
|
|
9
|
-
def emit_forms_with_headers(forms, env, current_scope)
|
|
9
|
+
def emit_forms_with_headers(forms, env, current_scope, result: true)
|
|
10
10
|
i = 0
|
|
11
11
|
codes = []
|
|
12
12
|
while i < forms.length
|
|
@@ -15,7 +15,9 @@ module Kapusta
|
|
|
15
15
|
codes << emit_bodyless_header(form, forms[(i + 1)..], env, current_scope)
|
|
16
16
|
break
|
|
17
17
|
else
|
|
18
|
-
code, env = emit_form_in_sequence(form, env, current_scope,
|
|
18
|
+
code, env = emit_form_in_sequence(form, env, current_scope,
|
|
19
|
+
allow_method_definitions: true,
|
|
20
|
+
result_needed: result && i == forms.length - 1)
|
|
19
21
|
codes << code
|
|
20
22
|
i += 1
|
|
21
23
|
end
|
|
@@ -50,25 +52,29 @@ module Kapusta
|
|
|
50
52
|
if inner.length == 1 && bodyless_header?(inner[0])
|
|
51
53
|
emit_bodyless_header(inner[0], remaining_forms, env, :module)
|
|
52
54
|
else
|
|
53
|
-
emit_forms_with_headers(remaining_forms, env, :module)
|
|
55
|
+
emit_forms_with_headers(remaining_forms, env, :module, result: false)
|
|
54
56
|
end
|
|
55
|
-
emit_module_wrapper(name_sym, body)
|
|
57
|
+
emit_direct_module_header(name_sym, body) || emit_module_wrapper(name_sym, body)
|
|
56
58
|
else
|
|
57
59
|
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
|
+
body = emit_forms_with_headers(remaining_forms, env, :class, result: false)
|
|
61
|
+
emit_direct_class_header(name_sym, supers, body) || emit_class_wrapper(name_sym, supers, env, body)
|
|
60
62
|
end
|
|
61
63
|
end
|
|
62
64
|
|
|
63
|
-
def emit_form_in_sequence(form, env, current_scope, allow_method_definitions:)
|
|
65
|
+
def emit_form_in_sequence(form, env, current_scope, allow_method_definitions:, result_needed: true)
|
|
64
66
|
if allow_method_definitions && method_definition_form?(form) && %i[module class].include?(current_scope)
|
|
65
67
|
[emit_method_definition(form, env), env]
|
|
66
68
|
elsif named_function_form?(form)
|
|
67
69
|
emit_named_fn_assignment(form, env, current_scope)
|
|
68
70
|
elsif local_form?(form)
|
|
69
|
-
emit_local_form(form, env, current_scope)
|
|
71
|
+
code, env = emit_local_form(form, env, current_scope)
|
|
72
|
+
code = code.delete_suffix("\nnil") unless result_needed
|
|
73
|
+
[code, env]
|
|
70
74
|
elsif do_form?(form)
|
|
71
|
-
emit_do_form(form.rest, env, current_scope)
|
|
75
|
+
emit_do_form(form.rest, env, current_scope, result_needed:)
|
|
76
|
+
elsif sequence_statement_form?(form)
|
|
77
|
+
emit_sequence_statement_form(form, env, current_scope, result_needed:)
|
|
72
78
|
elsif set_new_local_form?(form, env)
|
|
73
79
|
emit_set_form(form, env, current_scope)
|
|
74
80
|
else
|
|
@@ -76,22 +82,51 @@ module Kapusta
|
|
|
76
82
|
end
|
|
77
83
|
end
|
|
78
84
|
|
|
79
|
-
def emit_do_form(forms, env, current_scope)
|
|
80
|
-
body, new_env = emit_sequence(forms, env, current_scope,
|
|
85
|
+
def emit_do_form(forms, env, current_scope, result_needed: true)
|
|
86
|
+
body, new_env = emit_sequence(forms, env, current_scope,
|
|
87
|
+
allow_method_definitions: false,
|
|
88
|
+
result: result_needed)
|
|
89
|
+
return [body, new_env] unless result_needed
|
|
90
|
+
|
|
81
91
|
["begin\n#{indent(body)}\nend", new_env]
|
|
82
92
|
end
|
|
83
93
|
|
|
84
|
-
def emit_sequence(forms, env, current_scope, allow_method_definitions:)
|
|
94
|
+
def emit_sequence(forms, env, current_scope, allow_method_definitions:, result: true)
|
|
85
95
|
current_env = env
|
|
86
96
|
codes = []
|
|
87
|
-
forms.
|
|
97
|
+
forms.each_with_index do |form, index|
|
|
88
98
|
code, current_env = emit_form_in_sequence(form, current_env, current_scope,
|
|
89
|
-
allow_method_definitions
|
|
99
|
+
allow_method_definitions:,
|
|
100
|
+
result_needed: result && index == forms.length - 1)
|
|
90
101
|
codes << code
|
|
91
102
|
end
|
|
92
103
|
[codes.join("\n"), current_env]
|
|
93
104
|
end
|
|
94
105
|
|
|
106
|
+
def sequence_statement_form?(form)
|
|
107
|
+
return false unless form.is_a?(List) && form.head.is_a?(Sym)
|
|
108
|
+
|
|
109
|
+
%w[let while for each].include?(form.head.name)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def emit_sequence_statement_form(form, env, current_scope, result_needed:)
|
|
113
|
+
case form.head.name
|
|
114
|
+
when 'let'
|
|
115
|
+
return [emit_let_statement(form.rest, env, current_scope), env] unless result_needed
|
|
116
|
+
when 'while'
|
|
117
|
+
return [emit_while_statement(form.rest, env, current_scope), env]
|
|
118
|
+
when 'for'
|
|
119
|
+
return [emit_for_statement(form.rest, env, current_scope), env]
|
|
120
|
+
when 'each'
|
|
121
|
+
code = emit_each_statement(form.rest, env, current_scope)
|
|
122
|
+
return ["#{code}\nnil", env] if result_needed
|
|
123
|
+
|
|
124
|
+
return [code, env]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
[emit_expr(form, env, current_scope), env]
|
|
128
|
+
end
|
|
129
|
+
|
|
95
130
|
def special_form?(name)
|
|
96
131
|
Compiler::SPECIAL_FORMS.include?(name)
|
|
97
132
|
end
|
|
@@ -140,7 +175,35 @@ module Kapusta
|
|
|
140
175
|
|
|
141
176
|
def temp(prefix)
|
|
142
177
|
@temp_index += 1
|
|
143
|
-
"
|
|
178
|
+
"kap_#{prefix}_#{@temp_index}"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def define_local(env, source_name, shadow: false)
|
|
182
|
+
ruby_name = local_name(source_name, env, shadow:)
|
|
183
|
+
env.define(source_name, ruby_name)
|
|
184
|
+
ruby_name
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def local_name(source_name, env, shadow:)
|
|
188
|
+
base = sanitize_local(source_name)
|
|
189
|
+
base = "user_#{base}" if reserved_generated_name?(base)
|
|
190
|
+
return base unless ruby_name_defined?(env, base, shadow:)
|
|
191
|
+
|
|
192
|
+
index = 2
|
|
193
|
+
loop do
|
|
194
|
+
candidate = "#{base}_#{index}"
|
|
195
|
+
return candidate unless ruby_name_defined?(env, candidate, shadow:)
|
|
196
|
+
|
|
197
|
+
index += 1
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def ruby_name_defined?(env, name, shadow:)
|
|
202
|
+
shadow ? env.local_ruby_name_defined?(name) : env.ruby_name_defined?(name)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def reserved_generated_name?(name)
|
|
206
|
+
name.start_with?('kap_', '__kap_')
|
|
144
207
|
end
|
|
145
208
|
|
|
146
209
|
def runtime_helper(name)
|
|
@@ -156,11 +219,52 @@ module Kapusta
|
|
|
156
219
|
"#{runtime_helper(name)}(#{rendered_args.join(', ')})"
|
|
157
220
|
end
|
|
158
221
|
|
|
222
|
+
def parse_counted_for_bindings(bindings, env, current_scope)
|
|
223
|
+
name_sym = bindings[0]
|
|
224
|
+
loop_env = env.child
|
|
225
|
+
ruby_name = define_local(loop_env, name_sym.name)
|
|
226
|
+
start_code = emit_expr(bindings[1], env, current_scope)
|
|
227
|
+
finish_code = emit_expr(bindings[2], env, current_scope)
|
|
228
|
+
step_code = '1'
|
|
229
|
+
until_form = nil
|
|
230
|
+
i = 3
|
|
231
|
+
while i < bindings.length
|
|
232
|
+
if bindings[i].is_a?(Sym) && bindings[i].name == '&until'
|
|
233
|
+
until_form = bindings[i + 1]
|
|
234
|
+
i += 2
|
|
235
|
+
else
|
|
236
|
+
step_code = emit_expr(bindings[i], env, current_scope)
|
|
237
|
+
i += 1
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
{ ruby_name:, loop_env:, start_code:, finish_code:, step_code:, until_form: }
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def emit_counted_loop(ruby_name:, start_code:, finish_code:, step_code:,
|
|
244
|
+
until_form:, loop_env:, current_scope:, body_code:)
|
|
245
|
+
finish_var = temp('finish')
|
|
246
|
+
step_var = temp('step')
|
|
247
|
+
cmp_var = temp('cmp')
|
|
248
|
+
until_code = until_form ? "break if #{emit_expr(until_form, loop_env, current_scope)}" : nil
|
|
249
|
+
body = [until_code, body_code, "#{ruby_name} += #{step_var}"].compact.reject(&:empty?).join("\n")
|
|
250
|
+
[
|
|
251
|
+
"#{ruby_name} = #{start_code}",
|
|
252
|
+
"#{finish_var} = #{finish_code}",
|
|
253
|
+
"#{step_var} = #{step_code}",
|
|
254
|
+
"#{cmp_var} = #{step_var} >= 0 ? :<= : :>=",
|
|
255
|
+
"while #{ruby_name}.public_send(#{cmp_var}, #{finish_var})",
|
|
256
|
+
indent(body),
|
|
257
|
+
'end'
|
|
258
|
+
].join("\n")
|
|
259
|
+
end
|
|
260
|
+
|
|
159
261
|
def sanitize_local(name)
|
|
160
262
|
base = Kapusta.kebab_to_snake(name)
|
|
161
263
|
base = base.gsub('?', '_q').gsub('!', '_bang')
|
|
162
264
|
base = base.gsub(/[^a-zA-Z0-9_]/, '_')
|
|
163
|
-
|
|
265
|
+
if base.empty? || base.match?(/\A\d/) || base.match?(/\A[A-Z]/) || self.class::RUBY_KEYWORDS.include?(base)
|
|
266
|
+
base = "_#{base}"
|
|
267
|
+
end
|
|
164
268
|
base
|
|
165
269
|
end
|
|
166
270
|
end
|
|
@@ -20,12 +20,12 @@ module Kapusta
|
|
|
20
20
|
|
|
21
21
|
HELPER_SOURCES = {
|
|
22
22
|
kebab_to_snake: <<~RUBY.chomp,
|
|
23
|
-
def
|
|
23
|
+
def kap_kebab_to_snake(name)
|
|
24
24
|
name.tr('-', '_')
|
|
25
25
|
end
|
|
26
26
|
RUBY
|
|
27
27
|
call: <<~'RUBY'.chomp,
|
|
28
|
-
def
|
|
28
|
+
def kap_call(callee, positional, kwargs = nil, block = nil)
|
|
29
29
|
raise "not callable: #{callee.inspect}" unless callee.respond_to?(:call)
|
|
30
30
|
|
|
31
31
|
if block
|
|
@@ -36,7 +36,7 @@ module Kapusta
|
|
|
36
36
|
end
|
|
37
37
|
RUBY
|
|
38
38
|
send_call: <<~RUBY.chomp,
|
|
39
|
-
def
|
|
39
|
+
def kap_send_call(receiver, method_name, positional, kwargs = nil, block = nil)
|
|
40
40
|
if block
|
|
41
41
|
if kwargs
|
|
42
42
|
receiver.public_send(method_name, *positional, **kwargs, &block)
|
|
@@ -51,7 +51,7 @@ module Kapusta
|
|
|
51
51
|
end
|
|
52
52
|
RUBY
|
|
53
53
|
invoke_self: <<~RUBY.chomp,
|
|
54
|
-
def
|
|
54
|
+
def kap_invoke_self(receiver, method_name, positional, kwargs = nil, block = nil)
|
|
55
55
|
if block
|
|
56
56
|
if kwargs
|
|
57
57
|
receiver.send(method_name, *positional, **kwargs, &block)
|
|
@@ -64,7 +64,7 @@ module Kapusta
|
|
|
64
64
|
end
|
|
65
65
|
RUBY
|
|
66
66
|
stringify: <<~'RUBY'.chomp,
|
|
67
|
-
def
|
|
67
|
+
def kap_stringify(value)
|
|
68
68
|
render = nil
|
|
69
69
|
render = lambda do |item|
|
|
70
70
|
case item
|
|
@@ -91,23 +91,23 @@ module Kapusta
|
|
|
91
91
|
end
|
|
92
92
|
RUBY
|
|
93
93
|
print_values: <<~'RUBY'.chomp,
|
|
94
|
-
def
|
|
95
|
-
$stdout.puts(values.map { |value|
|
|
94
|
+
def kap_print_values(*values)
|
|
95
|
+
$stdout.puts(values.map { |value| kap_stringify(value) }.join("\t"))
|
|
96
96
|
nil
|
|
97
97
|
end
|
|
98
98
|
RUBY
|
|
99
99
|
concat: <<~RUBY.chomp,
|
|
100
|
-
def
|
|
101
|
-
values.map { |value|
|
|
100
|
+
def kap_concat(values)
|
|
101
|
+
values.map { |value| kap_stringify(value) }.join
|
|
102
102
|
end
|
|
103
103
|
RUBY
|
|
104
104
|
get_path: <<~RUBY.chomp,
|
|
105
|
-
def
|
|
105
|
+
def kap_get_path(obj, keys)
|
|
106
106
|
keys.reduce(obj) { |acc, key| acc[key] }
|
|
107
107
|
end
|
|
108
108
|
RUBY
|
|
109
109
|
qget_path: <<~RUBY.chomp,
|
|
110
|
-
def
|
|
110
|
+
def kap_qget_path(obj, keys)
|
|
111
111
|
keys.each do |key|
|
|
112
112
|
return nil if obj.nil?
|
|
113
113
|
|
|
@@ -117,64 +117,64 @@ module Kapusta
|
|
|
117
117
|
end
|
|
118
118
|
RUBY
|
|
119
119
|
set_path: <<~RUBY.chomp,
|
|
120
|
-
def
|
|
120
|
+
def kap_set_path(obj, keys, value)
|
|
121
121
|
target = obj
|
|
122
122
|
keys[0...-1].each { |key| target = target[key] }
|
|
123
123
|
target[keys.last] = value
|
|
124
124
|
end
|
|
125
125
|
RUBY
|
|
126
126
|
method_path_value: <<~RUBY.chomp,
|
|
127
|
-
def
|
|
128
|
-
segments.reduce(base) { |obj, segment| obj.public_send(
|
|
127
|
+
def kap_method_path_value(base, segments)
|
|
128
|
+
segments.reduce(base) { |obj, segment| obj.public_send(kap_kebab_to_snake(segment).to_sym) }
|
|
129
129
|
end
|
|
130
130
|
RUBY
|
|
131
131
|
set_method_path: <<~'RUBY'.chomp,
|
|
132
|
-
def
|
|
132
|
+
def kap_set_method_path(base, segments, value)
|
|
133
133
|
target = base
|
|
134
134
|
segments[0...-1].each do |segment|
|
|
135
|
-
target = target.public_send(
|
|
135
|
+
target = target.public_send(kap_kebab_to_snake(segment).to_sym)
|
|
136
136
|
end
|
|
137
|
-
setter = "#{
|
|
137
|
+
setter = "#{kap_kebab_to_snake(segments.last)}="
|
|
138
138
|
target.public_send(setter.to_sym, value)
|
|
139
139
|
end
|
|
140
140
|
RUBY
|
|
141
141
|
current_class_scope: <<~RUBY.chomp,
|
|
142
|
-
def
|
|
142
|
+
def kap_current_class_scope(receiver)
|
|
143
143
|
receiver.is_a?(Module) ? receiver : receiver.class
|
|
144
144
|
end
|
|
145
145
|
RUBY
|
|
146
146
|
get_ivar: <<~'RUBY'.chomp,
|
|
147
|
-
def
|
|
148
|
-
receiver.instance_variable_get("@#{
|
|
147
|
+
def kap_get_ivar(receiver, name)
|
|
148
|
+
receiver.instance_variable_get("@#{kap_kebab_to_snake(name)}")
|
|
149
149
|
end
|
|
150
150
|
RUBY
|
|
151
151
|
set_ivar: <<~'RUBY'.chomp,
|
|
152
|
-
def
|
|
153
|
-
receiver.instance_variable_set("@#{
|
|
152
|
+
def kap_set_ivar(receiver, name, value)
|
|
153
|
+
receiver.instance_variable_set("@#{kap_kebab_to_snake(name)}", value)
|
|
154
154
|
end
|
|
155
155
|
RUBY
|
|
156
156
|
get_cvar: <<~'RUBY'.chomp,
|
|
157
|
-
def
|
|
158
|
-
|
|
157
|
+
def kap_get_cvar(receiver, name)
|
|
158
|
+
kap_current_class_scope(receiver).class_variable_get("@@#{kap_kebab_to_snake(name)}")
|
|
159
159
|
end
|
|
160
160
|
RUBY
|
|
161
161
|
set_cvar: <<~'RUBY'.chomp,
|
|
162
|
-
def
|
|
163
|
-
|
|
162
|
+
def kap_set_cvar(receiver, name, value)
|
|
163
|
+
kap_current_class_scope(receiver).class_variable_set("@@#{kap_kebab_to_snake(name)}", value)
|
|
164
164
|
end
|
|
165
165
|
RUBY
|
|
166
166
|
get_gvar: <<~'RUBY'.chomp,
|
|
167
|
-
def
|
|
168
|
-
Kernel.eval("$#{
|
|
167
|
+
def kap_get_gvar(name)
|
|
168
|
+
Kernel.eval("$#{kap_kebab_to_snake(name)}", binding, __FILE__, __LINE__)
|
|
169
169
|
end
|
|
170
170
|
RUBY
|
|
171
171
|
set_gvar: <<~'RUBY'.chomp,
|
|
172
|
-
def
|
|
173
|
-
Kernel.eval("$#{
|
|
172
|
+
def kap_set_gvar(name, value)
|
|
173
|
+
Kernel.eval("$#{kap_kebab_to_snake(name)} = value", binding, __FILE__, __LINE__)
|
|
174
174
|
end
|
|
175
175
|
RUBY
|
|
176
176
|
ensure_module: <<~RUBY.chomp,
|
|
177
|
-
def
|
|
177
|
+
def kap_ensure_module(holder, path)
|
|
178
178
|
segments = path.split('.')
|
|
179
179
|
last = segments.pop
|
|
180
180
|
scope = holder.is_a?(Module) ? holder : Object
|
|
@@ -198,7 +198,7 @@ module Kapusta
|
|
|
198
198
|
end
|
|
199
199
|
RUBY
|
|
200
200
|
ensure_class: <<~RUBY.chomp,
|
|
201
|
-
def
|
|
201
|
+
def kap_ensure_class(holder, path, super_class)
|
|
202
202
|
segments = path.split('.')
|
|
203
203
|
last = segments.pop
|
|
204
204
|
scope = holder.is_a?(Module) ? holder : Object
|
|
@@ -222,14 +222,14 @@ module Kapusta
|
|
|
222
222
|
end
|
|
223
223
|
RUBY
|
|
224
224
|
destructure: <<~RUBY.chomp,
|
|
225
|
-
def
|
|
225
|
+
def kap_destructure(pattern, value)
|
|
226
226
|
bindings = {}
|
|
227
|
-
|
|
227
|
+
kap_destructure_into(pattern, value, bindings)
|
|
228
228
|
bindings
|
|
229
229
|
end
|
|
230
230
|
RUBY
|
|
231
231
|
destructure_into: <<~'RUBY'.chomp,
|
|
232
|
-
def
|
|
232
|
+
def kap_destructure_into(pattern, value, bindings)
|
|
233
233
|
case pattern[0]
|
|
234
234
|
when :sym
|
|
235
235
|
name = pattern[1]
|
|
@@ -241,18 +241,18 @@ module Kapusta
|
|
|
241
241
|
before = items[0...rest_idx]
|
|
242
242
|
rest_pattern = items[rest_idx][1]
|
|
243
243
|
before.each_with_index do |item, i|
|
|
244
|
-
|
|
244
|
+
kap_destructure_into(item, value ? value[i] : nil, bindings)
|
|
245
245
|
end
|
|
246
246
|
rest_value = value ? (value[rest_idx..] || []) : []
|
|
247
|
-
|
|
247
|
+
kap_destructure_into(rest_pattern, rest_value, bindings)
|
|
248
248
|
else
|
|
249
249
|
items.each_with_index do |item, i|
|
|
250
|
-
|
|
250
|
+
kap_destructure_into(item, value ? value[i] : nil, bindings)
|
|
251
251
|
end
|
|
252
252
|
end
|
|
253
253
|
when :hash
|
|
254
254
|
pattern[1].each do |key, subpattern|
|
|
255
|
-
|
|
255
|
+
kap_destructure_into(subpattern, value ? value[key] : nil, bindings)
|
|
256
256
|
end
|
|
257
257
|
when :ignore
|
|
258
258
|
nil
|
|
@@ -262,17 +262,24 @@ module Kapusta
|
|
|
262
262
|
end
|
|
263
263
|
RUBY
|
|
264
264
|
match_pattern: <<~RUBY.chomp,
|
|
265
|
-
def
|
|
265
|
+
def kap_match_pattern(pattern, value)
|
|
266
266
|
bindings = {}
|
|
267
|
-
[
|
|
267
|
+
[kap_match_pattern_into(pattern, value, bindings), bindings]
|
|
268
268
|
end
|
|
269
269
|
RUBY
|
|
270
270
|
match_pattern_into: <<~'RUBY'.chomp
|
|
271
|
-
def
|
|
271
|
+
def kap_match_pattern_into(pattern, value, bindings)
|
|
272
272
|
case pattern[0]
|
|
273
|
-
when :
|
|
273
|
+
when :bind
|
|
274
274
|
name = pattern[1]
|
|
275
|
-
|
|
275
|
+
allow_nil = pattern[2]
|
|
276
|
+
return false if value.nil? && !allow_nil
|
|
277
|
+
|
|
278
|
+
bindings[name] = value
|
|
279
|
+
true
|
|
280
|
+
when :ref
|
|
281
|
+
bindings.key?(pattern[1]) && bindings[pattern[1]] == value
|
|
282
|
+
when :wild
|
|
276
283
|
true
|
|
277
284
|
when :vec
|
|
278
285
|
return false unless value.is_a?(Array) || value.respond_to?(:to_ary)
|
|
@@ -286,14 +293,14 @@ module Kapusta
|
|
|
286
293
|
return false if array.length < before.length
|
|
287
294
|
|
|
288
295
|
before.each_with_index do |item, i|
|
|
289
|
-
return false unless
|
|
296
|
+
return false unless kap_match_pattern_into(item, array[i], bindings)
|
|
290
297
|
end
|
|
291
|
-
|
|
298
|
+
kap_match_pattern_into(rest_pattern, array[rest_idx..], bindings)
|
|
292
299
|
else
|
|
293
|
-
return false unless array.length
|
|
300
|
+
return false unless array.length >= items.length
|
|
294
301
|
|
|
295
302
|
items.each_with_index do |item, i|
|
|
296
|
-
return false unless
|
|
303
|
+
return false unless kap_match_pattern_into(item, array[i], bindings)
|
|
297
304
|
end
|
|
298
305
|
true
|
|
299
306
|
end
|
|
@@ -302,13 +309,21 @@ module Kapusta
|
|
|
302
309
|
|
|
303
310
|
pattern[1].each do |key, subpattern|
|
|
304
311
|
return false unless value.key?(key)
|
|
305
|
-
return false unless
|
|
312
|
+
return false unless kap_match_pattern_into(subpattern, value[key], bindings)
|
|
306
313
|
end
|
|
307
314
|
true
|
|
308
315
|
when :lit
|
|
309
316
|
value == pattern[1]
|
|
310
|
-
when :
|
|
311
|
-
value
|
|
317
|
+
when :pin
|
|
318
|
+
value == pattern[1]
|
|
319
|
+
when :or
|
|
320
|
+
pattern[1].any? do |option|
|
|
321
|
+
option_bindings = bindings.dup
|
|
322
|
+
next false unless kap_match_pattern_into(option, value, option_bindings)
|
|
323
|
+
|
|
324
|
+
bindings.replace(option_bindings)
|
|
325
|
+
true
|
|
326
|
+
end
|
|
312
327
|
else
|
|
313
328
|
raise "bad pattern: #{pattern.inspect}"
|
|
314
329
|
end
|
|
@@ -319,7 +334,7 @@ module Kapusta
|
|
|
319
334
|
module_function
|
|
320
335
|
|
|
321
336
|
def helper_name(name)
|
|
322
|
-
"
|
|
337
|
+
"kap_#{name}"
|
|
323
338
|
end
|
|
324
339
|
|
|
325
340
|
def helper_source(helpers)
|
|
@@ -351,7 +366,7 @@ module Kapusta
|
|
|
351
366
|
helper_methods = []
|
|
352
367
|
|
|
353
368
|
HELPER_SOURCES.each_key do |name|
|
|
354
|
-
helper_method = :"
|
|
369
|
+
helper_method = :"kap_#{name}"
|
|
355
370
|
define_singleton_method(name, instance_method(helper_method))
|
|
356
371
|
helper_methods << helper_method
|
|
357
372
|
end
|
data/lib/kapusta/compiler.rb
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'error'
|
|
3
4
|
require_relative 'compiler/runtime'
|
|
4
5
|
require_relative 'compiler/normalizer'
|
|
5
6
|
require_relative 'compiler/emitter'
|
|
6
7
|
|
|
7
8
|
module Kapusta
|
|
8
9
|
module Compiler
|
|
9
|
-
class Error <
|
|
10
|
+
class Error < Kapusta::Error; end
|
|
10
11
|
SPECIAL_FORMS = %w[
|
|
11
12
|
fn lambda λ let local var set if when unless case match
|
|
12
13
|
while for each do values
|
data/lib/kapusta/env.rb
CHANGED
|
@@ -25,6 +25,14 @@ module Kapusta
|
|
|
25
25
|
@vars.key?(name) || @parent&.defined?(name)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def ruby_name_defined?(name)
|
|
29
|
+
@vars.value?(name) || @parent&.ruby_name_defined?(name)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def local_ruby_name_defined?(name)
|
|
33
|
+
@vars.value?(name)
|
|
34
|
+
end
|
|
35
|
+
|
|
28
36
|
def set_existing!(name, value)
|
|
29
37
|
if @vars.key?(name)
|
|
30
38
|
@vars[name] = value
|
data/lib/kapusta/formatter.rb
CHANGED
|
@@ -519,11 +519,12 @@ module Kapusta
|
|
|
519
519
|
def render_pairwise_vec(vec, indent)
|
|
520
520
|
lines = ['[']
|
|
521
521
|
|
|
522
|
-
vec.items.each_slice(2) do |
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
522
|
+
vec.items.each_slice(2) do |pair|
|
|
523
|
+
left, right = pair
|
|
524
|
+
if pair.length == 2
|
|
525
|
+
rendered_pair = render_pair(left, right, indent + INDENT)
|
|
526
|
+
if rendered_pair
|
|
527
|
+
lines << indent_block(rendered_pair, INDENT)
|
|
527
528
|
else
|
|
528
529
|
lines << indent_block(render(left, indent + INDENT), INDENT)
|
|
529
530
|
lines << indent_block(render(right, indent + INDENT), INDENT)
|
|
@@ -547,8 +548,9 @@ module Kapusta
|
|
|
547
548
|
|
|
548
549
|
def render_hanging_pairwise_vec(vec)
|
|
549
550
|
pairs = vec.items.each_slice(2).to_a
|
|
550
|
-
rendered_pairs = pairs.map do |
|
|
551
|
-
|
|
551
|
+
rendered_pairs = pairs.map do |pair|
|
|
552
|
+
left, right = pair
|
|
553
|
+
return nil unless pair.length == 2
|
|
552
554
|
|
|
553
555
|
render_binding_pair(left, right)
|
|
554
556
|
end
|
|
@@ -673,15 +675,6 @@ module Kapusta
|
|
|
673
675
|
end
|
|
674
676
|
end
|
|
675
677
|
|
|
676
|
-
def fn_body(form)
|
|
677
|
-
args = list_rest(form)
|
|
678
|
-
if args[0].is_a?(Sym) && args[1].is_a?(Vec)
|
|
679
|
-
args.drop(2)
|
|
680
|
-
else
|
|
681
|
-
args.drop(1)
|
|
682
|
-
end
|
|
683
|
-
end
|
|
684
|
-
|
|
685
678
|
def consecutive_requires?(previous, current)
|
|
686
679
|
require_form?(previous) && require_form?(current)
|
|
687
680
|
end
|
|
@@ -832,6 +825,6 @@ module Kapusta
|
|
|
832
825
|
puts 'Formats Kapusta source using the built-in Kapusta reader and pretty-printer.'
|
|
833
826
|
end
|
|
834
827
|
|
|
835
|
-
class Error <
|
|
828
|
+
class Error < Kapusta::Error; end
|
|
836
829
|
end
|
|
837
830
|
end
|
data/lib/kapusta/reader.rb
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'error'
|
|
4
|
+
|
|
3
5
|
module Kapusta
|
|
4
6
|
class Reader
|
|
7
|
+
class Error < Kapusta::Error; end
|
|
8
|
+
|
|
5
9
|
WHITESPACE = [' ', "\t", "\n", "\r", "\f", "\v", ','].freeze
|
|
6
10
|
DELIMS = ['(', ')', '[', ']', '{', '}', '"', ';'].freeze
|
|
7
11
|
|
|
@@ -61,7 +65,7 @@ module Kapusta
|
|
|
61
65
|
|
|
62
66
|
def read_next_item
|
|
63
67
|
skip_ws
|
|
64
|
-
raise 'unexpected eof' if eof?
|
|
68
|
+
raise Error, 'unexpected eof' if eof?
|
|
65
69
|
|
|
66
70
|
return read_comment if @preserve_comments && peek == ';'
|
|
67
71
|
|
|
@@ -70,7 +74,7 @@ module Kapusta
|
|
|
70
74
|
|
|
71
75
|
def read_form
|
|
72
76
|
skip_ws
|
|
73
|
-
raise 'unexpected eof' if eof?
|
|
77
|
+
raise Error, 'unexpected eof' if eof?
|
|
74
78
|
|
|
75
79
|
return read_comment if @preserve_comments && peek == ';'
|
|
76
80
|
|
|
@@ -93,7 +97,7 @@ module Kapusta
|
|
|
93
97
|
items = []
|
|
94
98
|
loop do
|
|
95
99
|
skip_ws
|
|
96
|
-
raise 'unclosed (' if eof?
|
|
100
|
+
raise Error, 'unclosed (' if eof?
|
|
97
101
|
break if peek == ')'
|
|
98
102
|
|
|
99
103
|
items << read_next_item
|
|
@@ -107,7 +111,7 @@ module Kapusta
|
|
|
107
111
|
items = []
|
|
108
112
|
loop do
|
|
109
113
|
skip_ws
|
|
110
|
-
raise 'unclosed [' if eof?
|
|
114
|
+
raise Error, 'unclosed [' if eof?
|
|
111
115
|
break if peek == ']'
|
|
112
116
|
|
|
113
117
|
items << read_next_item
|
|
@@ -122,7 +126,7 @@ module Kapusta
|
|
|
122
126
|
pending = []
|
|
123
127
|
loop do
|
|
124
128
|
skip_ws
|
|
125
|
-
raise 'unclosed {' if eof?
|
|
129
|
+
raise Error, 'unclosed {' if eof?
|
|
126
130
|
break if peek == '}'
|
|
127
131
|
|
|
128
132
|
item = read_next_item
|
|
@@ -139,7 +143,7 @@ module Kapusta
|
|
|
139
143
|
end
|
|
140
144
|
advance
|
|
141
145
|
|
|
142
|
-
raise 'odd number of forms in hash' unless pending.empty?
|
|
146
|
+
raise Error, 'odd number of forms in hash' unless pending.empty?
|
|
143
147
|
|
|
144
148
|
HashLit.new(entries)
|
|
145
149
|
end
|
|
@@ -168,7 +172,7 @@ module Kapusta
|
|
|
168
172
|
buffer << advance
|
|
169
173
|
end
|
|
170
174
|
end
|
|
171
|
-
raise 'unterminated string' if eof?
|
|
175
|
+
raise Error, 'unterminated string' if eof?
|
|
172
176
|
|
|
173
177
|
advance
|
|
174
178
|
buffer
|
|
@@ -209,7 +213,7 @@ module Kapusta
|
|
|
209
213
|
start = @pos
|
|
210
214
|
advance until delim?(peek)
|
|
211
215
|
token = @src[start...@pos]
|
|
212
|
-
raise 'empty token' if token.empty?
|
|
216
|
+
raise Error, 'empty token' if token.empty?
|
|
213
217
|
|
|
214
218
|
parse_atom(token)
|
|
215
219
|
end
|
|
@@ -230,7 +234,7 @@ module Kapusta
|
|
|
230
234
|
|
|
231
235
|
def normalize_hash_pair(item, value)
|
|
232
236
|
if item.is_a?(Sym) && item.name == ':'
|
|
233
|
-
raise 'bad shorthand' unless value.is_a?(Sym)
|
|
237
|
+
raise Error, 'bad shorthand' unless value.is_a?(Sym)
|
|
234
238
|
|
|
235
239
|
key = Kapusta.kebab_to_snake(value.name).to_sym
|
|
236
240
|
[key, value]
|