kapusta 0.1.4 → 0.1.5
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/bin/compile-examples +24 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +105 -2
- data/lib/kapusta/compiler/emitter/expressions.rb +23 -7
- data/lib/kapusta/compiler/emitter/interop.rb +21 -5
- data/lib/kapusta/compiler/emitter/patterns.rb +2 -2
- data/lib/kapusta/compiler/emitter/support.rb +16 -3
- data/lib/kapusta/compiler/normalizer.rb +1 -1
- data/lib/kapusta/compiler/runtime.rb +23 -22
- data/lib/kapusta/env.rb +10 -2
- data/lib/kapusta/formatter.rb +9 -10
- data/lib/kapusta/reader.rb +3 -3
- data/lib/kapusta/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 52369f7a4cd658385e7ddc69f552477d94b55340cb9f796ac635550a7def6e6a
|
|
4
|
+
data.tar.gz: 6d673c9c387b52b0e4cd9e55b8ae2cf1d0a7e2c5f741dc7705f405b0ddaf25c6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 53510507ba41a4d67ec73184eaef665fee182220407046eb9db64cf05cae7d28a315b5b8cf4ace6071b359a83801bd85d7e45ce5e4aefd3c608fef614f116fee
|
|
7
|
+
data.tar.gz: ff1b88fc11dc10ccffc1459686b17754bd39dc6a941756d7cab304253aec3b63bc36967c9edcf50cf4c29eadee3ae2afb0467383b6428dcb74f41c452173ab4a
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
SOURCE_DIR="$ROOT_DIR/examples"
|
|
6
|
+
TARGET_DIR="$ROOT_DIR/examples-compiled"
|
|
7
|
+
|
|
8
|
+
mkdir -p "$TARGET_DIR"
|
|
9
|
+
|
|
10
|
+
shopt -s nullglob
|
|
11
|
+
kap_files=("$SOURCE_DIR"/*.kap)
|
|
12
|
+
|
|
13
|
+
if ((${#kap_files[@]} == 0)); then
|
|
14
|
+
printf 'No .kap files found in %s\n' "$SOURCE_DIR" >&2
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
for kap_file in "${kap_files[@]}"; do
|
|
19
|
+
name="$(basename "$kap_file" .kap)"
|
|
20
|
+
ruby_file="$TARGET_DIR/$name.rb"
|
|
21
|
+
|
|
22
|
+
"$ROOT_DIR/exe/kapusta" --compile "$kap_file" > "$ruby_file"
|
|
23
|
+
printf 'Compiled %s -> %s\n' "$kap_file" "$ruby_file"
|
|
24
|
+
done
|
|
@@ -57,6 +57,31 @@ module Kapusta
|
|
|
57
57
|
pattern.is_a?(Vec) && pattern.items.all? { |item| item.is_a?(Sym) && !item.dotted? && item.name != '&' }
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
+
def emit_definition_form(form, env, current_scope)
|
|
61
|
+
return [emit_method_definition(form, env), env] unless current_scope == :toplevel
|
|
62
|
+
|
|
63
|
+
emit_toplevel_method_definition(form, env)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def emit_toplevel_method_definition(form, env)
|
|
67
|
+
name_sym = form.items[1]
|
|
68
|
+
pattern = form.items[2]
|
|
69
|
+
body = form.items[3..]
|
|
70
|
+
return [nil, env] if name_sym.dotted?
|
|
71
|
+
return [nil, env] unless simple_parameter_pattern?(pattern)
|
|
72
|
+
|
|
73
|
+
ruby_name = direct_method_definition_name(name_sym)
|
|
74
|
+
return [nil, env] unless ruby_name
|
|
75
|
+
return [nil, env] if captures_outer_binding?(body, env, pattern_names(pattern))
|
|
76
|
+
|
|
77
|
+
env.define(name_sym.name, Env::MethodBinding.new(ruby_name))
|
|
78
|
+
definition = emit_direct_method_definition(name_sym, pattern, body, env)
|
|
79
|
+
if needs_toplevel_method_bridge?(ruby_name)
|
|
80
|
+
definition = join_code(definition, emit_toplevel_method_bridge(ruby_name))
|
|
81
|
+
end
|
|
82
|
+
[definition, env]
|
|
83
|
+
end
|
|
84
|
+
|
|
60
85
|
def emit_named_fn_assignment(form, env, current_scope)
|
|
61
86
|
name_sym = form.items[1]
|
|
62
87
|
ruby_name = define_local(env, name_sym.name)
|
|
@@ -70,6 +95,9 @@ module Kapusta
|
|
|
70
95
|
name_sym = form.items[1]
|
|
71
96
|
pattern = form.items[2]
|
|
72
97
|
body = form.items[3..]
|
|
98
|
+
direct_definition = emit_direct_method_definition(name_sym, pattern, body, env)
|
|
99
|
+
return direct_definition if direct_definition
|
|
100
|
+
|
|
73
101
|
block_header, body_code = emit_method_body(pattern, body, env)
|
|
74
102
|
|
|
75
103
|
if name_sym.name.start_with?('self.')
|
|
@@ -89,6 +117,48 @@ module Kapusta
|
|
|
89
117
|
end
|
|
90
118
|
end
|
|
91
119
|
|
|
120
|
+
def emit_direct_method_definition(name_sym, pattern, body, env)
|
|
121
|
+
return unless simple_parameter_pattern?(pattern)
|
|
122
|
+
|
|
123
|
+
ruby_name = direct_method_definition_name(name_sym)
|
|
124
|
+
return unless ruby_name
|
|
125
|
+
return if captures_outer_binding?(body, env, pattern_names(pattern))
|
|
126
|
+
|
|
127
|
+
body_env = env.child
|
|
128
|
+
params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
|
|
129
|
+
body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
|
|
130
|
+
header = params.empty? ? "def #{ruby_name}" : "def #{ruby_name}(#{params.join(', ')})"
|
|
131
|
+
[
|
|
132
|
+
header,
|
|
133
|
+
indent(body_code),
|
|
134
|
+
'end'
|
|
135
|
+
].join("\n")
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def direct_method_definition_name(name_sym)
|
|
139
|
+
source_name = name_sym.name
|
|
140
|
+
if source_name.start_with?('self.')
|
|
141
|
+
method_name = Kapusta.kebab_to_snake(source_name.delete_prefix('self.'))
|
|
142
|
+
return unless direct_method_name?(method_name)
|
|
143
|
+
|
|
144
|
+
"self.#{method_name}"
|
|
145
|
+
else
|
|
146
|
+
method_name = Kapusta.kebab_to_snake(source_name)
|
|
147
|
+
return unless direct_method_name?(method_name)
|
|
148
|
+
|
|
149
|
+
method_name
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def needs_toplevel_method_bridge?(ruby_name)
|
|
154
|
+
%w[context describe example it specify].include?(ruby_name)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def emit_toplevel_method_bridge(ruby_name)
|
|
158
|
+
method_name = ruby_name.to_sym.inspect
|
|
159
|
+
"define_singleton_method(#{method_name}, Object.instance_method(#{method_name}).bind(self))"
|
|
160
|
+
end
|
|
161
|
+
|
|
92
162
|
def emit_method_body(pattern, body, env)
|
|
93
163
|
return emit_simple_method_body(pattern, body, env) if simple_parameter_pattern?(pattern)
|
|
94
164
|
|
|
@@ -108,6 +178,33 @@ module Kapusta
|
|
|
108
178
|
[params.empty? ? 'do' : "do |#{params.join(', ')}|", body_code]
|
|
109
179
|
end
|
|
110
180
|
|
|
181
|
+
def captures_outer_binding?(forms, env, local_names)
|
|
182
|
+
forms.any? { |form| form_captures_outer_binding?(form, env, local_names) }
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def form_captures_outer_binding?(form, env, local_names)
|
|
186
|
+
case form
|
|
187
|
+
when Sym
|
|
188
|
+
sym_captures_outer_binding?(form, env, local_names)
|
|
189
|
+
when Vec, List
|
|
190
|
+
form.items.any? { |item| form_captures_outer_binding?(item, env, local_names) }
|
|
191
|
+
when HashLit
|
|
192
|
+
form.pairs.any? do |key, value|
|
|
193
|
+
form_captures_outer_binding?(key, env, local_names) ||
|
|
194
|
+
form_captures_outer_binding?(value, env, local_names)
|
|
195
|
+
end
|
|
196
|
+
else
|
|
197
|
+
false
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def sym_captures_outer_binding?(sym, env, local_names)
|
|
202
|
+
name = sym.dotted? ? sym.segments.first : sym.name
|
|
203
|
+
return false if local_names.include?(name) || !env.defined?(name)
|
|
204
|
+
|
|
205
|
+
!method_binding?(env.lookup(name))
|
|
206
|
+
end
|
|
207
|
+
|
|
111
208
|
def emit_let(args, env, current_scope)
|
|
112
209
|
binding_code, body_code = emit_let_parts(args, env, current_scope, result: true)
|
|
113
210
|
<<~RUBY.chomp
|
|
@@ -171,7 +268,10 @@ module Kapusta
|
|
|
171
268
|
if target.is_a?(Sym) && !target.dotted?
|
|
172
269
|
ruby_name =
|
|
173
270
|
if env.defined?(target.name)
|
|
174
|
-
env.lookup(target.name)
|
|
271
|
+
binding = env.lookup(target.name)
|
|
272
|
+
raise Error, "cannot set method binding: #{target.name}" if method_binding?(binding)
|
|
273
|
+
|
|
274
|
+
binding
|
|
175
275
|
else
|
|
176
276
|
define_local(env, target.name)
|
|
177
277
|
end
|
|
@@ -194,7 +294,10 @@ module Kapusta
|
|
|
194
294
|
base_code, segments = multisym_base(target.segments, env)
|
|
195
295
|
runtime_call(:set_method_path, base_code, segments.inspect, value_code)
|
|
196
296
|
else
|
|
197
|
-
|
|
297
|
+
binding = env.lookup(target.name)
|
|
298
|
+
raise Error, "cannot set method binding: #{target.name}" if method_binding?(binding)
|
|
299
|
+
|
|
300
|
+
"#{binding} = #{value_code}"
|
|
198
301
|
end
|
|
199
302
|
when List
|
|
200
303
|
head = target.head
|
|
@@ -36,7 +36,7 @@ module Kapusta
|
|
|
36
36
|
if head.is_a?(Sym)
|
|
37
37
|
return emit_special(head.name, args, env, current_scope) if special_form?(head.name)
|
|
38
38
|
return emit_multisym_call(head, args, env, current_scope) if head.dotted?
|
|
39
|
-
return
|
|
39
|
+
return emit_bound_call(env.lookup(head.name), args, env, current_scope) if env.defined?(head.name)
|
|
40
40
|
|
|
41
41
|
return emit_self_call(head.name, args, env, current_scope)
|
|
42
42
|
end
|
|
@@ -68,9 +68,7 @@ module Kapusta
|
|
|
68
68
|
when '.' then emit_lookup(args, env, current_scope)
|
|
69
69
|
when '?.' then emit_safe_lookup(args, env, current_scope)
|
|
70
70
|
when ':' then emit_colon(args, env, current_scope)
|
|
71
|
-
when '..' then
|
|
72
|
-
emit_expr(arg, env, current_scope)
|
|
73
|
-
end)
|
|
71
|
+
when '..' then emit_concat(args, env, current_scope)
|
|
74
72
|
when 'length' then "(#{emit_expr(args[0], env, current_scope)}).length"
|
|
75
73
|
when 'require' then emit_require(args[0], env, current_scope)
|
|
76
74
|
when 'module' then emit_module_expr(args, env)
|
|
@@ -95,13 +93,31 @@ module Kapusta
|
|
|
95
93
|
when '*' then emit_reduce(args, env, current_scope, '1', :*)
|
|
96
94
|
when '/' then emit_div(args, env, current_scope)
|
|
97
95
|
when '%' then args.map { |arg| parenthesize(emit_expr(arg, env, current_scope)) }.join(' % ')
|
|
98
|
-
when 'print' then
|
|
99
|
-
emit_expr(arg, env, current_scope)
|
|
100
|
-
end)
|
|
96
|
+
when 'print' then emit_print(args, env, current_scope)
|
|
101
97
|
else
|
|
102
98
|
raise Error, "unknown special form: #{name}"
|
|
103
99
|
end
|
|
104
100
|
end
|
|
101
|
+
|
|
102
|
+
def emit_concat(args, env, current_scope)
|
|
103
|
+
return '""' if args.empty?
|
|
104
|
+
|
|
105
|
+
args.map { |arg| emit_string_part(arg, env, current_scope) }.join(' + ')
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def emit_print(args, env, current_scope)
|
|
109
|
+
return '$stdout.puts("")' if args.empty?
|
|
110
|
+
|
|
111
|
+
values = args.map { |arg| emit_string_part(arg, env, current_scope) }
|
|
112
|
+
output = values.length == 1 ? values.first : "[#{values.join(', ')}].join(\"\\t\")"
|
|
113
|
+
"$stdout.puts(#{output})"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def emit_string_part(arg, env, current_scope)
|
|
117
|
+
return arg.inspect if arg.is_a?(String)
|
|
118
|
+
|
|
119
|
+
runtime_call(:stringify, emit_expr(arg, env, current_scope))
|
|
120
|
+
end
|
|
105
121
|
end
|
|
106
122
|
end
|
|
107
123
|
end
|
|
@@ -63,7 +63,7 @@ module Kapusta
|
|
|
63
63
|
|
|
64
64
|
def emit_direct_module_header(name_sym, body)
|
|
65
65
|
const_name = simple_constant_name(name_sym)
|
|
66
|
-
return
|
|
66
|
+
return unless const_name
|
|
67
67
|
|
|
68
68
|
[
|
|
69
69
|
"module #{const_name}",
|
|
@@ -94,7 +94,7 @@ module Kapusta
|
|
|
94
94
|
|
|
95
95
|
def emit_direct_class_header(name_sym, supers, body)
|
|
96
96
|
const_name = simple_constant_name(name_sym)
|
|
97
|
-
return
|
|
97
|
+
return unless const_name && supers.nil?
|
|
98
98
|
|
|
99
99
|
[
|
|
100
100
|
"class #{const_name}",
|
|
@@ -105,7 +105,7 @@ module Kapusta
|
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
def simple_constant_name(name_sym)
|
|
108
|
-
return
|
|
108
|
+
return unless name_sym.is_a?(Sym) && name_sym.name.match?(/\A[A-Z]\w*\z/)
|
|
109
109
|
|
|
110
110
|
name_sym.name
|
|
111
111
|
end
|
|
@@ -212,6 +212,22 @@ module Kapusta
|
|
|
212
212
|
runtime_call(:call, callee_code, "[#{positional.join(', ')}]", kwargs, block)
|
|
213
213
|
end
|
|
214
214
|
|
|
215
|
+
def emit_bound_call(binding, args, env, current_scope)
|
|
216
|
+
return emit_self_method_binding_call(binding, args, env, current_scope) if method_binding?(binding)
|
|
217
|
+
|
|
218
|
+
emit_callable_call(binding, args, env, current_scope)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def emit_self_method_binding_call(binding, args, env, current_scope)
|
|
222
|
+
positional = args.map { |arg| emit_expr(arg, env, current_scope) }
|
|
223
|
+
emit_direct_self_method_call(binding.ruby_name, positional)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def emit_direct_self_method_call(method_name, positional)
|
|
227
|
+
args = positional.join(', ')
|
|
228
|
+
args.empty? ? "#{method_name}()" : "#{method_name}(#{args})"
|
|
229
|
+
end
|
|
230
|
+
|
|
215
231
|
def emit_direct_callable_call(callee_code, positional)
|
|
216
232
|
rendered_args = positional.join(', ')
|
|
217
233
|
suffix = rendered_args.empty? ? '.call' : ".call(#{rendered_args})"
|
|
@@ -285,7 +301,7 @@ module Kapusta
|
|
|
285
301
|
name = sym.name
|
|
286
302
|
return 'self' if name == 'self'
|
|
287
303
|
return 'Float::INFINITY' if name == 'math.huge'
|
|
288
|
-
return env.lookup(name) if env.defined?(name)
|
|
304
|
+
return binding_value_code(env.lookup(name)) if env.defined?(name)
|
|
289
305
|
return emit_multisym_value(sym, env) if sym.dotted?
|
|
290
306
|
return 'ARGV' if name == 'ARGV'
|
|
291
307
|
return name if name.match?(/\A[A-Z]/)
|
|
@@ -311,7 +327,7 @@ module Kapusta
|
|
|
311
327
|
if segments[0] == 'self'
|
|
312
328
|
['self', segments[1..]]
|
|
313
329
|
elsif env.defined?(segments[0])
|
|
314
|
-
[env.lookup(segments[0]), segments[1..]]
|
|
330
|
+
[binding_value_code(env.lookup(segments[0])), segments[1..]]
|
|
315
331
|
else
|
|
316
332
|
idx = 0
|
|
317
333
|
const_path = []
|
|
@@ -91,7 +91,7 @@ module Kapusta
|
|
|
91
91
|
if state[:bound_names].key?(name)
|
|
92
92
|
"[:ref, #{name.inspect}]"
|
|
93
93
|
elsif prefer_pin && mode == :match && env.defined?(name)
|
|
94
|
-
"[:pin, #{env.lookup(name)}]"
|
|
94
|
+
"[:pin, #{binding_value_code(env.lookup(name))}]"
|
|
95
95
|
else
|
|
96
96
|
state[:bound_names][name] = true
|
|
97
97
|
state[:binding_names] << name
|
|
@@ -121,7 +121,7 @@ module Kapusta
|
|
|
121
121
|
raise Error, "bad pin pattern: #{pattern.inspect}" unless name_sym.is_a?(Sym)
|
|
122
122
|
raise Error, "cannot pin undefined name: #{name_sym.name}" unless env.defined?(name_sym.name)
|
|
123
123
|
|
|
124
|
-
"[:pin, #{env.lookup(name_sym.name)}]"
|
|
124
|
+
"[:pin, #{binding_value_code(env.lookup(name_sym.name))}]"
|
|
125
125
|
end
|
|
126
126
|
|
|
127
127
|
def emit_or_match_pattern(pattern, env, mode:, allow_pins:, state:)
|
|
@@ -63,9 +63,14 @@ module Kapusta
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def emit_form_in_sequence(form, env, current_scope, allow_method_definitions:, result_needed: true)
|
|
66
|
-
if allow_method_definitions &&
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
if allow_method_definitions &&
|
|
67
|
+
method_definition_form?(form) &&
|
|
68
|
+
%i[toplevel module class].include?(current_scope)
|
|
69
|
+
code, env = emit_definition_form(form, env, current_scope)
|
|
70
|
+
return [code, env] if code
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
if named_function_form?(form)
|
|
69
74
|
emit_named_fn_assignment(form, env, current_scope)
|
|
70
75
|
elsif local_form?(form)
|
|
71
76
|
code, env = emit_local_form(form, env, current_scope)
|
|
@@ -219,6 +224,14 @@ module Kapusta
|
|
|
219
224
|
"#{runtime_helper(name)}(#{rendered_args.join(', ')})"
|
|
220
225
|
end
|
|
221
226
|
|
|
227
|
+
def method_binding?(binding)
|
|
228
|
+
binding.is_a?(Env::MethodBinding)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def binding_value_code(binding)
|
|
232
|
+
method_binding?(binding) ? "method(#{binding.ruby_name.to_sym.inspect})" : binding
|
|
233
|
+
end
|
|
234
|
+
|
|
222
235
|
def parse_counted_for_bindings(bindings, env, current_scope)
|
|
223
236
|
name_sym = bindings[0]
|
|
224
237
|
loop_env = env.child
|
|
@@ -4,6 +4,7 @@ module Kapusta
|
|
|
4
4
|
module Compiler
|
|
5
5
|
module Runtime
|
|
6
6
|
HELPER_DEPENDENCIES = {
|
|
7
|
+
stringify: %i[repr],
|
|
7
8
|
print_values: %i[stringify],
|
|
8
9
|
concat: %i[stringify],
|
|
9
10
|
method_path_value: %i[kebab_to_snake],
|
|
@@ -63,33 +64,30 @@ module Kapusta
|
|
|
63
64
|
end
|
|
64
65
|
end
|
|
65
66
|
RUBY
|
|
66
|
-
stringify: <<~
|
|
67
|
+
stringify: <<~RUBY.chomp,
|
|
67
68
|
def kap_stringify(value)
|
|
68
|
-
render = nil
|
|
69
|
-
render = lambda do |item|
|
|
70
|
-
case item
|
|
71
|
-
when nil then 'nil'
|
|
72
|
-
when true then 'true'
|
|
73
|
-
when false then 'false'
|
|
74
|
-
when String, Symbol then item.inspect
|
|
75
|
-
when Array
|
|
76
|
-
"[#{item.map { |child| render.call(child) }.join(', ')}]"
|
|
77
|
-
when Hash
|
|
78
|
-
"{#{item.map { |key, child| "#{render.call(key)}=>#{render.call(child)}" }.join(', ')}}"
|
|
79
|
-
else
|
|
80
|
-
item.inspect
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
69
|
case value
|
|
85
70
|
when nil then 'nil'
|
|
86
|
-
when
|
|
87
|
-
when false then 'false'
|
|
88
|
-
when Array, Hash then render.call(value)
|
|
71
|
+
when Array, Hash then kap_repr(value)
|
|
89
72
|
else value.to_s
|
|
90
73
|
end
|
|
91
74
|
end
|
|
92
75
|
RUBY
|
|
76
|
+
repr: <<~'RUBY'.chomp,
|
|
77
|
+
def kap_repr(value)
|
|
78
|
+
case value
|
|
79
|
+
when nil then 'nil'
|
|
80
|
+
when true, false then value.to_s
|
|
81
|
+
when String, Symbol then value.inspect
|
|
82
|
+
when Array
|
|
83
|
+
"[#{value.map { |item| kap_repr(item) }.join(', ')}]"
|
|
84
|
+
when Hash
|
|
85
|
+
"{#{value.map { |key, item| "#{kap_repr(key)}=>#{kap_repr(item)}" }.join(', ')}}"
|
|
86
|
+
else
|
|
87
|
+
value.inspect
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
RUBY
|
|
93
91
|
print_values: <<~'RUBY'.chomp,
|
|
94
92
|
def kap_print_values(*values)
|
|
95
93
|
$stdout.puts(values.map { |value| kap_stringify(value) }.join("\t"))
|
|
@@ -109,7 +107,7 @@ module Kapusta
|
|
|
109
107
|
qget_path: <<~RUBY.chomp,
|
|
110
108
|
def kap_qget_path(obj, keys)
|
|
111
109
|
keys.each do |key|
|
|
112
|
-
return
|
|
110
|
+
return if obj.nil?
|
|
113
111
|
|
|
114
112
|
obj = obj[key]
|
|
115
113
|
end
|
|
@@ -367,10 +365,13 @@ module Kapusta
|
|
|
367
365
|
|
|
368
366
|
HELPER_SOURCES.each_key do |name|
|
|
369
367
|
helper_method = :"kap_#{name}"
|
|
370
|
-
|
|
368
|
+
body = instance_method(helper_method)
|
|
369
|
+
define_singleton_method(helper_method, body)
|
|
370
|
+
define_singleton_method(name, body)
|
|
371
371
|
helper_methods << helper_method
|
|
372
372
|
end
|
|
373
373
|
|
|
374
|
+
private_class_method(*helper_methods)
|
|
374
375
|
send(:private, *helper_methods)
|
|
375
376
|
end
|
|
376
377
|
end
|
data/lib/kapusta/env.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module Kapusta
|
|
4
4
|
class Env
|
|
5
|
+
MethodBinding = Struct.new(:ruby_name)
|
|
6
|
+
|
|
5
7
|
def initialize(parent = nil)
|
|
6
8
|
@parent = parent
|
|
7
9
|
@vars = {}
|
|
@@ -26,11 +28,11 @@ module Kapusta
|
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
def ruby_name_defined?(name)
|
|
29
|
-
@vars.value
|
|
31
|
+
@vars.any? { |_source_name, value| binding_ruby_name(value) == name } || @parent&.ruby_name_defined?(name)
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def local_ruby_name_defined?(name)
|
|
33
|
-
@vars.value
|
|
35
|
+
@vars.any? { |_source_name, value| binding_ruby_name(value) == name }
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def set_existing!(name, value)
|
|
@@ -46,5 +48,11 @@ module Kapusta
|
|
|
46
48
|
def child
|
|
47
49
|
Env.new(self)
|
|
48
50
|
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def binding_ruby_name(value)
|
|
55
|
+
value.respond_to?(:ruby_name) ? value.ruby_name : value
|
|
56
|
+
end
|
|
49
57
|
end
|
|
50
58
|
end
|
data/lib/kapusta/formatter.rb
CHANGED
|
@@ -177,15 +177,15 @@ module Kapusta
|
|
|
177
177
|
when Sym
|
|
178
178
|
form.name
|
|
179
179
|
when Vec
|
|
180
|
-
return
|
|
180
|
+
return if contains_comments?(form.items)
|
|
181
181
|
|
|
182
182
|
"[#{form.items.map { |item| flat_render(item) }.join(' ')}]"
|
|
183
183
|
when HashLit
|
|
184
|
-
return
|
|
184
|
+
return if contains_comments?(form.entries)
|
|
185
185
|
|
|
186
186
|
"{#{form.pairs.map { |key, value| flat_hash_pair(key, value) }.join(' ')}}"
|
|
187
187
|
when List
|
|
188
|
-
return
|
|
188
|
+
return if contains_comments?(form.items)
|
|
189
189
|
return "##{flat_render(semantic_items(form.items)[1])}" if hashfn_literal?(form)
|
|
190
190
|
|
|
191
191
|
"(#{form.items.map { |item| flat_render(item) }.join(' ')})"
|
|
@@ -548,13 +548,12 @@ module Kapusta
|
|
|
548
548
|
|
|
549
549
|
def render_hanging_pairwise_vec(vec)
|
|
550
550
|
pairs = vec.items.each_slice(2).to_a
|
|
551
|
-
|
|
552
|
-
left, right = pair
|
|
553
|
-
return nil unless pair.length == 2
|
|
551
|
+
return unless pairs.all? { |pair| pair.length == 2 }
|
|
554
552
|
|
|
553
|
+
rendered_pairs = pairs.map do |left, right|
|
|
555
554
|
render_binding_pair(left, right)
|
|
556
555
|
end
|
|
557
|
-
return
|
|
556
|
+
return if rendered_pairs.any?(&:nil?)
|
|
558
557
|
|
|
559
558
|
lines = ["[#{rendered_pairs.first}"]
|
|
560
559
|
continuation = ' ' * '(let ['.length
|
|
@@ -609,7 +608,7 @@ module Kapusta
|
|
|
609
608
|
def render_pair(left, right, indent)
|
|
610
609
|
left_rendered = flat_render(left) || render(left, indent)
|
|
611
610
|
right_rendered = flat_render(right) || render(right, indent)
|
|
612
|
-
return
|
|
611
|
+
return unless single_line?(left_rendered) && single_line?(right_rendered)
|
|
613
612
|
|
|
614
613
|
pair = "#{left_rendered} #{right_rendered}"
|
|
615
614
|
fits?(pair, indent) ? pair : nil
|
|
@@ -617,12 +616,12 @@ module Kapusta
|
|
|
617
616
|
|
|
618
617
|
def render_binding_pair(left, right)
|
|
619
618
|
left_rendered = flat_render(left)
|
|
620
|
-
return
|
|
619
|
+
return unless left_rendered
|
|
621
620
|
|
|
622
621
|
right_rendered = render(right, '(let ['.length + left_rendered.length + 1)
|
|
623
622
|
first_line, *rest = right_rendered.lines(chomp: true)
|
|
624
623
|
pair = "#{left_rendered} #{first_line}"
|
|
625
|
-
return
|
|
624
|
+
return unless pair.length <= MAX_WIDTH
|
|
626
625
|
|
|
627
626
|
return pair if rest.empty?
|
|
628
627
|
|
data/lib/kapusta/reader.rb
CHANGED
|
@@ -221,9 +221,9 @@ module Kapusta
|
|
|
221
221
|
def parse_atom(token)
|
|
222
222
|
return true if token == 'true'
|
|
223
223
|
return false if token == 'false'
|
|
224
|
-
return
|
|
225
|
-
return token
|
|
226
|
-
return token
|
|
224
|
+
return if token == 'nil'
|
|
225
|
+
return Integer(token, 10) if token.match?(/\A-?\d+\z/)
|
|
226
|
+
return Float(token) if token.match?(/\A-?\d+\.\d+\z/)
|
|
227
227
|
|
|
228
228
|
if token.start_with?(':') && token.length > 1
|
|
229
229
|
Kapusta.kebab_to_snake(token[1..]).to_sym
|
data/lib/kapusta/version.rb
CHANGED
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.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Evgenii Morozov
|
|
@@ -22,6 +22,7 @@ files:
|
|
|
22
22
|
- Gemfile
|
|
23
23
|
- README.md
|
|
24
24
|
- Rakefile
|
|
25
|
+
- bin/compile-examples
|
|
25
26
|
- bin/console
|
|
26
27
|
- bin/setup
|
|
27
28
|
- examples/accumulator.kap
|