kapusta 0.1.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 +7 -0
- data/.rspec +2 -0
- data/Gemfile +10 -0
- data/README.md +58 -0
- data/Rakefile +10 -0
- data/bin/console +8 -0
- data/bin/setup +4 -0
- data/examples/accumulator.kap +16 -0
- data/examples/ackermann.kap +7 -0
- data/examples/anagram.kap +13 -0
- data/examples/binary-search.kap +16 -0
- data/examples/block-sort.kap +3 -0
- data/examples/calc.kap +10 -0
- data/examples/counter.kap +19 -0
- data/examples/describe.kap +9 -0
- data/examples/destructure.kap +4 -0
- data/examples/doto.kap +2 -0
- data/examples/egg-count.kap +10 -0
- data/examples/even-squares.kap +7 -0
- data/examples/exceptions.kap +14 -0
- data/examples/factorial.kap +8 -0
- data/examples/fib.kap +4 -0
- data/examples/fizzbuzz.kap +7 -0
- data/examples/gcd.kap +5 -0
- data/examples/greet.kap +2 -0
- data/examples/hashfn.kap +4 -0
- data/examples/kwargs.kap +1 -0
- data/examples/leap-year.kap +5 -0
- data/examples/match.kap +9 -0
- data/examples/min-max.kap +11 -0
- data/examples/module-header.kap +6 -0
- data/examples/palindrome.kap +8 -0
- data/examples/pangram.kap +9 -0
- data/examples/pcall.kap +9 -0
- data/examples/pipeline.kap +6 -0
- data/examples/points.kap +9 -0
- data/examples/primes.kap +8 -0
- data/examples/raindrops.kap +13 -0
- data/examples/record.kap +6 -0
- data/examples/regex.kap +9 -0
- data/examples/ruby-eval.kap +1 -0
- data/examples/safe-lookup.kap +6 -0
- data/examples/scopes.kap +18 -0
- data/examples/shapes.kap +9 -0
- data/examples/squares.kap +3 -0
- data/examples/stack.kap +19 -0
- data/examples/sum.kap +3 -0
- data/examples/tset.kap +4 -0
- data/examples/two-sum.kap +17 -0
- data/exe/kapfmt +6 -0
- data/exe/kapusta +6 -0
- data/kapfmt +4 -0
- data/kapusta.gemspec +25 -0
- data/lib/kapusta/ast.rb +76 -0
- data/lib/kapusta/cli.rb +61 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +178 -0
- data/lib/kapusta/compiler/emitter/collections.rb +245 -0
- data/lib/kapusta/compiler/emitter/control_flow.rb +168 -0
- data/lib/kapusta/compiler/emitter/expressions.rb +107 -0
- data/lib/kapusta/compiler/emitter/interop.rb +277 -0
- data/lib/kapusta/compiler/emitter/patterns.rb +105 -0
- data/lib/kapusta/compiler/emitter/support.rb +169 -0
- data/lib/kapusta/compiler/emitter.rb +45 -0
- data/lib/kapusta/compiler/normalizer.rb +122 -0
- data/lib/kapusta/compiler/runtime.rb +583 -0
- data/lib/kapusta/compiler.rb +47 -0
- data/lib/kapusta/env.rb +42 -0
- data/lib/kapusta/formatter.rb +685 -0
- data/lib/kapusta/reader.rb +215 -0
- data/lib/kapusta/support.rb +7 -0
- data/lib/kapusta/version.rb +5 -0
- data/lib/kapusta.rb +30 -0
- data/spec/cli_spec.rb +77 -0
- data/spec/examples_spec.rb +258 -0
- data/spec/formatter_spec.rb +176 -0
- data/spec/spec_helper.rb +12 -0
- metadata +119 -0
data/lib/kapusta/cli.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../kapusta'
|
|
4
|
+
require 'optparse'
|
|
5
|
+
|
|
6
|
+
module Kapusta
|
|
7
|
+
class CLI
|
|
8
|
+
Options = Struct.new(:compile, :help, keyword_init: true)
|
|
9
|
+
|
|
10
|
+
def self.start(argv = ARGV)
|
|
11
|
+
args = argv.dup
|
|
12
|
+
options = parse_options(args)
|
|
13
|
+
|
|
14
|
+
if options.help
|
|
15
|
+
$stdout.puts usage
|
|
16
|
+
return
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
if options.compile
|
|
20
|
+
compile_file(args)
|
|
21
|
+
else
|
|
22
|
+
run_file(args)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.parse_options(args)
|
|
27
|
+
options = Options.new(compile: false, help: false)
|
|
28
|
+
|
|
29
|
+
OptionParser.new do |parser|
|
|
30
|
+
parser.banner = usage
|
|
31
|
+
parser.on('-c', '--compile', 'Compile .kap to Ruby') { options.compile = true }
|
|
32
|
+
parser.on('-h', '--help', 'Show this help') { options.help = true }
|
|
33
|
+
end.order!(args)
|
|
34
|
+
|
|
35
|
+
options
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.compile_file(args)
|
|
39
|
+
path = args.shift
|
|
40
|
+
abort usage unless path
|
|
41
|
+
abort usage unless args.empty?
|
|
42
|
+
|
|
43
|
+
$stdout.write(Kapusta.compile(File.read(path), path:))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.run_file(args)
|
|
47
|
+
path = args.shift
|
|
48
|
+
abort usage unless path
|
|
49
|
+
|
|
50
|
+
previous_argv = ARGV.dup
|
|
51
|
+
ARGV.replace(args)
|
|
52
|
+
Kapusta.dofile(path)
|
|
53
|
+
ensure
|
|
54
|
+
ARGV.replace(previous_argv) if previous_argv
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.usage
|
|
58
|
+
'usage: kapusta [--compile|-c] <file.kap> | kapusta <file.kap> [args...]'
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kapusta
|
|
4
|
+
module Compiler
|
|
5
|
+
module EmitterModules
|
|
6
|
+
module Bindings
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def emit_fn(args, env, current_scope)
|
|
10
|
+
if args[0].is_a?(Vec)
|
|
11
|
+
emit_lambda(args[0], args[1..], env, current_scope)
|
|
12
|
+
else
|
|
13
|
+
name_sym = args[0]
|
|
14
|
+
pattern = args[1]
|
|
15
|
+
body = args[2..]
|
|
16
|
+
ruby_name = temp(sanitize_local(name_sym.name))
|
|
17
|
+
fn_env = env.child
|
|
18
|
+
fn_env.define(name_sym.name, ruby_name)
|
|
19
|
+
<<~RUBY.chomp
|
|
20
|
+
(-> do
|
|
21
|
+
#{ruby_name} = nil
|
|
22
|
+
#{ruby_name} = #{emit_lambda(pattern, body, fn_env, current_scope)}
|
|
23
|
+
#{ruby_name}
|
|
24
|
+
end).call
|
|
25
|
+
RUBY
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def emit_lambda(pattern, body, env, current_scope)
|
|
30
|
+
args_var = temp('args')
|
|
31
|
+
body_env = env.child
|
|
32
|
+
bindings_code, body_env = emit_pattern_bind(pattern, args_var, body_env)
|
|
33
|
+
body_code, = emit_sequence(body, body_env, current_scope, allow_method_definitions: false)
|
|
34
|
+
<<~RUBY.chomp
|
|
35
|
+
->(*#{args_var}) do
|
|
36
|
+
#{bindings_code}
|
|
37
|
+
#{body_code}
|
|
38
|
+
end
|
|
39
|
+
RUBY
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def emit_named_fn_assignment(form, env, current_scope)
|
|
43
|
+
name_sym = form.items[1]
|
|
44
|
+
ruby_name = temp(sanitize_local(name_sym.name))
|
|
45
|
+
env.define(name_sym.name, ruby_name)
|
|
46
|
+
fn_env = env.child
|
|
47
|
+
fn_env.define(name_sym.name, ruby_name)
|
|
48
|
+
lambda_code = emit_lambda(form.items[2], form.items[3..], fn_env, current_scope)
|
|
49
|
+
["#{ruby_name} = nil\n#{ruby_name} = #{lambda_code}", env]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def emit_method_definition(form, env)
|
|
53
|
+
name_sym = form.items[1]
|
|
54
|
+
pattern = form.items[2]
|
|
55
|
+
body = form.items[3..]
|
|
56
|
+
method_env = env.child
|
|
57
|
+
args_var = temp('args')
|
|
58
|
+
bindings_code, body_env = emit_pattern_bind(pattern, args_var, method_env)
|
|
59
|
+
body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
|
|
60
|
+
|
|
61
|
+
if name_sym.name.start_with?('self.')
|
|
62
|
+
ruby_name = Kapusta.kebab_to_snake(name_sym.name.delete_prefix('self.')).to_sym.inspect
|
|
63
|
+
<<~RUBY.chomp
|
|
64
|
+
define_singleton_method(#{ruby_name}) do |*#{args_var}|
|
|
65
|
+
#{bindings_code}
|
|
66
|
+
#{body_code}
|
|
67
|
+
end
|
|
68
|
+
RUBY
|
|
69
|
+
else
|
|
70
|
+
ruby_name = Kapusta.kebab_to_snake(name_sym.name).to_sym.inspect
|
|
71
|
+
<<~RUBY.chomp
|
|
72
|
+
define_method(#{ruby_name}) do |*#{args_var}|
|
|
73
|
+
#{bindings_code}
|
|
74
|
+
#{body_code}
|
|
75
|
+
end
|
|
76
|
+
RUBY
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def emit_let(args, env, current_scope)
|
|
81
|
+
bindings = args[0]
|
|
82
|
+
body = args[1..]
|
|
83
|
+
child_env = env.child
|
|
84
|
+
binding_codes = []
|
|
85
|
+
items = bindings.items
|
|
86
|
+
i = 0
|
|
87
|
+
while i < items.length
|
|
88
|
+
pattern = items[i]
|
|
89
|
+
value_code = emit_expr(items[i + 1], child_env, current_scope)
|
|
90
|
+
bind_code, child_env = emit_pattern_bind(pattern, value_code, child_env)
|
|
91
|
+
binding_codes << bind_code
|
|
92
|
+
i += 2
|
|
93
|
+
end
|
|
94
|
+
body_code, = emit_sequence(body, child_env, current_scope, allow_method_definitions: false)
|
|
95
|
+
<<~RUBY.chomp
|
|
96
|
+
(-> do
|
|
97
|
+
#{binding_codes.join("\n")}
|
|
98
|
+
#{body_code}
|
|
99
|
+
end).call
|
|
100
|
+
RUBY
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def emit_local_form(form, env, current_scope)
|
|
104
|
+
target = form.items[1]
|
|
105
|
+
value_code = emit_expr(form.items[2], env, current_scope)
|
|
106
|
+
|
|
107
|
+
if target.is_a?(Sym)
|
|
108
|
+
ruby_name = temp(sanitize_local(target.name))
|
|
109
|
+
env.define(target.name, ruby_name)
|
|
110
|
+
["#{ruby_name} = #{value_code}\nnil", env]
|
|
111
|
+
else
|
|
112
|
+
bind_code, env = emit_pattern_bind(target, value_code, env)
|
|
113
|
+
["#{bind_code}\nnil", env]
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def emit_local_expr(args, env, current_scope)
|
|
118
|
+
code, = emit_local_form(List.new([Sym.new('local'), *args]), env.child, current_scope)
|
|
119
|
+
"(-> do\n#{indent(code)}\nend).call"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def emit_set_form(form, env, current_scope)
|
|
123
|
+
target = form.items[1]
|
|
124
|
+
value_code = emit_expr(form.items[2], env, current_scope)
|
|
125
|
+
|
|
126
|
+
if target.is_a?(Sym) && !target.dotted?
|
|
127
|
+
ruby_name =
|
|
128
|
+
if env.defined?(target.name)
|
|
129
|
+
env.lookup(target.name)
|
|
130
|
+
else
|
|
131
|
+
fresh = temp(sanitize_local(target.name))
|
|
132
|
+
env.define(target.name, fresh)
|
|
133
|
+
fresh
|
|
134
|
+
end
|
|
135
|
+
["#{ruby_name} = #{value_code}", env]
|
|
136
|
+
else
|
|
137
|
+
[emit_set_target(target, value_code, env, current_scope), env]
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def emit_set_expr(args, env, current_scope)
|
|
142
|
+
target = args[0]
|
|
143
|
+
value_code = emit_expr(args[1], env, current_scope)
|
|
144
|
+
emit_set_target(target, value_code, env, current_scope)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def emit_set_target(target, value_code, env, current_scope)
|
|
148
|
+
case target
|
|
149
|
+
when Sym
|
|
150
|
+
if target.dotted?
|
|
151
|
+
base_code, segments = multisym_base(target.segments, env)
|
|
152
|
+
runtime_call(:set_method_path, base_code, segments.inspect, value_code)
|
|
153
|
+
else
|
|
154
|
+
"#{env.lookup(target.name)} = #{value_code}"
|
|
155
|
+
end
|
|
156
|
+
when List
|
|
157
|
+
head = target.head
|
|
158
|
+
if head.is_a?(Sym) && head.name == '.'
|
|
159
|
+
object_code = emit_expr(target.items[1], env, current_scope)
|
|
160
|
+
keys_code = "[#{target.items[2..].map { |item| emit_expr(item, env, current_scope) }.join(', ')}]"
|
|
161
|
+
runtime_call(:set_path, object_code, keys_code, value_code)
|
|
162
|
+
elsif head.is_a?(Sym) && head.name == 'ivar'
|
|
163
|
+
runtime_call(:set_ivar, 'self', target.items[1].name.inspect, value_code)
|
|
164
|
+
elsif head.is_a?(Sym) && head.name == 'cvar'
|
|
165
|
+
runtime_call(:set_cvar, 'self', target.items[1].name.inspect, value_code)
|
|
166
|
+
elsif head.is_a?(Sym) && head.name == 'gvar'
|
|
167
|
+
runtime_call(:set_gvar, target.items[1].name.inspect, value_code)
|
|
168
|
+
else
|
|
169
|
+
raise Error, "bad set target: #{target.inspect}"
|
|
170
|
+
end
|
|
171
|
+
else
|
|
172
|
+
raise Error, "bad set target: #{target.inspect}"
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kapusta
|
|
4
|
+
module Compiler
|
|
5
|
+
module EmitterModules
|
|
6
|
+
module Collections
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def emit_icollect(args, env, current_scope)
|
|
10
|
+
result_var = temp('result')
|
|
11
|
+
iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
|
|
12
|
+
body = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
|
|
13
|
+
<<~RUBY.chomp
|
|
14
|
+
__kap_value = begin
|
|
15
|
+
#{indent(body)}
|
|
16
|
+
end
|
|
17
|
+
#{result_var} << __kap_value unless __kap_value.nil?
|
|
18
|
+
RUBY
|
|
19
|
+
end
|
|
20
|
+
<<~RUBY.chomp
|
|
21
|
+
(-> do
|
|
22
|
+
#{result_var} = []
|
|
23
|
+
#{iter_code}
|
|
24
|
+
#{result_var}
|
|
25
|
+
end).call
|
|
26
|
+
RUBY
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def emit_collect(args, env, current_scope)
|
|
30
|
+
result_var = temp('result')
|
|
31
|
+
iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
|
|
32
|
+
body = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
|
|
33
|
+
<<~RUBY.chomp
|
|
34
|
+
__kap_pair = begin
|
|
35
|
+
#{indent(body)}
|
|
36
|
+
end
|
|
37
|
+
if __kap_pair.is_a?(Array) && __kap_pair.length == 2 && !__kap_pair[0].nil? && !__kap_pair[1].nil?
|
|
38
|
+
#{result_var}[__kap_pair[0]] = __kap_pair[1]
|
|
39
|
+
end
|
|
40
|
+
RUBY
|
|
41
|
+
end
|
|
42
|
+
<<~RUBY.chomp
|
|
43
|
+
(-> do
|
|
44
|
+
#{result_var} = {}
|
|
45
|
+
#{iter_code}
|
|
46
|
+
#{result_var}
|
|
47
|
+
end).call
|
|
48
|
+
RUBY
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def emit_fcollect(args, env, current_scope)
|
|
52
|
+
result_var = temp('result')
|
|
53
|
+
bindings = args[0].items
|
|
54
|
+
ruby_name = temp(sanitize_local(bindings[0].name))
|
|
55
|
+
loop_env = env.child
|
|
56
|
+
loop_env.define(bindings[0].name, ruby_name)
|
|
57
|
+
start_code = emit_expr(bindings[1], env, current_scope)
|
|
58
|
+
finish_code = emit_expr(bindings[2], env, current_scope)
|
|
59
|
+
step_code = '1'
|
|
60
|
+
until_form = nil
|
|
61
|
+
i = 3
|
|
62
|
+
while i < bindings.length
|
|
63
|
+
if bindings[i].is_a?(Sym) && bindings[i].name == '&until'
|
|
64
|
+
until_form = bindings[i + 1]
|
|
65
|
+
i += 2
|
|
66
|
+
else
|
|
67
|
+
step_code = emit_expr(bindings[i], env, current_scope)
|
|
68
|
+
i += 1
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
body_code, = emit_sequence(args[1..], loop_env, current_scope, allow_method_definitions: false)
|
|
72
|
+
until_code = until_form ? "break if #{emit_expr(until_form, loop_env, current_scope)}" : nil
|
|
73
|
+
finish_var = temp('finish')
|
|
74
|
+
step_var = temp('step')
|
|
75
|
+
cmp_var = temp('cmp')
|
|
76
|
+
<<~RUBY.chomp
|
|
77
|
+
(-> do
|
|
78
|
+
#{result_var} = []
|
|
79
|
+
#{ruby_name} = #{start_code}
|
|
80
|
+
#{finish_var} = #{finish_code}
|
|
81
|
+
#{step_var} = #{step_code}
|
|
82
|
+
#{cmp_var} = #{step_var} >= 0 ? :<= : :>=
|
|
83
|
+
while #{ruby_name}.public_send(#{cmp_var}, #{finish_var})
|
|
84
|
+
#{until_code}
|
|
85
|
+
__kap_value = begin
|
|
86
|
+
#{indent(body_code)}
|
|
87
|
+
end
|
|
88
|
+
#{result_var} << __kap_value unless __kap_value.nil?
|
|
89
|
+
#{ruby_name} += #{step_var}
|
|
90
|
+
end
|
|
91
|
+
#{result_var}
|
|
92
|
+
end).call
|
|
93
|
+
RUBY
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def emit_accumulate(args, env, current_scope)
|
|
97
|
+
bindings = args[0].items
|
|
98
|
+
acc_name = bindings[0]
|
|
99
|
+
acc_var = temp(sanitize_local(acc_name.name))
|
|
100
|
+
iter_bindings = Vec.new(bindings[2..])
|
|
101
|
+
loop_env = env.child
|
|
102
|
+
loop_env.define(acc_name.name, acc_var)
|
|
103
|
+
iter_code = emit_iteration(iter_bindings, loop_env, current_scope) do |iter_env|
|
|
104
|
+
iter_env.define(acc_name.name, acc_var)
|
|
105
|
+
emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first.then do |body|
|
|
106
|
+
"#{acc_var} = begin\n#{indent(body)}\nend"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
<<~RUBY.chomp
|
|
110
|
+
(-> do
|
|
111
|
+
#{acc_var} = #{emit_expr(bindings[1], env, current_scope)}
|
|
112
|
+
#{iter_code}
|
|
113
|
+
#{acc_var}
|
|
114
|
+
end).call
|
|
115
|
+
RUBY
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def emit_faccumulate(args, env, current_scope)
|
|
119
|
+
bindings = args[0].items
|
|
120
|
+
acc_name = bindings[0]
|
|
121
|
+
acc_var = temp(sanitize_local(acc_name.name))
|
|
122
|
+
loop_name = bindings[2]
|
|
123
|
+
loop_var = temp(sanitize_local(loop_name.name))
|
|
124
|
+
loop_env = env.child
|
|
125
|
+
loop_env.define(acc_name.name, acc_var)
|
|
126
|
+
loop_env.define(loop_name.name, loop_var)
|
|
127
|
+
start_code = emit_expr(bindings[3], env, current_scope)
|
|
128
|
+
finish_code = emit_expr(bindings[4], env, current_scope)
|
|
129
|
+
step_code = bindings[5] ? emit_expr(bindings[5], env, current_scope) : '1'
|
|
130
|
+
body_code, = emit_sequence(args[1..], loop_env, current_scope, allow_method_definitions: false)
|
|
131
|
+
finish_var = temp('finish')
|
|
132
|
+
step_var = temp('step')
|
|
133
|
+
cmp_var = temp('cmp')
|
|
134
|
+
<<~RUBY.chomp
|
|
135
|
+
(-> do
|
|
136
|
+
#{acc_var} = #{emit_expr(bindings[1], env, current_scope)}
|
|
137
|
+
#{loop_var} = #{start_code}
|
|
138
|
+
#{finish_var} = #{finish_code}
|
|
139
|
+
#{step_var} = #{step_code}
|
|
140
|
+
#{cmp_var} = #{step_var} >= 0 ? :<= : :>=
|
|
141
|
+
while #{loop_var}.public_send(#{cmp_var}, #{finish_var})
|
|
142
|
+
#{acc_var} = begin
|
|
143
|
+
#{indent(body_code)}
|
|
144
|
+
end
|
|
145
|
+
#{loop_var} += #{step_var}
|
|
146
|
+
end
|
|
147
|
+
#{acc_var}
|
|
148
|
+
end).call
|
|
149
|
+
RUBY
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def emit_hashfn(args, env, current_scope)
|
|
153
|
+
args_var = temp('args')
|
|
154
|
+
hash_env = env.child
|
|
155
|
+
hash_env.define('$', "#{args_var}[0]")
|
|
156
|
+
(1..9).each do |index|
|
|
157
|
+
hash_env.define("$#{index}", "#{args_var}[#{index - 1}]")
|
|
158
|
+
end
|
|
159
|
+
hash_env.define('$...', args_var)
|
|
160
|
+
body_code = emit_expr(args[0], hash_env, current_scope)
|
|
161
|
+
<<~RUBY.chomp
|
|
162
|
+
->(*#{args_var}) do
|
|
163
|
+
#{body_code}
|
|
164
|
+
end
|
|
165
|
+
RUBY
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def emit_iteration(bindings_vec, env, current_scope)
|
|
169
|
+
items = bindings_vec.items
|
|
170
|
+
iter_expr = items.last
|
|
171
|
+
binding_pats = items[0...-1]
|
|
172
|
+
|
|
173
|
+
if iter_expr.is_a?(List) && iter_expr.head.is_a?(Sym)
|
|
174
|
+
case iter_expr.head.name
|
|
175
|
+
when 'ipairs'
|
|
176
|
+
value_var = temp('value')
|
|
177
|
+
index_var = temp('index')
|
|
178
|
+
body_env = env.child
|
|
179
|
+
bind_code, body_env = emit_iteration_bindings(
|
|
180
|
+
[[binding_pats[0], index_var], [binding_pats[1], value_var]], body_env
|
|
181
|
+
)
|
|
182
|
+
body_code = yield(body_env)
|
|
183
|
+
return <<~RUBY.chomp
|
|
184
|
+
#{emit_expr(iter_expr.items[1], env, current_scope)}.each_with_index do |#{value_var}, #{index_var}|
|
|
185
|
+
#{bind_code}
|
|
186
|
+
#{body_code}
|
|
187
|
+
end
|
|
188
|
+
RUBY
|
|
189
|
+
when 'pairs'
|
|
190
|
+
key_var = temp('key')
|
|
191
|
+
value_var = temp('value')
|
|
192
|
+
body_env = env.child
|
|
193
|
+
bind_code, body_env = emit_iteration_bindings([[binding_pats[0], key_var], [binding_pats[1], value_var]],
|
|
194
|
+
body_env)
|
|
195
|
+
body_code = yield(body_env)
|
|
196
|
+
return <<~RUBY.chomp
|
|
197
|
+
#{emit_expr(iter_expr.items[1], env, current_scope)}.each do |#{key_var}, #{value_var}|
|
|
198
|
+
#{bind_code}
|
|
199
|
+
#{body_code}
|
|
200
|
+
end
|
|
201
|
+
RUBY
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
coll_code = emit_expr(iter_expr, env, current_scope)
|
|
206
|
+
if binding_pats.length == 1
|
|
207
|
+
value_var = temp('value')
|
|
208
|
+
body_env = env.child
|
|
209
|
+
bind_code, body_env = emit_iteration_bindings([[binding_pats[0], value_var]], body_env)
|
|
210
|
+
body_code = yield(body_env)
|
|
211
|
+
<<~RUBY.chomp
|
|
212
|
+
#{coll_code}.each do |#{value_var}|
|
|
213
|
+
#{bind_code}
|
|
214
|
+
#{body_code}
|
|
215
|
+
end
|
|
216
|
+
RUBY
|
|
217
|
+
else
|
|
218
|
+
parts_var = temp('parts')
|
|
219
|
+
body_env = env.child
|
|
220
|
+
pairs = binding_pats.each_with_index.map { |pattern, i| [pattern, "#{parts_var}[#{i}]"] }
|
|
221
|
+
bind_code, body_env = emit_iteration_bindings(pairs, body_env)
|
|
222
|
+
body_code = yield(body_env)
|
|
223
|
+
<<~RUBY.chomp
|
|
224
|
+
#{coll_code}.each do |*#{parts_var}|
|
|
225
|
+
#{bind_code}
|
|
226
|
+
#{body_code}
|
|
227
|
+
end
|
|
228
|
+
RUBY
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def emit_iteration_bindings(pairs, env)
|
|
233
|
+
current_env = env
|
|
234
|
+
codes = pairs.compact.map do |pattern, value_code|
|
|
235
|
+
next nil unless pattern
|
|
236
|
+
|
|
237
|
+
code, current_env = emit_pattern_bind(pattern, value_code, current_env)
|
|
238
|
+
code
|
|
239
|
+
end.compact
|
|
240
|
+
[codes.join("\n"), current_env]
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kapusta
|
|
4
|
+
module Compiler
|
|
5
|
+
module EmitterModules
|
|
6
|
+
module ControlFlow
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def emit_if(args, env, current_scope)
|
|
10
|
+
build_if(args, env, current_scope)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def build_if(args, env, current_scope)
|
|
14
|
+
return 'nil' if args.empty?
|
|
15
|
+
return emit_expr(args[0], env, current_scope) if args.length == 1
|
|
16
|
+
|
|
17
|
+
cond = emit_expr(args[0], env, current_scope)
|
|
18
|
+
truthy = emit_expr(args[1], env, current_scope)
|
|
19
|
+
falsy = build_if(args[2..], env, current_scope)
|
|
20
|
+
<<~RUBY.chomp
|
|
21
|
+
if #{cond}
|
|
22
|
+
#{truthy}
|
|
23
|
+
else
|
|
24
|
+
#{indent(falsy)}
|
|
25
|
+
end
|
|
26
|
+
RUBY
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def emit_case(args, env, current_scope)
|
|
30
|
+
value_var = temp('case_value')
|
|
31
|
+
body = build_case_clauses(value_var, args[1..], env, current_scope)
|
|
32
|
+
<<~RUBY.chomp
|
|
33
|
+
(-> do
|
|
34
|
+
#{value_var} = #{emit_expr(args[0], env, current_scope)}
|
|
35
|
+
#{body}
|
|
36
|
+
end).call
|
|
37
|
+
RUBY
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def build_case_clauses(value_var, clauses, env, current_scope)
|
|
41
|
+
return 'nil' if clauses.empty?
|
|
42
|
+
|
|
43
|
+
pattern = clauses[0]
|
|
44
|
+
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)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def emit_case_clause(value_var, pattern, body, else_code, env, current_scope)
|
|
50
|
+
if where_pattern?(pattern)
|
|
51
|
+
emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope)
|
|
52
|
+
else
|
|
53
|
+
emit_simple_case_clause(value_var, pattern, body, else_code, env, current_scope)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def emit_simple_case_clause(value_var, pattern, body, else_code, env, current_scope)
|
|
58
|
+
match_var = temp('match')
|
|
59
|
+
bindings_var = temp('bindings')
|
|
60
|
+
arm_env = env.child
|
|
61
|
+
assign_code, arm_env = emit_bindings_from_match(pattern, bindings_var, arm_env)
|
|
62
|
+
body_code = emit_expr(body, arm_env, current_scope)
|
|
63
|
+
<<~RUBY.chomp
|
|
64
|
+
#{match_var} = #{runtime_call(:match_pattern, emit_pattern(pattern), value_var)}
|
|
65
|
+
if #{match_var}[0]
|
|
66
|
+
#{bindings_var} = #{match_var}[1]
|
|
67
|
+
#{assign_code}
|
|
68
|
+
#{body_code}
|
|
69
|
+
else
|
|
70
|
+
#{indent(else_code)}
|
|
71
|
+
end
|
|
72
|
+
RUBY
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope)
|
|
76
|
+
inner = pattern.items[1]
|
|
77
|
+
guard = pattern.items[2]
|
|
78
|
+
match_var = temp('match')
|
|
79
|
+
bindings_var = temp('bindings')
|
|
80
|
+
arm_env = env.child
|
|
81
|
+
assign_code, arm_env = emit_bindings_from_match(inner, bindings_var, arm_env)
|
|
82
|
+
guard_code = emit_expr(guard, arm_env, current_scope)
|
|
83
|
+
body_code = emit_expr(body, arm_env, current_scope)
|
|
84
|
+
<<~RUBY.chomp
|
|
85
|
+
#{match_var} = #{runtime_call(:match_pattern, emit_pattern(inner), value_var)}
|
|
86
|
+
if #{match_var}[0]
|
|
87
|
+
#{bindings_var} = #{match_var}[1]
|
|
88
|
+
#{assign_code}
|
|
89
|
+
if #{guard_code}
|
|
90
|
+
#{body_code}
|
|
91
|
+
else
|
|
92
|
+
#{indent(else_code, 2)}
|
|
93
|
+
end
|
|
94
|
+
else
|
|
95
|
+
#{indent(else_code)}
|
|
96
|
+
end
|
|
97
|
+
RUBY
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def emit_while(args, env, current_scope)
|
|
101
|
+
body_code, = emit_sequence(args[1..], env, current_scope, allow_method_definitions: false)
|
|
102
|
+
<<~RUBY.chomp
|
|
103
|
+
(-> do
|
|
104
|
+
while #{emit_expr(args[0], env, current_scope)}
|
|
105
|
+
#{indent(body_code)}
|
|
106
|
+
end
|
|
107
|
+
nil
|
|
108
|
+
end).call
|
|
109
|
+
RUBY
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def emit_for(args, env, current_scope)
|
|
113
|
+
bindings = args[0].items
|
|
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')
|
|
138
|
+
<<~RUBY.chomp
|
|
139
|
+
(-> do
|
|
140
|
+
#{ruby_name} = #{start_code}
|
|
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
|
|
149
|
+
nil
|
|
150
|
+
end).call
|
|
151
|
+
RUBY
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def emit_each(args, env, current_scope)
|
|
155
|
+
iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
|
|
156
|
+
emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
|
|
157
|
+
end
|
|
158
|
+
<<~RUBY.chomp
|
|
159
|
+
(-> do
|
|
160
|
+
#{iter_code}
|
|
161
|
+
nil
|
|
162
|
+
end).call
|
|
163
|
+
RUBY
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|