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
|
@@ -16,19 +16,40 @@ module Kapusta
|
|
|
16
16
|
|
|
17
17
|
cond = emit_expr(args[0], env, current_scope)
|
|
18
18
|
truthy = emit_expr(args[1], env, current_scope)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
lines = ["if #{cond}", indent(truthy)]
|
|
20
|
+
append_else_lines(lines, args[2..], env, current_scope)
|
|
21
|
+
lines << 'end'
|
|
22
|
+
lines.join("\n")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def append_else_lines(lines, args, env, current_scope)
|
|
26
|
+
return if args.empty?
|
|
27
|
+
|
|
28
|
+
if args.length == 1 && if_form?(args[0])
|
|
29
|
+
append_elsif_lines(lines, args[0].rest, env, current_scope)
|
|
30
|
+
elsif args.length >= 2
|
|
31
|
+
append_elsif_lines(lines, args, env, current_scope)
|
|
32
|
+
else
|
|
33
|
+
lines << 'else'
|
|
34
|
+
lines << indent(emit_expr(args[0], env, current_scope))
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def append_elsif_lines(lines, args, env, current_scope)
|
|
39
|
+
return append_else_lines(lines, args, env, current_scope) if args.length < 2
|
|
40
|
+
|
|
41
|
+
lines << "elsif #{emit_expr(args[0], env, current_scope)}"
|
|
42
|
+
lines << indent(emit_expr(args[1], env, current_scope))
|
|
43
|
+
append_else_lines(lines, args[2..], env, current_scope)
|
|
27
44
|
end
|
|
28
45
|
|
|
29
|
-
def
|
|
46
|
+
def if_form?(form)
|
|
47
|
+
form.is_a?(List) && form.head.is_a?(Sym) && form.head.name == 'if'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def emit_case(args, env, current_scope, mode)
|
|
30
51
|
value_var = temp('case_value')
|
|
31
|
-
body = build_case_clauses(value_var, args[1..], env, current_scope)
|
|
52
|
+
body = build_case_clauses(value_var, args[1..], env, current_scope, mode)
|
|
32
53
|
<<~RUBY.chomp
|
|
33
54
|
(-> do
|
|
34
55
|
#{value_var} = #{emit_expr(args[0], env, current_scope)}
|
|
@@ -37,31 +58,32 @@ module Kapusta
|
|
|
37
58
|
RUBY
|
|
38
59
|
end
|
|
39
60
|
|
|
40
|
-
def build_case_clauses(value_var, clauses, env, current_scope)
|
|
61
|
+
def build_case_clauses(value_var, clauses, env, current_scope, mode)
|
|
41
62
|
return 'nil' if clauses.empty?
|
|
42
63
|
|
|
43
64
|
pattern = clauses[0]
|
|
44
65
|
body = clauses[1]
|
|
45
|
-
else_code = build_case_clauses(value_var, clauses[2..], env, current_scope)
|
|
46
|
-
emit_case_clause(value_var, pattern, body, else_code, env, current_scope)
|
|
66
|
+
else_code = build_case_clauses(value_var, clauses[2..], env, current_scope, mode)
|
|
67
|
+
emit_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
|
|
47
68
|
end
|
|
48
69
|
|
|
49
|
-
def emit_case_clause(value_var, pattern, body, else_code, env, current_scope)
|
|
70
|
+
def emit_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
|
|
50
71
|
if where_pattern?(pattern)
|
|
51
|
-
emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope)
|
|
72
|
+
emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
|
|
52
73
|
else
|
|
53
|
-
emit_simple_case_clause(value_var, pattern, body, else_code, env, current_scope)
|
|
74
|
+
emit_simple_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
|
|
54
75
|
end
|
|
55
76
|
end
|
|
56
77
|
|
|
57
|
-
def emit_simple_case_clause(value_var, pattern, body, else_code, env, current_scope)
|
|
78
|
+
def emit_simple_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
|
|
58
79
|
match_var = temp('match')
|
|
59
80
|
bindings_var = temp('bindings')
|
|
81
|
+
plan = pattern_match_plan(pattern, env, mode:, allow_pins: false)
|
|
60
82
|
arm_env = env.child
|
|
61
|
-
assign_code, arm_env = emit_bindings_from_match(
|
|
83
|
+
assign_code, arm_env = emit_bindings_from_match(plan[:bindings], bindings_var, arm_env)
|
|
62
84
|
body_code = emit_expr(body, arm_env, current_scope)
|
|
63
85
|
<<~RUBY.chomp
|
|
64
|
-
#{match_var} = #{runtime_call(:match_pattern,
|
|
86
|
+
#{match_var} = #{runtime_call(:match_pattern, plan[:pattern], value_var)}
|
|
65
87
|
if #{match_var}[0]
|
|
66
88
|
#{bindings_var} = #{match_var}[1]
|
|
67
89
|
#{assign_code}
|
|
@@ -72,17 +94,18 @@ module Kapusta
|
|
|
72
94
|
RUBY
|
|
73
95
|
end
|
|
74
96
|
|
|
75
|
-
def emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope)
|
|
97
|
+
def emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope, mode)
|
|
76
98
|
inner = pattern.items[1]
|
|
77
|
-
|
|
99
|
+
guards = pattern.items[2..]
|
|
78
100
|
match_var = temp('match')
|
|
79
101
|
bindings_var = temp('bindings')
|
|
102
|
+
plan = pattern_match_plan(inner, env, mode:, allow_pins: mode == :case)
|
|
80
103
|
arm_env = env.child
|
|
81
|
-
assign_code, arm_env = emit_bindings_from_match(
|
|
82
|
-
guard_code =
|
|
104
|
+
assign_code, arm_env = emit_bindings_from_match(plan[:bindings], bindings_var, arm_env)
|
|
105
|
+
guard_code = emit_case_guards(guards, arm_env, current_scope)
|
|
83
106
|
body_code = emit_expr(body, arm_env, current_scope)
|
|
84
107
|
<<~RUBY.chomp
|
|
85
|
-
#{match_var} = #{runtime_call(:match_pattern,
|
|
108
|
+
#{match_var} = #{runtime_call(:match_pattern, plan[:pattern], value_var)}
|
|
86
109
|
if #{match_var}[0]
|
|
87
110
|
#{bindings_var} = #{match_var}[1]
|
|
88
111
|
#{assign_code}
|
|
@@ -97,64 +120,33 @@ module Kapusta
|
|
|
97
120
|
RUBY
|
|
98
121
|
end
|
|
99
122
|
|
|
123
|
+
def emit_case_guards(guards, env, current_scope)
|
|
124
|
+
return 'true' if guards.empty?
|
|
125
|
+
|
|
126
|
+
guards.map { |guard| parenthesize(emit_expr(guard, env, current_scope)) }.join(' && ')
|
|
127
|
+
end
|
|
128
|
+
|
|
100
129
|
def emit_while(args, env, current_scope)
|
|
101
|
-
body_code, = emit_sequence(args[1..], env, current_scope, allow_method_definitions: false)
|
|
102
130
|
<<~RUBY.chomp
|
|
103
131
|
(-> do
|
|
104
|
-
|
|
105
|
-
#{indent(body_code)}
|
|
106
|
-
end
|
|
132
|
+
#{indent(emit_while_statement(args, env, current_scope))}
|
|
107
133
|
nil
|
|
108
134
|
end).call
|
|
109
135
|
RUBY
|
|
110
136
|
end
|
|
111
137
|
|
|
112
138
|
def emit_for(args, env, current_scope)
|
|
113
|
-
|
|
114
|
-
name = bindings[0]
|
|
115
|
-
start_code = emit_expr(bindings[1], env, current_scope)
|
|
116
|
-
finish_code = emit_expr(bindings[2], env, current_scope)
|
|
117
|
-
step_code = '1'
|
|
118
|
-
until_form = nil
|
|
119
|
-
i = 3
|
|
120
|
-
while i < bindings.length
|
|
121
|
-
if bindings[i].is_a?(Sym) && bindings[i].name == '&until'
|
|
122
|
-
until_form = bindings[i + 1]
|
|
123
|
-
i += 2
|
|
124
|
-
else
|
|
125
|
-
step_code = emit_expr(bindings[i], env, current_scope)
|
|
126
|
-
i += 1
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
loop_env = env.child
|
|
131
|
-
ruby_name = temp(sanitize_local(name.name))
|
|
132
|
-
loop_env.define(name.name, ruby_name)
|
|
133
|
-
body_code, = emit_sequence(args[1..], loop_env, current_scope, allow_method_definitions: false)
|
|
134
|
-
until_code = until_form ? "break if #{emit_expr(until_form, loop_env, current_scope)}" : nil
|
|
135
|
-
cmp_var = temp('cmp')
|
|
136
|
-
step_var = temp('step')
|
|
137
|
-
finish_var = temp('finish')
|
|
139
|
+
loop_code = emit_for_statement(args, env, current_scope)
|
|
138
140
|
<<~RUBY.chomp
|
|
139
141
|
(-> do
|
|
140
|
-
#{
|
|
141
|
-
#{finish_var} = #{finish_code}
|
|
142
|
-
#{step_var} = #{step_code}
|
|
143
|
-
#{cmp_var} = #{step_var} >= 0 ? :<= : :>=
|
|
144
|
-
while #{ruby_name}.public_send(#{cmp_var}, #{finish_var})
|
|
145
|
-
#{until_code}
|
|
146
|
-
#{indent(body_code)}
|
|
147
|
-
#{ruby_name} += #{step_var}
|
|
148
|
-
end
|
|
142
|
+
#{indent(loop_code)}
|
|
149
143
|
nil
|
|
150
144
|
end).call
|
|
151
145
|
RUBY
|
|
152
146
|
end
|
|
153
147
|
|
|
154
148
|
def emit_each(args, env, current_scope)
|
|
155
|
-
iter_code =
|
|
156
|
-
emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
|
|
157
|
-
end
|
|
149
|
+
iter_code = emit_each_statement(args, env, current_scope)
|
|
158
150
|
<<~RUBY.chomp
|
|
159
151
|
(-> do
|
|
160
152
|
#{iter_code}
|
|
@@ -162,6 +154,33 @@ module Kapusta
|
|
|
162
154
|
end).call
|
|
163
155
|
RUBY
|
|
164
156
|
end
|
|
157
|
+
|
|
158
|
+
def emit_while_statement(args, env, current_scope)
|
|
159
|
+
body_code, = emit_sequence(args[1..], env, current_scope,
|
|
160
|
+
allow_method_definitions: false,
|
|
161
|
+
result: false)
|
|
162
|
+
[
|
|
163
|
+
"while #{emit_expr(args[0], env, current_scope)}",
|
|
164
|
+
indent(body_code),
|
|
165
|
+
'end'
|
|
166
|
+
].join("\n")
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def emit_for_statement(args, env, current_scope)
|
|
170
|
+
parsed = parse_counted_for_bindings(args[0].items, env, current_scope)
|
|
171
|
+
body_code, = emit_sequence(args[1..], parsed[:loop_env], current_scope,
|
|
172
|
+
allow_method_definitions: false,
|
|
173
|
+
result: false)
|
|
174
|
+
emit_counted_loop(**parsed, current_scope:, body_code:)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def emit_each_statement(args, env, current_scope)
|
|
178
|
+
emit_iteration(args[0], env, current_scope) do |iter_env|
|
|
179
|
+
emit_sequence(args[1..], iter_env, current_scope,
|
|
180
|
+
allow_method_definitions: false,
|
|
181
|
+
result: false).first
|
|
182
|
+
end
|
|
183
|
+
end
|
|
165
184
|
end
|
|
166
185
|
end
|
|
167
186
|
end
|
|
@@ -51,7 +51,8 @@ module Kapusta
|
|
|
51
51
|
when 'local', 'var' then emit_local_expr(args, env, current_scope)
|
|
52
52
|
when 'set' then emit_set_expr(args, env, current_scope)
|
|
53
53
|
when 'if' then emit_if(args, env, current_scope)
|
|
54
|
-
when 'case'
|
|
54
|
+
when 'case' then emit_case(args, env, current_scope, :case)
|
|
55
|
+
when 'match' then emit_case(args, env, current_scope, :match)
|
|
55
56
|
when 'while' then emit_while(args, env, current_scope)
|
|
56
57
|
when 'for' then emit_for(args, env, current_scope)
|
|
57
58
|
when 'each' then emit_each(args, env, current_scope)
|
|
@@ -78,7 +79,7 @@ module Kapusta
|
|
|
78
79
|
when 'raise' then emit_raise(args, env, current_scope)
|
|
79
80
|
when 'ivar' then runtime_call(:get_ivar, 'self', args[0].name.inspect)
|
|
80
81
|
when 'cvar' then runtime_call(:get_cvar, 'self', args[0].name.inspect)
|
|
81
|
-
when 'gvar' then
|
|
82
|
+
when 'gvar' then emit_gvar(args[0])
|
|
82
83
|
when 'ruby' then "Kernel.eval(#{emit_expr(args[0], env, current_scope)})"
|
|
83
84
|
when 'and' then emit_and(args, env, current_scope)
|
|
84
85
|
when 'or' then emit_or(args, env, current_scope)
|
|
@@ -37,26 +37,40 @@ module Kapusta
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def emit_module_expr(args, env)
|
|
40
|
-
|
|
40
|
+
body = emit_sequence(args[1..], env, :module, allow_method_definitions: true, result: false).first
|
|
41
|
+
emit_module_wrapper(args[0], body)
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
def emit_class_expr(args, env)
|
|
44
45
|
name_sym, supers, body_forms = split_class_args(args)
|
|
46
|
+
body = emit_sequence(body_forms, env, :class, allow_method_definitions: true, result: false).first
|
|
45
47
|
emit_class_wrapper(name_sym, supers, env,
|
|
46
|
-
|
|
48
|
+
body)
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
def emit_module_wrapper(name_sym, body)
|
|
50
52
|
mod_var = temp('module')
|
|
51
|
-
|
|
52
|
-
(-> do
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
end).call
|
|
59
|
-
|
|
53
|
+
[
|
|
54
|
+
'(-> do',
|
|
55
|
+
indent("#{mod_var} = #{runtime_call(:ensure_module, 'self', name_sym.name.inspect)}"),
|
|
56
|
+
indent("#{mod_var}.module_eval do"),
|
|
57
|
+
indent(body, 2),
|
|
58
|
+
indent('end'),
|
|
59
|
+
indent(mod_var),
|
|
60
|
+
'end).call'
|
|
61
|
+
].join("\n")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def emit_direct_module_header(name_sym, body)
|
|
65
|
+
const_name = simple_constant_name(name_sym)
|
|
66
|
+
return nil unless const_name
|
|
67
|
+
|
|
68
|
+
[
|
|
69
|
+
"module #{const_name}",
|
|
70
|
+
indent(body),
|
|
71
|
+
'end',
|
|
72
|
+
const_name
|
|
73
|
+
].join("\n")
|
|
60
74
|
end
|
|
61
75
|
|
|
62
76
|
def emit_class_wrapper(name_sym, supers, env, body)
|
|
@@ -67,15 +81,33 @@ module Kapusta
|
|
|
67
81
|
else
|
|
68
82
|
'Object'
|
|
69
83
|
end
|
|
70
|
-
|
|
71
|
-
(-> do
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
end).call
|
|
78
|
-
|
|
84
|
+
[
|
|
85
|
+
'(-> do',
|
|
86
|
+
indent("#{klass_var} = #{runtime_call(:ensure_class, 'self', name_sym.name.inspect, super_code)}"),
|
|
87
|
+
indent("#{klass_var}.class_eval do"),
|
|
88
|
+
indent(body, 2),
|
|
89
|
+
indent('end'),
|
|
90
|
+
indent(klass_var),
|
|
91
|
+
'end).call'
|
|
92
|
+
].join("\n")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def emit_direct_class_header(name_sym, supers, body)
|
|
96
|
+
const_name = simple_constant_name(name_sym)
|
|
97
|
+
return nil unless const_name && supers.nil?
|
|
98
|
+
|
|
99
|
+
[
|
|
100
|
+
"class #{const_name}",
|
|
101
|
+
indent(body),
|
|
102
|
+
'end',
|
|
103
|
+
const_name
|
|
104
|
+
].join("\n")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def simple_constant_name(name_sym)
|
|
108
|
+
return nil unless name_sym.is_a?(Sym) && name_sym.name.match?(/\A[A-Z]\w*\z/)
|
|
109
|
+
|
|
110
|
+
name_sym.name
|
|
79
111
|
end
|
|
80
112
|
|
|
81
113
|
def emit_try(args, env, current_scope)
|
|
@@ -103,8 +135,7 @@ module Kapusta
|
|
|
103
135
|
lines = ['begin', indent(emit_expr(args[0], env, current_scope))]
|
|
104
136
|
catches.each do |klass_form, bind_sym, body|
|
|
105
137
|
rescue_env = env.child
|
|
106
|
-
rescue_name =
|
|
107
|
-
rescue_env.define(bind_sym.name, rescue_name)
|
|
138
|
+
rescue_name = define_local(rescue_env, bind_sym.name)
|
|
108
139
|
body_code, = emit_sequence(body, rescue_env, current_scope, allow_method_definitions: false)
|
|
109
140
|
rescue_line =
|
|
110
141
|
if klass_form
|
|
@@ -176,9 +207,17 @@ module Kapusta
|
|
|
176
207
|
|
|
177
208
|
def emit_callable_call(callee_code, args, env, current_scope)
|
|
178
209
|
positional, kwargs, block = split_call_args(args, env, current_scope)
|
|
210
|
+
return emit_direct_callable_call(callee_code, positional) unless kwargs || block
|
|
211
|
+
|
|
179
212
|
runtime_call(:call, callee_code, "[#{positional.join(', ')}]", kwargs, block)
|
|
180
213
|
end
|
|
181
214
|
|
|
215
|
+
def emit_direct_callable_call(callee_code, positional)
|
|
216
|
+
rendered_args = positional.join(', ')
|
|
217
|
+
suffix = rendered_args.empty? ? '.call' : ".call(#{rendered_args})"
|
|
218
|
+
"#{parenthesize(callee_code)}#{suffix}"
|
|
219
|
+
end
|
|
220
|
+
|
|
182
221
|
def emit_multisym_call(head, args, env, current_scope)
|
|
183
222
|
base_code, segments = multisym_base(head.segments, env)
|
|
184
223
|
if segments.empty?
|
|
@@ -192,10 +231,21 @@ module Kapusta
|
|
|
192
231
|
end
|
|
193
232
|
method_name = Kapusta.kebab_to_snake(segments.last).to_sym.inspect
|
|
194
233
|
positional, kwargs, block = split_call_args(args, env, current_scope)
|
|
234
|
+
if segments.length == 1 && !kwargs && !block && direct_method_name?(segments.last)
|
|
235
|
+
return emit_direct_method_call(receiver, Kapusta.kebab_to_snake(segments.last), positional)
|
|
236
|
+
end
|
|
237
|
+
|
|
195
238
|
runtime_call(:send_call, receiver, method_name, positional, kwargs, block)
|
|
196
239
|
end
|
|
197
240
|
end
|
|
198
241
|
|
|
242
|
+
def emit_direct_method_call(receiver, method_name, positional)
|
|
243
|
+
args = positional.join(', ')
|
|
244
|
+
rendered_receiver = simple_expression?(receiver) ? receiver : parenthesize(receiver)
|
|
245
|
+
suffix = args.empty? ? method_name : "#{method_name}(#{args})"
|
|
246
|
+
"#{rendered_receiver}.#{suffix}"
|
|
247
|
+
end
|
|
248
|
+
|
|
199
249
|
def emit_self_call(name, args, env, current_scope)
|
|
200
250
|
positional, kwargs, block = split_call_args(args, env, current_scope)
|
|
201
251
|
method_name = Kapusta.kebab_to_snake(name).to_sym.inspect
|
|
@@ -243,6 +293,13 @@ module Kapusta
|
|
|
243
293
|
raise Error, "undefined symbol: #{name}"
|
|
244
294
|
end
|
|
245
295
|
|
|
296
|
+
def emit_gvar(sym)
|
|
297
|
+
ruby_name = global_name(sym.name)
|
|
298
|
+
return "$#{ruby_name}" if direct_global_name?(ruby_name)
|
|
299
|
+
|
|
300
|
+
runtime_call(:get_gvar, sym.name.inspect)
|
|
301
|
+
end
|
|
302
|
+
|
|
246
303
|
def emit_multisym_value(sym, env)
|
|
247
304
|
base_code, segments = multisym_base(sym.segments, env)
|
|
248
305
|
return base_code if segments.empty?
|
|
@@ -269,8 +326,34 @@ module Kapusta
|
|
|
269
326
|
end
|
|
270
327
|
|
|
271
328
|
def parenthesize(code)
|
|
329
|
+
return code if simple_expression?(code)
|
|
330
|
+
|
|
272
331
|
"(#{code})"
|
|
273
332
|
end
|
|
333
|
+
|
|
334
|
+
def direct_method_name?(name)
|
|
335
|
+
Kapusta.kebab_to_snake(name).match?(/\A[a-z_]\w*[!?=]?\z/)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def direct_global_name?(name)
|
|
339
|
+
name.match?(/\A[a-z_]\w*\z/)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def global_name(name)
|
|
343
|
+
Kapusta.kebab_to_snake(name).gsub(/[^a-zA-Z0-9_]/, '_')
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def simple_expression?(code)
|
|
347
|
+
code.match?(/\A[a-z_]\w*\z/) ||
|
|
348
|
+
code.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*\z/) ||
|
|
349
|
+
code.match?(/\A[a-z_]\w*[!?=]?\([^()\n]*\)\z/) ||
|
|
350
|
+
code.match?(/\A\d+(?:\.\d+)?\z/) ||
|
|
351
|
+
code.match?(/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?)+\z/) ||
|
|
352
|
+
code.match?(/\A:[a-zA-Z_]\w*[!?=]?\z/) ||
|
|
353
|
+
code.match?(/\A"(?:[^"\\]|\\.)*"\z/) ||
|
|
354
|
+
code.match?(/\A'(?:[^'\\]|\\.)*'\z/) ||
|
|
355
|
+
%w[nil true false self].include?(code)
|
|
356
|
+
end
|
|
274
357
|
end
|
|
275
358
|
end
|
|
276
359
|
end
|
|
@@ -10,8 +10,7 @@ module Kapusta
|
|
|
10
10
|
if pattern.is_a?(Sym)
|
|
11
11
|
return ['nil', env] if pattern.name == '_'
|
|
12
12
|
|
|
13
|
-
ruby_name =
|
|
14
|
-
env.define(pattern.name, ruby_name)
|
|
13
|
+
ruby_name = define_local(env, pattern.name)
|
|
15
14
|
["#{ruby_name} = #{value_code}", env]
|
|
16
15
|
else
|
|
17
16
|
bindings_var = temp('bindings')
|
|
@@ -20,25 +19,135 @@ module Kapusta
|
|
|
20
19
|
"#{bindings_var} = #{runtime_call(:destructure, emit_pattern(pattern), value_code)}"
|
|
21
20
|
]
|
|
22
21
|
pattern_names(pattern).each do |name|
|
|
23
|
-
ruby_name =
|
|
24
|
-
current_env.define(name, ruby_name)
|
|
22
|
+
ruby_name = define_local(current_env, name)
|
|
25
23
|
lines << "#{ruby_name} = #{bindings_var}.fetch(#{name.inspect})"
|
|
26
24
|
end
|
|
27
25
|
[lines.join("\n"), current_env]
|
|
28
26
|
end
|
|
29
27
|
end
|
|
30
28
|
|
|
31
|
-
def emit_bindings_from_match(
|
|
29
|
+
def emit_bindings_from_match(binding_names, bindings_var, env)
|
|
32
30
|
current_env = env
|
|
33
31
|
lines = []
|
|
34
|
-
|
|
35
|
-
ruby_name =
|
|
36
|
-
current_env.define(name, ruby_name)
|
|
32
|
+
binding_names.each do |name|
|
|
33
|
+
ruby_name = define_local(current_env, name)
|
|
37
34
|
lines << "#{ruby_name} = #{bindings_var}.fetch(#{name.inspect})"
|
|
38
35
|
end
|
|
39
36
|
[lines.join("\n"), current_env]
|
|
40
37
|
end
|
|
41
38
|
|
|
39
|
+
def pattern_match_plan(pattern, env, mode:, allow_pins:)
|
|
40
|
+
state = { bound_names: {}, binding_names: [] }
|
|
41
|
+
{
|
|
42
|
+
pattern: emit_match_pattern(pattern, env, mode:, allow_pins:, state:),
|
|
43
|
+
bindings: state[:binding_names]
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def emit_match_pattern(pattern, env, mode:, allow_pins:, state:)
|
|
48
|
+
case pattern
|
|
49
|
+
when Sym
|
|
50
|
+
emit_symbol_match_pattern(pattern, env, mode:, state:)
|
|
51
|
+
when Vec
|
|
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(', ')}]]"
|
|
58
|
+
when List
|
|
59
|
+
if pin_pattern?(pattern)
|
|
60
|
+
emit_pin_match_pattern(pattern, env, mode:, allow_pins:)
|
|
61
|
+
elsif or_pattern?(pattern)
|
|
62
|
+
emit_or_match_pattern(pattern, env, mode:, allow_pins:, state:)
|
|
63
|
+
elsif where_pattern?(pattern)
|
|
64
|
+
raise Error, '`where` is only valid as a case/match clause head'
|
|
65
|
+
else
|
|
66
|
+
emit_sequence_match_pattern(pattern.items, env, mode:, allow_pins:, state:)
|
|
67
|
+
end
|
|
68
|
+
when nil
|
|
69
|
+
'[:lit, nil]'
|
|
70
|
+
when Symbol, String, Numeric, true, false
|
|
71
|
+
"[:lit, #{pattern.inspect}]"
|
|
72
|
+
else
|
|
73
|
+
raise Error, "bad pattern: #{pattern.inspect}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def emit_symbol_match_pattern(pattern, env, mode:, state:)
|
|
78
|
+
name = pattern.name
|
|
79
|
+
|
|
80
|
+
if name == '_'
|
|
81
|
+
'[:wild]'
|
|
82
|
+
elsif nil_allowing_pattern_name?(name)
|
|
83
|
+
bind_name = name.start_with?('?') ? name.delete_prefix('?') : name
|
|
84
|
+
emit_named_match_pattern(bind_name, env, mode:, state:, allow_nil: true, prefer_pin: false)
|
|
85
|
+
else
|
|
86
|
+
emit_named_match_pattern(name, env, mode:, state:, allow_nil: false, prefer_pin: true)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def emit_named_match_pattern(name, env, mode:, state:, allow_nil:, prefer_pin:)
|
|
91
|
+
if state[:bound_names].key?(name)
|
|
92
|
+
"[:ref, #{name.inspect}]"
|
|
93
|
+
elsif prefer_pin && mode == :match && env.defined?(name)
|
|
94
|
+
"[:pin, #{env.lookup(name)}]"
|
|
95
|
+
else
|
|
96
|
+
state[:bound_names][name] = true
|
|
97
|
+
state[:binding_names] << name
|
|
98
|
+
"[:bind, #{name.inspect}, #{allow_nil}]"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def emit_sequence_match_pattern(items, env, mode:, allow_pins:, state:)
|
|
103
|
+
parts = []
|
|
104
|
+
i = 0
|
|
105
|
+
while i < items.length
|
|
106
|
+
if rest_pattern_marker?(items, i)
|
|
107
|
+
parts << "[:rest, #{emit_match_pattern(items[i + 1], env, mode:, allow_pins:, state:)}]"
|
|
108
|
+
i += 2
|
|
109
|
+
else
|
|
110
|
+
parts << emit_match_pattern(items[i], env, mode:, allow_pins:, state:)
|
|
111
|
+
i += 1
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
"[:vec, [#{parts.join(', ')}]]"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def emit_pin_match_pattern(pattern, env, mode:, allow_pins:)
|
|
118
|
+
raise Error, 'pin patterns are only supported inside `case` guards' unless allow_pins && mode == :case
|
|
119
|
+
|
|
120
|
+
name_sym = pattern.items[1]
|
|
121
|
+
raise Error, "bad pin pattern: #{pattern.inspect}" unless name_sym.is_a?(Sym)
|
|
122
|
+
raise Error, "cannot pin undefined name: #{name_sym.name}" unless env.defined?(name_sym.name)
|
|
123
|
+
|
|
124
|
+
"[:pin, #{env.lookup(name_sym.name)}]"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def emit_or_match_pattern(pattern, env, mode:, allow_pins:, state:)
|
|
128
|
+
initial_names = state[:binding_names].length
|
|
129
|
+
initial_bound = state[:bound_names].dup
|
|
130
|
+
canonical_names = nil
|
|
131
|
+
variants = pattern.items[1..].map do |subpattern|
|
|
132
|
+
alt_state = {
|
|
133
|
+
bound_names: initial_bound.dup,
|
|
134
|
+
binding_names: state[:binding_names].dup
|
|
135
|
+
}
|
|
136
|
+
compiled = emit_match_pattern(subpattern, env, mode:, allow_pins:, state: alt_state)
|
|
137
|
+
alt_names = alt_state[:binding_names][initial_names..]
|
|
138
|
+
canonical_names ||= alt_names
|
|
139
|
+
raise Error, 'all `or` patterns must bind the same names' if canonical_names.sort != alt_names.sort
|
|
140
|
+
|
|
141
|
+
compiled
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
canonical_names.each do |name|
|
|
145
|
+
state[:bound_names][name] = true
|
|
146
|
+
state[:binding_names] << name
|
|
147
|
+
end
|
|
148
|
+
"[:or, [#{variants.join(', ')}]]"
|
|
149
|
+
end
|
|
150
|
+
|
|
42
151
|
def emit_pattern(pattern)
|
|
43
152
|
case pattern
|
|
44
153
|
when Sym
|
|
@@ -99,6 +208,25 @@ module Kapusta
|
|
|
99
208
|
def where_pattern?(pattern)
|
|
100
209
|
pattern.is_a?(List) && pattern.head.is_a?(Sym) && pattern.head.name == 'where'
|
|
101
210
|
end
|
|
211
|
+
|
|
212
|
+
def pin_pattern?(pattern)
|
|
213
|
+
pattern.is_a?(List) &&
|
|
214
|
+
pattern.items.length == 2 &&
|
|
215
|
+
pattern.head.is_a?(Sym) &&
|
|
216
|
+
pattern.head.name == '='
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def or_pattern?(pattern)
|
|
220
|
+
pattern.is_a?(List) && pattern.head.is_a?(Sym) && pattern.head.name == 'or'
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def nil_allowing_pattern_name?(name)
|
|
224
|
+
name.length > 1 && (name.start_with?('?') || name.start_with?('_'))
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def rest_pattern_marker?(items, index)
|
|
228
|
+
items[index].is_a?(Sym) && items[index].name == '&'
|
|
229
|
+
end
|
|
102
230
|
end
|
|
103
231
|
end
|
|
104
232
|
end
|