kapusta 0.2.4 → 0.4.1
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 +7 -2
- data/bin/check-all +19 -0
- data/bin/fennel-parity +157 -0
- data/examples/macros-dbg.kap +9 -0
- data/examples/macros-multi.kap +12 -0
- data/examples/macros-swap.kap +9 -0
- data/examples/macros-thrice-if.kap +18 -0
- data/examples/macros-unless.kap +7 -0
- data/examples/macros-when-let.kap +7 -0
- data/examples/manhattan-distance.kap +9 -0
- data/examples/packet-router.kap +2 -5
- data/examples/subtract-product-sum.kap +14 -0
- data/examples/tic-tac-toe.kap +4 -9
- data/examples/ugly-number.kap +22 -0
- data/lib/kapusta/ast.rb +42 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +18 -7
- data/lib/kapusta/compiler/emitter/collections.rb +69 -45
- data/lib/kapusta/compiler/emitter/control_flow.rb +39 -66
- data/lib/kapusta/compiler/emitter/expressions.rb +34 -0
- data/lib/kapusta/compiler/emitter/interop.rb +65 -72
- data/lib/kapusta/compiler/emitter/patterns.rb +192 -109
- data/lib/kapusta/compiler/emitter/support.rb +4 -16
- data/lib/kapusta/compiler/emitter.rb +1 -3
- data/lib/kapusta/compiler/macro_expander.rb +256 -0
- data/lib/kapusta/compiler/normalizer.rb +2 -2
- data/lib/kapusta/compiler.rb +8 -1
- data/lib/kapusta/formatter.rb +216 -87
- data/lib/kapusta/reader.rb +46 -10
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +28 -4
- data/spec/examples_spec.rb +59 -0
- data/spec/formatter_spec.rb +8 -10
- metadata +13 -2
- data/lib/kapusta/compiler/runtime.rb +0 -226
data/lib/kapusta.rb
CHANGED
|
@@ -12,10 +12,12 @@ module Kapusta
|
|
|
12
12
|
@loaded_kapusta_features = {}
|
|
13
13
|
|
|
14
14
|
def self.eval(source, path: '(eval)', **_opts)
|
|
15
|
+
install!
|
|
15
16
|
Compiler.run(source, path:)
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def self.dofile(path, **_opts)
|
|
20
|
+
install!
|
|
19
21
|
source = File.read(path)
|
|
20
22
|
self.eval(source, path:)
|
|
21
23
|
end
|
|
@@ -25,6 +27,7 @@ module Kapusta
|
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def self.require(feature, relative_to: nil)
|
|
30
|
+
install!
|
|
28
31
|
feature = feature.to_s
|
|
29
32
|
local_path = resolve_require_path(feature, relative_to:)
|
|
30
33
|
|
|
@@ -35,12 +38,33 @@ module Kapusta
|
|
|
35
38
|
end
|
|
36
39
|
|
|
37
40
|
def self.install!
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
return if @installed
|
|
42
|
+
|
|
43
|
+
@installed = true
|
|
44
|
+
Kernel.module_eval do
|
|
45
|
+
def require_relative(path)
|
|
46
|
+
location = caller_locations(1, 1).first
|
|
47
|
+
kap_path = Kapusta.send(:resolve_kap_relative, path, location)
|
|
48
|
+
return Kapusta.send(:require_kapusta_file, kap_path) if kap_path
|
|
49
|
+
|
|
50
|
+
base_file = location&.absolute_path || location&.path
|
|
51
|
+
target = base_file ? File.expand_path(path, File.dirname(base_file)) : path
|
|
52
|
+
Kernel.require(target)
|
|
53
|
+
end
|
|
41
54
|
end
|
|
42
55
|
end
|
|
43
56
|
|
|
57
|
+
def self.resolve_kap_relative(path, location)
|
|
58
|
+
return unless path.is_a?(String) && location
|
|
59
|
+
|
|
60
|
+
base_file = location.absolute_path || location.path
|
|
61
|
+
return unless base_file
|
|
62
|
+
|
|
63
|
+
full = File.expand_path(path, File.dirname(File.expand_path(base_file)))
|
|
64
|
+
candidates = full.end_with?('.kap') ? [full] : ["#{full}.kap"]
|
|
65
|
+
candidates.find { |c| File.file?(c) }
|
|
66
|
+
end
|
|
67
|
+
|
|
44
68
|
def self.resolve_require_path(feature, relative_to:)
|
|
45
69
|
return unless local_feature?(feature)
|
|
46
70
|
|
|
@@ -88,5 +112,5 @@ module Kapusta
|
|
|
88
112
|
end
|
|
89
113
|
|
|
90
114
|
private_class_method :resolve_require_path, :local_feature?, :require_base_dir,
|
|
91
|
-
:existing_feature_path, :require_kapusta_file
|
|
115
|
+
:existing_feature_path, :require_kapusta_file, :resolve_kap_relative
|
|
92
116
|
end
|
data/spec/examples_spec.rb
CHANGED
|
@@ -484,6 +484,10 @@ RSpec.describe 'examples' do
|
|
|
484
484
|
expect(run_example('majority-element.kap')).to eq("3\n2\n1\n")
|
|
485
485
|
end
|
|
486
486
|
|
|
487
|
+
it 'manhattan-distance.kap' do
|
|
488
|
+
expect(run_example('manhattan-distance.kap')).to eq("14\n")
|
|
489
|
+
end
|
|
490
|
+
|
|
487
491
|
it 'plus-one.kap' do
|
|
488
492
|
expect(run_example('plus-one.kap')).to eq(<<~OUT)
|
|
489
493
|
[1, 2, 4]
|
|
@@ -492,4 +496,59 @@ RSpec.describe 'examples' do
|
|
|
492
496
|
[1, 0, 0]
|
|
493
497
|
OUT
|
|
494
498
|
end
|
|
499
|
+
|
|
500
|
+
it 'subtract-product-sum.kap' do
|
|
501
|
+
expect(run_example('subtract-product-sum.kap')).to eq("15\n21\n0\n")
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
it 'ugly-number.kap' do
|
|
505
|
+
expect(run_example('ugly-number.kap')).to eq("true\ntrue\nfalse\nfalse\ntrue\n")
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
it 'macros-unless.kap' do
|
|
509
|
+
expect(run_example('macros-unless.kap')).to eq(<<~OUT)
|
|
510
|
+
"shown"
|
|
511
|
+
"also shown"
|
|
512
|
+
OUT
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
it 'macros-swap.kap' do
|
|
516
|
+
expect(run_example('macros-swap.kap')).to eq("2\n1\n")
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
it 'macros-when-let.kap' do
|
|
520
|
+
expect(run_example('macros-when-let.kap')).to eq(<<~OUT)
|
|
521
|
+
"got"
|
|
522
|
+
3
|
|
523
|
+
"done"
|
|
524
|
+
OUT
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
it 'macros-multi.kap' do
|
|
528
|
+
expect(run_example('macros-multi.kap')).to eq("10\n20\n7\n")
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
it 'macros-thrice-if.kap' do
|
|
532
|
+
expect(run_example('macros-thrice-if.kap')).to eq(<<~OUT)
|
|
533
|
+
"tick"
|
|
534
|
+
1
|
|
535
|
+
"tick"
|
|
536
|
+
2
|
|
537
|
+
"tick"
|
|
538
|
+
3
|
|
539
|
+
"final"
|
|
540
|
+
3
|
|
541
|
+
OUT
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
it 'macros-dbg.kap' do
|
|
545
|
+
expect(run_example('macros-dbg.kap')).to eq(<<~OUT)
|
|
546
|
+
"dbg"
|
|
547
|
+
6
|
|
548
|
+
"result"
|
|
549
|
+
6
|
|
550
|
+
"dbg"
|
|
551
|
+
50
|
|
552
|
+
OUT
|
|
553
|
+
end
|
|
495
554
|
end
|
data/spec/formatter_spec.rb
CHANGED
|
@@ -210,16 +210,14 @@ RSpec.describe Kapusta::Formatter do
|
|
|
210
210
|
end
|
|
211
211
|
|
|
212
212
|
expect(output).to eq(<<~KAP)
|
|
213
|
-
(let
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
role
|
|
222
|
-
"Engineer"]
|
|
213
|
+
(let [
|
|
214
|
+
profile
|
|
215
|
+
{:name "Ada"
|
|
216
|
+
; active user
|
|
217
|
+
:active true}
|
|
218
|
+
; next binding
|
|
219
|
+
role
|
|
220
|
+
"Engineer"]
|
|
223
221
|
(print profile role))
|
|
224
222
|
KAP
|
|
225
223
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kapusta
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Evgenii Morozov
|
|
@@ -20,8 +20,10 @@ files:
|
|
|
20
20
|
- Gemfile
|
|
21
21
|
- README.md
|
|
22
22
|
- Rakefile
|
|
23
|
+
- bin/check-all
|
|
23
24
|
- bin/compile-examples
|
|
24
25
|
- bin/console
|
|
26
|
+
- bin/fennel-parity
|
|
25
27
|
- bin/setup
|
|
26
28
|
- examples/accumulator.kap
|
|
27
29
|
- examples/ackermann.kap
|
|
@@ -56,7 +58,14 @@ files:
|
|
|
56
58
|
- examples/kwargs.kap
|
|
57
59
|
- examples/leap-year.kap
|
|
58
60
|
- examples/length-of-last-word.kap
|
|
61
|
+
- examples/macros-dbg.kap
|
|
62
|
+
- examples/macros-multi.kap
|
|
63
|
+
- examples/macros-swap.kap
|
|
64
|
+
- examples/macros-thrice-if.kap
|
|
65
|
+
- examples/macros-unless.kap
|
|
66
|
+
- examples/macros-when-let.kap
|
|
59
67
|
- examples/majority-element.kap
|
|
68
|
+
- examples/manhattan-distance.kap
|
|
60
69
|
- examples/match.kap
|
|
61
70
|
- examples/maximum-subarray.kap
|
|
62
71
|
- examples/min-max.kap
|
|
@@ -84,12 +93,14 @@ files:
|
|
|
84
93
|
- examples/single-number.kap
|
|
85
94
|
- examples/squares.kap
|
|
86
95
|
- examples/stack.kap
|
|
96
|
+
- examples/subtract-product-sum.kap
|
|
87
97
|
- examples/sum.kap
|
|
88
98
|
- examples/threading.kap
|
|
89
99
|
- examples/tic-tac-toe.kap
|
|
90
100
|
- examples/tset.kap
|
|
91
101
|
- examples/two-sum-hash.kap
|
|
92
102
|
- examples/two-sum.kap
|
|
103
|
+
- examples/ugly-number.kap
|
|
93
104
|
- examples/underscore-patterns.kap
|
|
94
105
|
- examples/use_bank_account.rb
|
|
95
106
|
- examples/valid-parentheses-1.kap
|
|
@@ -111,8 +122,8 @@ files:
|
|
|
111
122
|
- lib/kapusta/compiler/emitter/interop.rb
|
|
112
123
|
- lib/kapusta/compiler/emitter/patterns.rb
|
|
113
124
|
- lib/kapusta/compiler/emitter/support.rb
|
|
125
|
+
- lib/kapusta/compiler/macro_expander.rb
|
|
114
126
|
- lib/kapusta/compiler/normalizer.rb
|
|
115
|
-
- lib/kapusta/compiler/runtime.rb
|
|
116
127
|
- lib/kapusta/env.rb
|
|
117
128
|
- lib/kapusta/error.rb
|
|
118
129
|
- lib/kapusta/formatter.rb
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Kapusta
|
|
4
|
-
module Compiler
|
|
5
|
-
module Runtime
|
|
6
|
-
HELPER_DEPENDENCIES = {
|
|
7
|
-
destructure: %i[destructure_into],
|
|
8
|
-
match_pattern: %i[match_pattern_into]
|
|
9
|
-
}.freeze
|
|
10
|
-
|
|
11
|
-
HELPER_SOURCES = {
|
|
12
|
-
qget_path: <<~RUBY.chomp,
|
|
13
|
-
def kap_qget_path(obj, keys)
|
|
14
|
-
keys.each do |key|
|
|
15
|
-
return if obj.nil?
|
|
16
|
-
|
|
17
|
-
obj = obj[key]
|
|
18
|
-
end
|
|
19
|
-
obj
|
|
20
|
-
end
|
|
21
|
-
RUBY
|
|
22
|
-
ensure_module: <<~RUBY.chomp,
|
|
23
|
-
def kap_ensure_module(holder, path)
|
|
24
|
-
segments = path.split('.')
|
|
25
|
-
last = segments.pop
|
|
26
|
-
scope = holder.is_a?(Module) ? holder : Object
|
|
27
|
-
segments.each do |segment|
|
|
28
|
-
scope =
|
|
29
|
-
if scope.const_defined?(segment, false)
|
|
30
|
-
scope.const_get(segment, false)
|
|
31
|
-
else
|
|
32
|
-
mod = Module.new
|
|
33
|
-
scope.const_set(segment, mod)
|
|
34
|
-
mod
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
if scope.const_defined?(last, false)
|
|
38
|
-
scope.const_get(last, false)
|
|
39
|
-
else
|
|
40
|
-
mod = Module.new
|
|
41
|
-
scope.const_set(last, mod)
|
|
42
|
-
mod
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
RUBY
|
|
46
|
-
ensure_class: <<~RUBY.chomp,
|
|
47
|
-
def kap_ensure_class(holder, path, super_class)
|
|
48
|
-
segments = path.split('.')
|
|
49
|
-
last = segments.pop
|
|
50
|
-
scope = holder.is_a?(Module) ? holder : Object
|
|
51
|
-
segments.each do |segment|
|
|
52
|
-
scope =
|
|
53
|
-
if scope.const_defined?(segment, false)
|
|
54
|
-
scope.const_get(segment, false)
|
|
55
|
-
else
|
|
56
|
-
mod = Module.new
|
|
57
|
-
scope.const_set(segment, mod)
|
|
58
|
-
mod
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
if scope.const_defined?(last, false)
|
|
62
|
-
scope.const_get(last, false)
|
|
63
|
-
else
|
|
64
|
-
klass = Class.new(super_class)
|
|
65
|
-
scope.const_set(last, klass)
|
|
66
|
-
klass
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
RUBY
|
|
70
|
-
destructure: <<~RUBY.chomp,
|
|
71
|
-
def kap_destructure(pattern, value)
|
|
72
|
-
bindings = {}
|
|
73
|
-
kap_destructure_into(pattern, value, bindings)
|
|
74
|
-
bindings
|
|
75
|
-
end
|
|
76
|
-
RUBY
|
|
77
|
-
destructure_into: <<~'RUBY'.chomp,
|
|
78
|
-
def kap_destructure_into(pattern, value, bindings)
|
|
79
|
-
case pattern[0]
|
|
80
|
-
when :sym
|
|
81
|
-
name = pattern[1]
|
|
82
|
-
bindings[name] = value unless name == '_'
|
|
83
|
-
when :vec
|
|
84
|
-
items = pattern[1]
|
|
85
|
-
rest_idx = items.index { |item| item.is_a?(Array) && item[0] == :rest }
|
|
86
|
-
if rest_idx
|
|
87
|
-
before = items[0...rest_idx]
|
|
88
|
-
rest_pattern = items[rest_idx][1]
|
|
89
|
-
before.each_with_index do |item, i|
|
|
90
|
-
kap_destructure_into(item, value ? value[i] : nil, bindings)
|
|
91
|
-
end
|
|
92
|
-
rest_value = value ? (value[rest_idx..] || []) : []
|
|
93
|
-
kap_destructure_into(rest_pattern, rest_value, bindings)
|
|
94
|
-
else
|
|
95
|
-
items.each_with_index do |item, i|
|
|
96
|
-
kap_destructure_into(item, value ? value[i] : nil, bindings)
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
when :hash
|
|
100
|
-
pattern[1].each do |key, subpattern|
|
|
101
|
-
kap_destructure_into(subpattern, value ? value[key] : nil, bindings)
|
|
102
|
-
end
|
|
103
|
-
when :ignore
|
|
104
|
-
nil
|
|
105
|
-
else
|
|
106
|
-
raise "unknown destructure pattern: #{pattern.inspect}"
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
RUBY
|
|
110
|
-
match_pattern: <<~RUBY.chomp,
|
|
111
|
-
def kap_match_pattern(pattern, value)
|
|
112
|
-
bindings = {}
|
|
113
|
-
[kap_match_pattern_into(pattern, value, bindings), bindings]
|
|
114
|
-
end
|
|
115
|
-
RUBY
|
|
116
|
-
match_pattern_into: <<~'RUBY'.chomp
|
|
117
|
-
def kap_match_pattern_into(pattern, value, bindings)
|
|
118
|
-
case pattern[0]
|
|
119
|
-
when :bind
|
|
120
|
-
name = pattern[1]
|
|
121
|
-
allow_nil = pattern[2]
|
|
122
|
-
return false if value.nil? && !allow_nil
|
|
123
|
-
|
|
124
|
-
bindings[name] = value
|
|
125
|
-
true
|
|
126
|
-
when :ref
|
|
127
|
-
bindings.key?(pattern[1]) && bindings[pattern[1]] == value
|
|
128
|
-
when :wild
|
|
129
|
-
true
|
|
130
|
-
when :vec
|
|
131
|
-
return false unless value.is_a?(Array) || value.respond_to?(:to_ary)
|
|
132
|
-
|
|
133
|
-
array = value.is_a?(Array) ? value : value.to_ary
|
|
134
|
-
items = pattern[1]
|
|
135
|
-
rest_idx = items.index { |item| item.is_a?(Array) && item[0] == :rest }
|
|
136
|
-
if rest_idx
|
|
137
|
-
before = items[0...rest_idx]
|
|
138
|
-
rest_pattern = items[rest_idx][1]
|
|
139
|
-
return false if array.length < before.length
|
|
140
|
-
|
|
141
|
-
before.each_with_index do |item, i|
|
|
142
|
-
return false unless kap_match_pattern_into(item, array[i], bindings)
|
|
143
|
-
end
|
|
144
|
-
kap_match_pattern_into(rest_pattern, array[rest_idx..], bindings)
|
|
145
|
-
else
|
|
146
|
-
return false unless array.length >= items.length
|
|
147
|
-
|
|
148
|
-
items.each_with_index do |item, i|
|
|
149
|
-
return false unless kap_match_pattern_into(item, array[i], bindings)
|
|
150
|
-
end
|
|
151
|
-
true
|
|
152
|
-
end
|
|
153
|
-
when :hash
|
|
154
|
-
return false unless value.is_a?(Hash)
|
|
155
|
-
|
|
156
|
-
pattern[1].each do |key, subpattern|
|
|
157
|
-
return false unless value.key?(key)
|
|
158
|
-
return false unless kap_match_pattern_into(subpattern, value[key], bindings)
|
|
159
|
-
end
|
|
160
|
-
true
|
|
161
|
-
when :lit
|
|
162
|
-
value == pattern[1]
|
|
163
|
-
when :pin
|
|
164
|
-
value == pattern[1]
|
|
165
|
-
when :or
|
|
166
|
-
pattern[1].any? do |option|
|
|
167
|
-
option_bindings = bindings.dup
|
|
168
|
-
next false unless kap_match_pattern_into(option, value, option_bindings)
|
|
169
|
-
|
|
170
|
-
bindings.replace(option_bindings)
|
|
171
|
-
true
|
|
172
|
-
end
|
|
173
|
-
else
|
|
174
|
-
raise "bad pattern: #{pattern.inspect}"
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
RUBY
|
|
178
|
-
}.transform_values(&:freeze).freeze
|
|
179
|
-
|
|
180
|
-
module_function
|
|
181
|
-
|
|
182
|
-
def helper_name(name)
|
|
183
|
-
"kap_#{name}"
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
def helper_source(helpers)
|
|
187
|
-
ordered = []
|
|
188
|
-
seen = {}
|
|
189
|
-
helpers.each { |name| append_helper_source(name.to_sym, ordered, seen) }
|
|
190
|
-
return '' if ordered.empty?
|
|
191
|
-
|
|
192
|
-
[
|
|
193
|
-
ordered.map { |name| HELPER_SOURCES.fetch(name) }.join("\n\n"),
|
|
194
|
-
"private #{ordered.map { |name| ":#{helper_name(name)}" }.join(', ')}"
|
|
195
|
-
].join("\n\n")
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def append_helper_source(name, ordered, seen)
|
|
199
|
-
return if seen[name]
|
|
200
|
-
|
|
201
|
-
HELPER_DEPENDENCIES.fetch(name, []).each do |dependency|
|
|
202
|
-
append_helper_source(dependency, ordered, seen)
|
|
203
|
-
end
|
|
204
|
-
ordered << name
|
|
205
|
-
seen[name] = true
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
HELPER_SOURCES.each_value do |source|
|
|
209
|
-
module_eval(source, __FILE__, __LINE__)
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
helper_methods = []
|
|
213
|
-
|
|
214
|
-
HELPER_SOURCES.each_key do |name|
|
|
215
|
-
helper_method = :"kap_#{name}"
|
|
216
|
-
body = instance_method(helper_method)
|
|
217
|
-
define_singleton_method(helper_method, body)
|
|
218
|
-
define_singleton_method(name, body)
|
|
219
|
-
helper_methods << helper_method
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
private_class_method(*helper_methods)
|
|
223
|
-
send(:private, *helper_methods)
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
end
|