kapusta 0.1.0 → 0.1.2
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 +10 -2
- data/examples/accumulator.kap +1 -1
- data/examples/anagram.kap +3 -3
- data/examples/binary-search.kap +2 -2
- data/examples/block-sort.kap +1 -1
- data/examples/blocks-and-kwargs.kap +20 -0
- data/examples/calc.kap +1 -1
- data/examples/counter.kap +1 -1
- data/examples/doto.kap +1 -1
- data/examples/even-squares.kap +1 -1
- data/examples/exceptions.kap +2 -2
- data/examples/files.kap +13 -0
- data/examples/greet.kap +1 -1
- data/examples/inheritance.kap +13 -0
- data/examples/kwargs.kap +1 -1
- data/examples/match.kap +3 -3
- data/examples/module-header.kap +2 -1
- data/examples/palindrome.kap +3 -3
- data/examples/pangram.kap +2 -2
- data/examples/pcall.kap +6 -6
- data/examples/pipeline.kap +4 -1
- data/examples/points.kap +1 -1
- data/examples/record.kap +1 -1
- data/examples/regex.kap +3 -1
- data/examples/ruby-eval.kap +1 -1
- data/examples/safe-lookup.kap +2 -2
- data/examples/scopes.kap +4 -4
- data/examples/threading.kap +28 -0
- data/examples/tset.kap +2 -2
- data/examples/two-sum.kap +3 -3
- data/kapusta.gemspec +4 -0
- data/lib/kapusta/ast.rb +16 -4
- data/lib/kapusta/cli.rb +8 -2
- data/lib/kapusta/compiler/runtime.rb +26 -247
- data/lib/kapusta/formatter.rb +262 -110
- data/lib/kapusta/reader.rb +51 -24
- data/lib/kapusta/version.rb +1 -1
- data/spec/cli_spec.rb +15 -0
- data/spec/examples_spec.rb +25 -0
- data/spec/formatter_spec.rb +114 -5
- metadata +9 -2
data/lib/kapusta/reader.rb
CHANGED
|
@@ -5,13 +5,14 @@ module Kapusta
|
|
|
5
5
|
WHITESPACE = [' ', "\t", "\n", "\r", "\f", "\v", ','].freeze
|
|
6
6
|
DELIMS = ['(', ')', '[', ']', '{', '}', '"', ';'].freeze
|
|
7
7
|
|
|
8
|
-
def self.read_all(source)
|
|
9
|
-
new(source).read_all
|
|
8
|
+
def self.read_all(source, preserve_comments: false)
|
|
9
|
+
new(source, preserve_comments:).read_all
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def initialize(source)
|
|
12
|
+
def initialize(source, preserve_comments: false)
|
|
13
13
|
@src = source
|
|
14
14
|
@pos = 0
|
|
15
|
+
@preserve_comments = preserve_comments
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def read_all
|
|
@@ -20,7 +21,7 @@ module Kapusta
|
|
|
20
21
|
skip_ws
|
|
21
22
|
break if eof?
|
|
22
23
|
|
|
23
|
-
forms <<
|
|
24
|
+
forms << read_next_item
|
|
24
25
|
end
|
|
25
26
|
forms
|
|
26
27
|
end
|
|
@@ -46,7 +47,7 @@ module Kapusta
|
|
|
46
47
|
char = peek
|
|
47
48
|
if WHITESPACE.include?(char)
|
|
48
49
|
advance
|
|
49
|
-
elsif char == ';'
|
|
50
|
+
elsif !@preserve_comments && char == ';'
|
|
50
51
|
advance until eof? || peek == "\n"
|
|
51
52
|
else
|
|
52
53
|
break
|
|
@@ -58,10 +59,21 @@ module Kapusta
|
|
|
58
59
|
char.nil? || WHITESPACE.include?(char) || DELIMS.include?(char)
|
|
59
60
|
end
|
|
60
61
|
|
|
62
|
+
def read_next_item
|
|
63
|
+
skip_ws
|
|
64
|
+
raise 'unexpected eof' if eof?
|
|
65
|
+
|
|
66
|
+
return read_comment if @preserve_comments && peek == ';'
|
|
67
|
+
|
|
68
|
+
read_form
|
|
69
|
+
end
|
|
70
|
+
|
|
61
71
|
def read_form
|
|
62
72
|
skip_ws
|
|
63
73
|
raise 'unexpected eof' if eof?
|
|
64
74
|
|
|
75
|
+
return read_comment if @preserve_comments && peek == ';'
|
|
76
|
+
|
|
65
77
|
form =
|
|
66
78
|
case peek
|
|
67
79
|
when '(' then read_list
|
|
@@ -84,7 +96,7 @@ module Kapusta
|
|
|
84
96
|
raise 'unclosed (' if eof?
|
|
85
97
|
break if peek == ')'
|
|
86
98
|
|
|
87
|
-
items <<
|
|
99
|
+
items << read_next_item
|
|
88
100
|
end
|
|
89
101
|
advance
|
|
90
102
|
List.new(items)
|
|
@@ -98,7 +110,7 @@ module Kapusta
|
|
|
98
110
|
raise 'unclosed [' if eof?
|
|
99
111
|
break if peek == ']'
|
|
100
112
|
|
|
101
|
-
items <<
|
|
113
|
+
items << read_next_item
|
|
102
114
|
end
|
|
103
115
|
advance
|
|
104
116
|
Vec.new(items)
|
|
@@ -106,32 +118,30 @@ module Kapusta
|
|
|
106
118
|
|
|
107
119
|
def read_hash
|
|
108
120
|
advance
|
|
109
|
-
|
|
121
|
+
entries = []
|
|
122
|
+
pending = []
|
|
110
123
|
loop do
|
|
111
124
|
skip_ws
|
|
112
125
|
raise 'unclosed {' if eof?
|
|
113
126
|
break if peek == '}'
|
|
114
127
|
|
|
115
|
-
|
|
128
|
+
item = read_next_item
|
|
129
|
+
if item.is_a?(Comment)
|
|
130
|
+
entries << item
|
|
131
|
+
next
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
pending << item
|
|
135
|
+
next unless pending.length == 2
|
|
136
|
+
|
|
137
|
+
entries << normalize_hash_pair(pending[0], pending[1])
|
|
138
|
+
pending.clear
|
|
116
139
|
end
|
|
117
140
|
advance
|
|
118
141
|
|
|
119
|
-
|
|
120
|
-
i = 0
|
|
121
|
-
while i < items.length
|
|
122
|
-
item = items[i]
|
|
123
|
-
if item.is_a?(Sym) && item.name == ':'
|
|
124
|
-
sym = items[i + 1]
|
|
125
|
-
raise 'bad shorthand' unless sym.is_a?(Sym)
|
|
142
|
+
raise 'odd number of forms in hash' unless pending.empty?
|
|
126
143
|
|
|
127
|
-
|
|
128
|
-
pairs << [key, sym]
|
|
129
|
-
else
|
|
130
|
-
pairs << [item, items[i + 1]]
|
|
131
|
-
end
|
|
132
|
-
i += 2
|
|
133
|
-
end
|
|
134
|
-
HashLit.new(pairs)
|
|
144
|
+
HashLit.new(entries)
|
|
135
145
|
end
|
|
136
146
|
|
|
137
147
|
def read_string
|
|
@@ -170,6 +180,12 @@ module Kapusta
|
|
|
170
180
|
List.new([Sym.new('hashfn'), form])
|
|
171
181
|
end
|
|
172
182
|
|
|
183
|
+
def read_comment
|
|
184
|
+
start = @pos
|
|
185
|
+
advance until eof? || peek == "\n"
|
|
186
|
+
Comment.new(@src[start...@pos].rstrip)
|
|
187
|
+
end
|
|
188
|
+
|
|
173
189
|
def read_postfix(form)
|
|
174
190
|
current = form
|
|
175
191
|
|
|
@@ -211,5 +227,16 @@ module Kapusta
|
|
|
211
227
|
Sym.new(token)
|
|
212
228
|
end
|
|
213
229
|
end
|
|
230
|
+
|
|
231
|
+
def normalize_hash_pair(item, value)
|
|
232
|
+
if item.is_a?(Sym) && item.name == ':'
|
|
233
|
+
raise 'bad shorthand' unless value.is_a?(Sym)
|
|
234
|
+
|
|
235
|
+
key = Kapusta.kebab_to_snake(value.name).to_sym
|
|
236
|
+
[key, value]
|
|
237
|
+
else
|
|
238
|
+
[item, value]
|
|
239
|
+
end
|
|
240
|
+
end
|
|
214
241
|
end
|
|
215
242
|
end
|
data/lib/kapusta/version.rb
CHANGED
data/spec/cli_spec.rb
CHANGED
|
@@ -74,4 +74,19 @@ RSpec.describe Kapusta::CLI do
|
|
|
74
74
|
|
|
75
75
|
expect(output).to eq("Hello, Ada!\n")
|
|
76
76
|
end
|
|
77
|
+
|
|
78
|
+
it 'prints the version with -v' do
|
|
79
|
+
output = capture_stdout do
|
|
80
|
+
described_class.start(['-v'])
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
expect(output).to eq("kapusta #{Kapusta::VERSION}\n")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'prints the version with --version from the executable' do
|
|
87
|
+
stdout, stderr, status = Open3.capture3(RbConfig.ruby, File.expand_path('../exe/kapusta', __dir__), '--version')
|
|
88
|
+
|
|
89
|
+
expect(status.success?).to eq(true), stderr
|
|
90
|
+
expect(stdout).to eq("kapusta #{Kapusta::VERSION}\n")
|
|
91
|
+
end
|
|
77
92
|
end
|
data/spec/examples_spec.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'spec_helper'
|
|
4
|
+
require 'fileutils'
|
|
4
5
|
require 'stringio'
|
|
5
6
|
|
|
6
7
|
EXAMPLES_DIR = File.expand_path('../examples', __dir__)
|
|
@@ -38,6 +39,14 @@ RSpec.describe 'examples' do
|
|
|
38
39
|
expect(run_example('binary-search.kap')).to eq("3\nnil\n")
|
|
39
40
|
end
|
|
40
41
|
|
|
42
|
+
it 'blocks-and-kwargs.kap' do
|
|
43
|
+
path = File.expand_path('../tmp/blocks-and-kwargs.txt', EXAMPLES_DIR)
|
|
44
|
+
FileUtils.rm_f(path)
|
|
45
|
+
|
|
46
|
+
expect(run_example('blocks-and-kwargs.kap')).to eq("Ada\nLin\n2\n")
|
|
47
|
+
expect(File.exist?(path)).to eq(false)
|
|
48
|
+
end
|
|
49
|
+
|
|
41
50
|
it 'block-sort.kap' do
|
|
42
51
|
expect(run_example('block-sort.kap')).to eq("3, 2, 1\n")
|
|
43
52
|
end
|
|
@@ -74,6 +83,14 @@ RSpec.describe 'examples' do
|
|
|
74
83
|
expect(run_example('factorial.kap')).to eq("0\t1\n1\t1\n5\t120\n6\t720\n10\t3628800\n")
|
|
75
84
|
end
|
|
76
85
|
|
|
86
|
+
it 'files.kap' do
|
|
87
|
+
path = File.expand_path('../tmp/file-io-example.txt', EXAMPLES_DIR)
|
|
88
|
+
FileUtils.rm_f(path)
|
|
89
|
+
|
|
90
|
+
expect(run_example('files.kap')).to eq("Ada\nLin\n2\n")
|
|
91
|
+
expect(File.exist?(path)).to eq(false)
|
|
92
|
+
end
|
|
93
|
+
|
|
77
94
|
it 'fib.kap' do
|
|
78
95
|
expect(run_example('fib.kap')).to eq("55\n")
|
|
79
96
|
end
|
|
@@ -95,6 +112,10 @@ RSpec.describe 'examples' do
|
|
|
95
112
|
expect(run_example('hashfn.kap')).to eq("5\n21\n")
|
|
96
113
|
end
|
|
97
114
|
|
|
115
|
+
it 'inheritance.kap' do
|
|
116
|
+
expect(run_example('inheritance.kap')).to eq("true\tanimalia\tPoppy the dog\twoof\n")
|
|
117
|
+
end
|
|
118
|
+
|
|
98
119
|
it 'leap-year.kap' do
|
|
99
120
|
expect(run_example('leap-year.kap')).to eq("true\n")
|
|
100
121
|
end
|
|
@@ -199,6 +220,10 @@ RSpec.describe 'examples' do
|
|
|
199
220
|
it 'two-sum.kap' do
|
|
200
221
|
expect(run_example('two-sum.kap')).to eq("[0, 1]\n[1, 2]\nnil\n")
|
|
201
222
|
end
|
|
223
|
+
|
|
224
|
+
it 'threading.kap' do
|
|
225
|
+
expect(run_example('threading.kap')).to eq("[Ada Lovelace]!\t<Ada!>\tnil\tATSUPAK\tnil\n")
|
|
226
|
+
end
|
|
202
227
|
end
|
|
203
228
|
|
|
204
229
|
RSpec.describe Kapusta do
|
data/spec/formatter_spec.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require 'spec_helper'
|
|
4
4
|
require 'kapusta/formatter'
|
|
5
|
+
require 'open3'
|
|
6
|
+
require 'rbconfig'
|
|
5
7
|
require 'stringio'
|
|
6
8
|
require 'tmpdir'
|
|
7
9
|
|
|
@@ -128,16 +130,123 @@ RSpec.describe Kapusta::Formatter do
|
|
|
128
130
|
expect(error_output).to include('Cannot use --fix with stdin (-).')
|
|
129
131
|
end
|
|
130
132
|
|
|
131
|
-
it '
|
|
133
|
+
it 'prints the version with -v' do
|
|
134
|
+
output = capture_stdout do
|
|
135
|
+
expect(described_class.new(['-v']).run).to eq(0)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
expect(output).to eq("kapfmt #{Kapusta::VERSION}\n")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it 'prints the version with --version from the executable' do
|
|
142
|
+
stdout, stderr, status = Open3.capture3(RbConfig.ruby, File.expand_path('../exe/kapfmt', __dir__), '--version')
|
|
143
|
+
|
|
144
|
+
expect(status.success?).to eq(true), stderr
|
|
145
|
+
expect(stdout).to eq("kapfmt #{Kapusta::VERSION}\n")
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it 'preserves top-level comments and comments inside forms' do
|
|
132
149
|
Dir.mktmpdir do |dir|
|
|
133
150
|
path = File.join(dir, 'sample.kap')
|
|
134
|
-
File.write(path,
|
|
151
|
+
File.write(path, <<~KAP)
|
|
152
|
+
; entry point
|
|
153
|
+
(fn main [] ; inline body comment
|
|
154
|
+
(print 1))
|
|
155
|
+
KAP
|
|
135
156
|
|
|
136
|
-
|
|
137
|
-
expect(described_class.new([path]).run).to eq(
|
|
157
|
+
output = capture_stdout do
|
|
158
|
+
expect(described_class.new([path]).run).to eq(0)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
expect(output).to eq(<<~KAP)
|
|
162
|
+
; entry point
|
|
163
|
+
(fn main []
|
|
164
|
+
; inline body comment
|
|
165
|
+
(print 1))
|
|
166
|
+
KAP
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it 'normalizes end-of-line comments into standalone indented comments' do
|
|
171
|
+
Dir.mktmpdir do |dir|
|
|
172
|
+
path = File.join(dir, 'sample.kap')
|
|
173
|
+
File.write(path, <<~KAP)
|
|
174
|
+
(let [words ["red" "green" "blue"]] ; source data
|
|
175
|
+
(-> words ; start pipeline
|
|
176
|
+
(: :select (fn [w] (< (length w) 5))) ; keep short words
|
|
177
|
+
(: :map (fn [w] (w.upcase)))))
|
|
178
|
+
KAP
|
|
179
|
+
|
|
180
|
+
output = capture_stdout do
|
|
181
|
+
expect(described_class.new([path]).run).to eq(0)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
expect(output).to eq(<<~KAP)
|
|
185
|
+
(let [words ["red" "green" "blue"]]
|
|
186
|
+
; source data
|
|
187
|
+
(-> words
|
|
188
|
+
; start pipeline
|
|
189
|
+
(: :select (fn [w] (< (length w) 5)))
|
|
190
|
+
; keep short words
|
|
191
|
+
(: :map (fn [w] (w.upcase)))))
|
|
192
|
+
KAP
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it 'preserves comments in multiline collections' do
|
|
197
|
+
Dir.mktmpdir do |dir|
|
|
198
|
+
path = File.join(dir, 'sample.kap')
|
|
199
|
+
File.write(path, <<~KAP)
|
|
200
|
+
(let [profile {:name "Ada"
|
|
201
|
+
; active user
|
|
202
|
+
:active true}
|
|
203
|
+
; next binding
|
|
204
|
+
role "Engineer"]
|
|
205
|
+
(print profile role))
|
|
206
|
+
KAP
|
|
207
|
+
|
|
208
|
+
output = capture_stdout do
|
|
209
|
+
expect(described_class.new([path]).run).to eq(0)
|
|
138
210
|
end
|
|
139
211
|
|
|
140
|
-
expect(
|
|
212
|
+
expect(output).to eq(<<~KAP)
|
|
213
|
+
(let
|
|
214
|
+
[
|
|
215
|
+
profile
|
|
216
|
+
{
|
|
217
|
+
:name "Ada"
|
|
218
|
+
; active user
|
|
219
|
+
:active true}
|
|
220
|
+
; next binding
|
|
221
|
+
role
|
|
222
|
+
"Engineer"]
|
|
223
|
+
(print profile role))
|
|
224
|
+
KAP
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
it 'preserves indented comments in nested bodies' do
|
|
229
|
+
Dir.mktmpdir do |dir|
|
|
230
|
+
path = File.join(dir, 'sample.kap')
|
|
231
|
+
File.write(path, <<~KAP)
|
|
232
|
+
(fn classify [score]
|
|
233
|
+
(if (> score 90)
|
|
234
|
+
; fast path for top scores
|
|
235
|
+
:great
|
|
236
|
+
:ok))
|
|
237
|
+
KAP
|
|
238
|
+
|
|
239
|
+
output = capture_stdout do
|
|
240
|
+
expect(described_class.new([path]).run).to eq(0)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
expect(output).to eq(<<~KAP)
|
|
244
|
+
(fn classify [score]
|
|
245
|
+
(if (> score 90)
|
|
246
|
+
; fast path for top scores
|
|
247
|
+
:great
|
|
248
|
+
:ok))
|
|
249
|
+
KAP
|
|
141
250
|
end
|
|
142
251
|
end
|
|
143
252
|
|
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.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Evgenii Morozov
|
|
@@ -29,6 +29,7 @@ files:
|
|
|
29
29
|
- examples/anagram.kap
|
|
30
30
|
- examples/binary-search.kap
|
|
31
31
|
- examples/block-sort.kap
|
|
32
|
+
- examples/blocks-and-kwargs.kap
|
|
32
33
|
- examples/calc.kap
|
|
33
34
|
- examples/counter.kap
|
|
34
35
|
- examples/describe.kap
|
|
@@ -39,10 +40,12 @@ files:
|
|
|
39
40
|
- examples/exceptions.kap
|
|
40
41
|
- examples/factorial.kap
|
|
41
42
|
- examples/fib.kap
|
|
43
|
+
- examples/files.kap
|
|
42
44
|
- examples/fizzbuzz.kap
|
|
43
45
|
- examples/gcd.kap
|
|
44
46
|
- examples/greet.kap
|
|
45
47
|
- examples/hashfn.kap
|
|
48
|
+
- examples/inheritance.kap
|
|
46
49
|
- examples/kwargs.kap
|
|
47
50
|
- examples/leap-year.kap
|
|
48
51
|
- examples/match.kap
|
|
@@ -64,6 +67,7 @@ files:
|
|
|
64
67
|
- examples/squares.kap
|
|
65
68
|
- examples/stack.kap
|
|
66
69
|
- examples/sum.kap
|
|
70
|
+
- examples/threading.kap
|
|
67
71
|
- examples/tset.kap
|
|
68
72
|
- examples/two-sum.kap
|
|
69
73
|
- exe/kapfmt
|
|
@@ -93,10 +97,13 @@ files:
|
|
|
93
97
|
- spec/examples_spec.rb
|
|
94
98
|
- spec/formatter_spec.rb
|
|
95
99
|
- spec/spec_helper.rb
|
|
96
|
-
homepage:
|
|
100
|
+
homepage: https://github.com/evmorov/kapusta
|
|
97
101
|
licenses: []
|
|
98
102
|
metadata:
|
|
99
103
|
rubygems_mfa_required: 'true'
|
|
104
|
+
homepage_uri: https://github.com/evmorov/kapusta
|
|
105
|
+
source_code_uri: https://github.com/evmorov/kapusta
|
|
106
|
+
bug_tracker_uri: https://github.com/evmorov/kapusta/issues
|
|
100
107
|
post_install_message:
|
|
101
108
|
rdoc_options: []
|
|
102
109
|
require_paths:
|