kapusta 0.4.1 → 0.7.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 +4 -4
- data/README.md +24 -6
- data/bin/fennel-parity +38 -6
- data/examples/classify-wallet.kap +11 -0
- data/examples/even-squares.kap +22 -7
- data/examples/power-of-three.kap +12 -0
- data/examples/roman-to-integer.kap +3 -3
- data/exe/kapusta-ls +14 -0
- data/kapusta.gemspec +2 -2
- data/lib/kapusta/ast.rb +38 -4
- data/lib/kapusta/cli.rb +3 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +90 -10
- data/lib/kapusta/compiler/emitter/collections.rb +85 -49
- data/lib/kapusta/compiler/emitter/control_flow.rb +31 -6
- data/lib/kapusta/compiler/emitter/expressions.rb +25 -16
- data/lib/kapusta/compiler/emitter/interop.rb +8 -5
- data/lib/kapusta/compiler/emitter/patterns.rb +74 -5
- data/lib/kapusta/compiler/emitter/support.rb +45 -23
- data/lib/kapusta/compiler/emitter.rb +1 -1
- data/lib/kapusta/compiler/lua_compat.rb +149 -0
- data/lib/kapusta/compiler/macro_expander.rb +57 -25
- data/lib/kapusta/compiler/normalizer.rb +39 -28
- data/lib/kapusta/compiler.rb +10 -4
- data/lib/kapusta/error.rb +25 -1
- data/lib/kapusta/errors.rb +70 -0
- data/lib/kapusta/formatter.rb +16 -5
- data/lib/kapusta/lsp.rb +258 -0
- data/lib/kapusta/reader.rb +33 -13
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +1 -0
- data/spec/examples_errors_spec.rb +354 -0
- data/spec/formatter_spec.rb +7 -6
- data/spec/lsp_spec.rb +83 -0
- metadata +10 -2
- data/spec/reader_spec.rb +0 -26
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'open3'
|
|
5
|
+
require 'rbconfig'
|
|
6
|
+
|
|
7
|
+
ERRORS_DIR = File.expand_path('../examples-errors', __dir__)
|
|
8
|
+
KAPUSTA_BIN = File.expand_path('../exe/kapusta', __dir__)
|
|
9
|
+
KAPFMT_BIN = File.expand_path('../exe/kapfmt', __dir__)
|
|
10
|
+
|
|
11
|
+
def run_error_example(name)
|
|
12
|
+
k_out, k_err, k_status = Open3.capture3(RbConfig.ruby, KAPUSTA_BIN, name, chdir: ERRORS_DIR)
|
|
13
|
+
f_out, f_err, f_status = Open3.capture3(RbConfig.ruby, KAPFMT_BIN, name, chdir: ERRORS_DIR)
|
|
14
|
+
|
|
15
|
+
raise "kapusta unexpectedly succeeded for #{name}" if k_status.success?
|
|
16
|
+
raise "kapfmt unexpectedly succeeded for #{name}" if f_status.success?
|
|
17
|
+
raise "kapusta wrote to stdout for #{name}: #{k_out.inspect}" unless k_out.empty?
|
|
18
|
+
raise "kapfmt wrote to stdout for #{name}: #{f_out.inspect}" unless f_out.empty?
|
|
19
|
+
raise "kapusta and kapfmt disagree for #{name}:\n kapusta: #{k_err} kapfmt: #{f_err}" unless k_err == f_err
|
|
20
|
+
|
|
21
|
+
k_err
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
RSpec.describe 'examples-errors' do
|
|
25
|
+
it 'accumulate-missing-iterator.kap' do
|
|
26
|
+
expect(run_error_example('accumulate-missing-iterator.kap'))
|
|
27
|
+
.to eq("accumulate-missing-iterator.kap:5:3: expected initial value and iterator binding table\n")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'auto-gensym-outside-quasiquote.kap' do
|
|
31
|
+
expect(run_error_example('auto-gensym-outside-quasiquote.kap'))
|
|
32
|
+
.to eq("auto-gensym-outside-quasiquote.kap: auto-gensym x# outside quasiquote\n")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'bad-multisym.kap' do
|
|
36
|
+
expect(run_error_example('bad-multisym.kap'))
|
|
37
|
+
.to eq("bad-multisym.kap:1:8: bad multisym: unbound.foo\n")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'bad-set-target.kap' do
|
|
41
|
+
expect(run_error_example('bad-set-target.kap'))
|
|
42
|
+
.to eq("bad-set-target.kap:2:1: bad set target: 1\n")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'bad-shorthand.kap' do
|
|
46
|
+
expect(run_error_example('bad-shorthand.kap'))
|
|
47
|
+
.to eq("bad-shorthand.kap:2:14: bad shorthand\n")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'call-empty-form.kap' do
|
|
51
|
+
expect(run_error_example('call-empty-form.kap'))
|
|
52
|
+
.to eq("call-empty-form.kap:7:8: expected a function, macro, or special to call\n")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'call-literal-number.kap' do
|
|
56
|
+
expect(run_error_example('call-literal-number.kap'))
|
|
57
|
+
.to eq("call-literal-number.kap:6:14: cannot call literal value 1\n")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'cannot-set-method-binding.kap' do
|
|
61
|
+
expect(run_error_example('cannot-set-method-binding.kap'))
|
|
62
|
+
.to eq("cannot-set-method-binding.kap:2:1: cannot set method binding: foo\n")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'case-no-patterns.kap' do
|
|
66
|
+
expect(run_error_example('case-no-patterns.kap'))
|
|
67
|
+
.to eq("case-no-patterns.kap:3:5: expected at least one pattern/body pair\n")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'case-no-subject.kap' do
|
|
71
|
+
expect(run_error_example('case-no-subject.kap'))
|
|
72
|
+
.to eq("case-no-subject.kap:1:1: missing subject\n")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'case-odd-pattern-body.kap' do
|
|
76
|
+
expect(run_error_example('case-odd-pattern-body.kap'))
|
|
77
|
+
.to eq("case-odd-pattern-body.kap:2:3: expected even number of pattern/body pairs\n")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'case-unsupported.kap' do
|
|
81
|
+
expect(run_error_example('case-unsupported.kap'))
|
|
82
|
+
.to eq("case-unsupported.kap:1:1: case/match clauses use patterns this compiler cannot translate\n")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'destructure-literal-number.kap' do
|
|
86
|
+
expect(run_error_example('destructure-literal-number.kap'))
|
|
87
|
+
.to eq("destructure-literal-number.kap:5:3: could not destructure literal\n")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'destructure-literal-table.kap' do
|
|
91
|
+
expect(run_error_example('destructure-literal-table.kap'))
|
|
92
|
+
.to eq("destructure-literal-table.kap:4:1: could not destructure literal\n")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'destructure-rest-as-table.kap' do
|
|
96
|
+
expect(run_error_example('destructure-rest-as-table.kap'))
|
|
97
|
+
.to eq("destructure-rest-as-table.kap:6:3: unable to bind table ...\n")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'dot-without-table.kap' do
|
|
101
|
+
expect(run_error_example('dot-without-table.kap'))
|
|
102
|
+
.to eq("dot-without-table.kap:5:15: expected table argument\n")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'each-not-binding-table.kap' do
|
|
106
|
+
expect(run_error_example('each-not-binding-table.kap'))
|
|
107
|
+
.to eq("each-not-binding-table.kap:6:3: expected binding table\n")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it 'faccumulate-missing-iterator.kap' do
|
|
111
|
+
expect(run_error_example('faccumulate-missing-iterator.kap'))
|
|
112
|
+
.to eq("faccumulate-missing-iterator.kap:7:3: expected initial value and iterator binding table\n")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'fcollect-missing-range.kap' do
|
|
116
|
+
expect(run_error_example('fcollect-missing-range.kap'))
|
|
117
|
+
.to eq("fcollect-missing-range.kap:6:3: expected range to include start and stop\n")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'fn-non-symbol-param.kap' do
|
|
121
|
+
expect(run_error_example('fn-non-symbol-param.kap'))
|
|
122
|
+
.to eq("fn-non-symbol-param.kap:4:1: destructure pattern this compiler cannot translate: [1 2]\n")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'fn-without-params.kap' do
|
|
126
|
+
expect(run_error_example('fn-without-params.kap'))
|
|
127
|
+
.to eq("fn-without-params.kap:4:11: expected parameters table\n")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'for-missing-stop.kap' do
|
|
131
|
+
expect(run_error_example('for-missing-stop.kap'))
|
|
132
|
+
.to eq("for-missing-stop.kap:6:3: expected range to include start and stop\n")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it 'global-non-symbol-name.kap' do
|
|
136
|
+
expect(run_error_example('global-non-symbol-name.kap'))
|
|
137
|
+
.to eq("global-non-symbol-name.kap:6:1: unable to bind integer 1\n")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'global-without-value.kap' do
|
|
141
|
+
expect(run_error_example('global-without-value.kap'))
|
|
142
|
+
.to eq("global-without-value.kap:6:1: expected name and value\n")
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it 'icollect-missing-iterator.kap' do
|
|
146
|
+
expect(run_error_example('icollect-missing-iterator.kap'))
|
|
147
|
+
.to eq("icollect-missing-iterator.kap:6:3: expected iterator binding table\n")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it 'if-no-body.kap' do
|
|
151
|
+
expect(run_error_example('if-no-body.kap'))
|
|
152
|
+
.to eq("if-no-body.kap:8:5: expected condition and body\n")
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it 'import-macros-missing-module.kap' do
|
|
156
|
+
expect(run_error_example('import-macros-missing-module.kap'))
|
|
157
|
+
.to eq("import-macros-missing-module.kap:4:1: import-macros is not yet supported\n")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it 'invalid-class-name.kap' do
|
|
161
|
+
expect(run_error_example('invalid-class-name.kap'))
|
|
162
|
+
.to eq("invalid-class-name.kap: invalid class name: lowercase\n")
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it 'invalid-module-name.kap' do
|
|
166
|
+
expect(run_error_example('invalid-module-name.kap'))
|
|
167
|
+
.to eq("invalid-module-name.kap: invalid module name: lowercase\n")
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it 'let-odd-bindings.kap' do
|
|
171
|
+
expect(run_error_example('let-odd-bindings.kap'))
|
|
172
|
+
.to eq("let-odd-bindings.kap:2:3: expected even number of name/value bindings\n")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it 'let-without-body-form.kap' do
|
|
176
|
+
expect(run_error_example('let-without-body-form.kap'))
|
|
177
|
+
.to eq("let-without-body-form.kap:4:5: expected body expression\n")
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it 'local-with-extra-args.kap' do
|
|
181
|
+
expect(run_error_example('local-with-extra-args.kap'))
|
|
182
|
+
.to eq("local-with-extra-args.kap:6:3: local: expected name and value\n")
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it 'local-without-value.kap' do
|
|
186
|
+
expect(run_error_example('local-without-value.kap'))
|
|
187
|
+
.to eq("local-without-value.kap:6:3: local: expected name and value\n")
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it 'macro-name-must-be-symbol.kap' do
|
|
191
|
+
expect(run_error_example('macro-name-must-be-symbol.kap'))
|
|
192
|
+
.to eq("macro-name-must-be-symbol.kap: macro name must be a symbol\n")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'macro-params-must-be-vector.kap' do
|
|
196
|
+
expect(run_error_example('macro-params-must-be-vector.kap'))
|
|
197
|
+
.to eq("macro-params-must-be-vector.kap:1:12: macro params must be a vector\n")
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it 'macro-unsafe-bind.kap' do
|
|
201
|
+
expect(run_error_example('macro-unsafe-bind.kap'))
|
|
202
|
+
.to eq("macro-unsafe-bind.kap:13:8: macro tried to bind unsafe without gensym\n")
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it 'macro-vararg-with-operator.kap' do
|
|
206
|
+
expect(run_error_example('macro-vararg-with-operator.kap'))
|
|
207
|
+
.to eq("macro-vararg-with-operator.kap:5:3: tried to use vararg with operator\n")
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it 'macros-entry-must-be-fn.kap' do
|
|
211
|
+
expect(run_error_example('macros-entry-must-be-fn.kap'))
|
|
212
|
+
.to eq("macros-entry-must-be-fn.kap: macros entry value must be a fn form, got 1\n")
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'macros-entry-params-must-be-vector.kap' do
|
|
216
|
+
expect(run_error_example('macros-entry-params-must-be-vector.kap'))
|
|
217
|
+
.to eq("macros-entry-params-must-be-vector.kap:1:19: macros entry params must be a vector\n")
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it 'macros-expects-hash.kap' do
|
|
221
|
+
expect(run_error_example('macros-expects-hash.kap'))
|
|
222
|
+
.to eq("macros-expects-hash.kap: macros expects a hash literal\n")
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it 'match-no-patterns.kap' do
|
|
226
|
+
expect(run_error_example('match-no-patterns.kap'))
|
|
227
|
+
.to eq("match-no-patterns.kap:3:5: expected at least one pattern/body pair\n")
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
it 'match-no-subject.kap' do
|
|
231
|
+
expect(run_error_example('match-no-subject.kap'))
|
|
232
|
+
.to eq("match-no-subject.kap:1:1: missing subject\n")
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it 'mismatched-brackets.kap' do
|
|
236
|
+
expect(run_error_example('mismatched-brackets.kap'))
|
|
237
|
+
.to eq("mismatched-brackets.kap:4:19: unexpected closing delimiter ')'\n")
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it 'nested-quasiquote.kap' do
|
|
241
|
+
expect(run_error_example('nested-quasiquote.kap'))
|
|
242
|
+
.to eq("nested-quasiquote.kap: nested quasiquote is not supported\n")
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
it 'odd-forms-in-hash.kap' do
|
|
246
|
+
expect(run_error_example('odd-forms-in-hash.kap'))
|
|
247
|
+
.to eq("odd-forms-in-hash.kap:1:9: odd number of forms in hash\n")
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
it 'only-rest-param.kap' do
|
|
251
|
+
expect(run_error_example('only-rest-param.kap'))
|
|
252
|
+
.to eq("only-rest-param.kap:4:1: expected rest argument before last parameter\n")
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it 'quote-runtime.kap' do
|
|
256
|
+
expect(run_error_example('quote-runtime.kap'))
|
|
257
|
+
.to eq("quote-runtime.kap:6:1: cannot emit form: `hello\n")
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
it 'rest-not-last.kap' do
|
|
261
|
+
expect(run_error_example('rest-not-last.kap'))
|
|
262
|
+
.to eq("rest-not-last.kap:6:3: expected rest argument before last parameter\n")
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
it 'set-immutable-let.kap' do
|
|
266
|
+
expect(run_error_example('set-immutable-let.kap'))
|
|
267
|
+
.to eq("set-immutable-let.kap:2:3: expected var counter\n")
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it 'set-immutable-local.kap' do
|
|
271
|
+
expect(run_error_example('set-immutable-local.kap'))
|
|
272
|
+
.to eq("set-immutable-local.kap:8:3: expected var counter\n")
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
it 'shadow-special-fn.kap' do
|
|
276
|
+
expect(run_error_example('shadow-special-fn.kap'))
|
|
277
|
+
.to eq("shadow-special-fn.kap:6:3: local fn was overshadowed by a special form or macro\n")
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
it 'shadow-special-if.kap' do
|
|
281
|
+
expect(run_error_example('shadow-special-if.kap'))
|
|
282
|
+
.to eq("shadow-special-if.kap:4:1: local if was overshadowed by a special form or macro\n")
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
it 'symbol-starting-with-digit.kap' do
|
|
286
|
+
expect(run_error_example('symbol-starting-with-digit.kap'))
|
|
287
|
+
.to eq("symbol-starting-with-digit.kap:6:10: could not read number \"5var\"\n")
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
it 'tset-missing-value.kap' do
|
|
291
|
+
expect(run_error_example('tset-missing-value.kap'))
|
|
292
|
+
.to eq("tset-missing-value.kap:5:5: tset: expected table, key, and value arguments\n")
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
it 'unbalanced-parens.kap' do
|
|
296
|
+
expect(run_error_example('unbalanced-parens.kap'))
|
|
297
|
+
.to eq("unbalanced-parens.kap:4:1: unclosed opening delimiter '('\n")
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
it 'unclosed-table.kap' do
|
|
301
|
+
expect(run_error_example('unclosed-table.kap'))
|
|
302
|
+
.to eq("unclosed-table.kap:4:21: unexpected closing delimiter ']'\n")
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
it 'undefined-symbol.kap' do
|
|
306
|
+
expect(run_error_example('undefined-symbol.kap'))
|
|
307
|
+
.to eq("undefined-symbol.kap:1:8: undefined symbol: missing-symbol\n")
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
it 'unexpected-eof.kap' do
|
|
311
|
+
expect(run_error_example('unexpected-eof.kap'))
|
|
312
|
+
.to eq("unexpected-eof.kap:1:2: unexpected eof\n")
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
it 'unexpected-vararg.kap' do
|
|
316
|
+
expect(run_error_example('unexpected-vararg.kap'))
|
|
317
|
+
.to eq("unexpected-vararg.kap:2:10: unexpected vararg\n")
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
it 'unknown-special-form.kap' do
|
|
321
|
+
expect(run_error_example('unknown-special-form.kap'))
|
|
322
|
+
.to eq("unknown-special-form.kap:1:1: unknown special form: catch\n")
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
it 'unquote-outside-quote.kap' do
|
|
326
|
+
expect(run_error_example('unquote-outside-quote.kap'))
|
|
327
|
+
.to eq("unquote-outside-quote.kap:5:10: cannot emit form: ,x\n")
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
it 'unquote-splice-outside-list.kap' do
|
|
331
|
+
expect(run_error_example('unquote-splice-outside-list.kap'))
|
|
332
|
+
.to eq("unquote-splice-outside-list.kap: unquote-splice must appear inside a quoted list/vec\n")
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
it 'unterminated-string.kap' do
|
|
336
|
+
expect(run_error_example('unterminated-string.kap'))
|
|
337
|
+
.to eq("unterminated-string.kap:1:8: unterminated string\n")
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
it 'vararg-not-last.kap' do
|
|
341
|
+
expect(run_error_example('vararg-not-last.kap'))
|
|
342
|
+
.to eq("vararg-not-last.kap:1:1: expected vararg as last parameter\n")
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
it 'var-without-value.kap' do
|
|
346
|
+
expect(run_error_example('var-without-value.kap'))
|
|
347
|
+
.to eq("var-without-value.kap:6:3: var: expected name and value\n")
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
it 'when-no-body.kap' do
|
|
351
|
+
expect(run_error_example('when-no-body.kap'))
|
|
352
|
+
.to eq("when-no-body.kap:4:3: when: expected body\n")
|
|
353
|
+
end
|
|
354
|
+
end
|
data/spec/formatter_spec.rb
CHANGED
|
@@ -43,7 +43,7 @@ RSpec.describe Kapusta::Formatter do
|
|
|
43
43
|
Dir.mktmpdir do |dir|
|
|
44
44
|
path = File.join(dir, 'sample.kap')
|
|
45
45
|
File.write(path, <<~KAP)
|
|
46
|
-
(let [uri (URI.join (ivar base-uri) (.. "/posts/" id)) body (Net.HTTP.get uri) post (JSON.parse body {:symbolize-names true}) {: title : author} post] (values title author))
|
|
46
|
+
(fn fetch-post [id] (let [uri (URI.join (ivar base-uri) (.. "/posts/" id)) body (Net.HTTP.get uri) post (JSON.parse body {:symbolize-names true}) {: title : author} post] (values title author)))
|
|
47
47
|
KAP
|
|
48
48
|
|
|
49
49
|
previous_path = ENV.fetch('PATH', nil)
|
|
@@ -54,11 +54,12 @@ RSpec.describe Kapusta::Formatter do
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
expect(output).to eq(<<~KAP)
|
|
57
|
-
(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
(fn fetch-post [id]
|
|
58
|
+
(let [uri (URI.join (ivar base-uri) (.. "/posts/" id))
|
|
59
|
+
body (Net.HTTP.get uri)
|
|
60
|
+
post (JSON.parse body {:symbolize-names true})
|
|
61
|
+
{: title : author} post]
|
|
62
|
+
(values title author)))
|
|
62
63
|
KAP
|
|
63
64
|
ensure
|
|
64
65
|
ENV['PATH'] = previous_path
|
data/spec/lsp_spec.rb
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'kapusta/lsp'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'stringio'
|
|
7
|
+
|
|
8
|
+
RSpec.describe Kapusta::LSP do
|
|
9
|
+
def frame(payload)
|
|
10
|
+
body = JSON.generate(payload)
|
|
11
|
+
"Content-Length: #{body.bytesize}\r\n\r\n#{body}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def parse_responses(stdout)
|
|
15
|
+
messages = []
|
|
16
|
+
rest = stdout.dup
|
|
17
|
+
while (m = rest.match(/\AContent-Length: (\d+)\r\n\r\n/))
|
|
18
|
+
len = Integer(m[1], 10)
|
|
19
|
+
messages << JSON.parse(rest[m[0].length, len])
|
|
20
|
+
rest = rest[(m[0].length + len)..]
|
|
21
|
+
end
|
|
22
|
+
messages
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def run(*frames)
|
|
26
|
+
input = StringIO.new(frames.join)
|
|
27
|
+
output = StringIO.new
|
|
28
|
+
log = StringIO.new
|
|
29
|
+
described_class.new(input:, output:, log:).run
|
|
30
|
+
parse_responses(output.string)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'advertises diagnostics and formatting capabilities on initialize' do
|
|
34
|
+
responses = run(frame(jsonrpc: '2.0', id: 1, method: 'initialize', params: {}))
|
|
35
|
+
capabilities = responses.first.dig('result', 'capabilities')
|
|
36
|
+
|
|
37
|
+
expect(capabilities).to include('textDocumentSync', 'documentFormattingProvider')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'publishes diagnostics for invalid source' do
|
|
41
|
+
responses = run(
|
|
42
|
+
frame(jsonrpc: '2.0', id: 1, method: 'initialize', params: {}),
|
|
43
|
+
frame(jsonrpc: '2.0', method: 'textDocument/didOpen',
|
|
44
|
+
params: { textDocument: { uri: 'file:///x.kap', version: 1, text: '(let [x 1] (+ x ()))' } })
|
|
45
|
+
)
|
|
46
|
+
diagnostics = responses.last.dig('params', 'diagnostics')
|
|
47
|
+
|
|
48
|
+
expect(diagnostics).not_to be_empty
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'publishes no diagnostics for valid source' do
|
|
52
|
+
responses = run(
|
|
53
|
+
frame(jsonrpc: '2.0', id: 1, method: 'initialize', params: {}),
|
|
54
|
+
frame(jsonrpc: '2.0', method: 'textDocument/didOpen',
|
|
55
|
+
params: { textDocument: { uri: 'file:///x.kap', version: 1, text: '(print "hi")' } })
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
expect(responses.last.dig('params', 'diagnostics')).to be_empty
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'returns a TextEdit for formatting' do
|
|
62
|
+
responses = run(
|
|
63
|
+
frame(jsonrpc: '2.0', id: 1, method: 'initialize', params: {}),
|
|
64
|
+
frame(jsonrpc: '2.0', method: 'textDocument/didOpen',
|
|
65
|
+
params: { textDocument: { uri: 'file:///x.kap', version: 1, text: "(fn greet [x] (print x))\n" } }),
|
|
66
|
+
frame(jsonrpc: '2.0', id: 2, method: 'textDocument/formatting',
|
|
67
|
+
params: { textDocument: { uri: 'file:///x.kap' } })
|
|
68
|
+
)
|
|
69
|
+
edits = responses.find { |m| m['id'] == 2 }['result']
|
|
70
|
+
|
|
71
|
+
expect(edits).to be_an(Array).and(be_one)
|
|
72
|
+
expect(edits.first).to include('range', 'newText')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'rejects requests sent before initialize' do
|
|
76
|
+
responses = run(
|
|
77
|
+
frame(jsonrpc: '2.0', id: 1, method: 'textDocument/formatting',
|
|
78
|
+
params: { textDocument: { uri: 'file:///x.kap' } })
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
expect(responses.first.dig('error', 'code')).to eq(-32_002)
|
|
82
|
+
end
|
|
83
|
+
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.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Evgenii Morozov
|
|
@@ -13,6 +13,7 @@ description: Kapusta is a Lisp for the Ruby runtime.
|
|
|
13
13
|
executables:
|
|
14
14
|
- kapfmt
|
|
15
15
|
- kapusta
|
|
16
|
+
- kapusta-ls
|
|
16
17
|
extensions: []
|
|
17
18
|
extra_rdoc_files: []
|
|
18
19
|
files:
|
|
@@ -37,6 +38,7 @@ files:
|
|
|
37
38
|
- examples/block-sort.kap
|
|
38
39
|
- examples/blocks-and-kwargs.kap
|
|
39
40
|
- examples/calc.kap
|
|
41
|
+
- examples/classify-wallet.kap
|
|
40
42
|
- examples/climbing-stairs.kap
|
|
41
43
|
- examples/contains-duplicate.kap
|
|
42
44
|
- examples/counter.kap
|
|
@@ -80,6 +82,7 @@ files:
|
|
|
80
82
|
- examples/pivot-index.kap
|
|
81
83
|
- examples/plus-one.kap
|
|
82
84
|
- examples/points.kap
|
|
85
|
+
- examples/power-of-three.kap
|
|
83
86
|
- examples/primes.kap
|
|
84
87
|
- examples/raindrops.kap
|
|
85
88
|
- examples/record.kap
|
|
@@ -109,6 +112,7 @@ files:
|
|
|
109
112
|
- examples/zoo-animal-inheritance-2.kap
|
|
110
113
|
- exe/kapfmt
|
|
111
114
|
- exe/kapusta
|
|
115
|
+
- exe/kapusta-ls
|
|
112
116
|
- kapusta.gemspec
|
|
113
117
|
- lib/kapusta.rb
|
|
114
118
|
- lib/kapusta/ast.rb
|
|
@@ -122,18 +126,22 @@ files:
|
|
|
122
126
|
- lib/kapusta/compiler/emitter/interop.rb
|
|
123
127
|
- lib/kapusta/compiler/emitter/patterns.rb
|
|
124
128
|
- lib/kapusta/compiler/emitter/support.rb
|
|
129
|
+
- lib/kapusta/compiler/lua_compat.rb
|
|
125
130
|
- lib/kapusta/compiler/macro_expander.rb
|
|
126
131
|
- lib/kapusta/compiler/normalizer.rb
|
|
127
132
|
- lib/kapusta/env.rb
|
|
128
133
|
- lib/kapusta/error.rb
|
|
134
|
+
- lib/kapusta/errors.rb
|
|
129
135
|
- lib/kapusta/formatter.rb
|
|
136
|
+
- lib/kapusta/lsp.rb
|
|
130
137
|
- lib/kapusta/reader.rb
|
|
131
138
|
- lib/kapusta/support.rb
|
|
132
139
|
- lib/kapusta/version.rb
|
|
133
140
|
- spec/cli_spec.rb
|
|
141
|
+
- spec/examples_errors_spec.rb
|
|
134
142
|
- spec/examples_spec.rb
|
|
135
143
|
- spec/formatter_spec.rb
|
|
136
|
-
- spec/
|
|
144
|
+
- spec/lsp_spec.rb
|
|
137
145
|
- spec/spec_helper.rb
|
|
138
146
|
homepage: https://github.com/evmorov/kapusta
|
|
139
147
|
licenses:
|
data/spec/reader_spec.rb
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe Kapusta::Reader do
|
|
6
|
-
describe 'error messages' do
|
|
7
|
-
it 'reports unexpected closing delimiters with their source position' do
|
|
8
|
-
expect { Kapusta.eval('(print 1))') }
|
|
9
|
-
.to raise_error(Kapusta::Reader::Error,
|
|
10
|
-
/unexpected closing delimiter '\)' at line 1, column 10/)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
it 'reports unclosed opening delimiters with their source position' do
|
|
14
|
-
cases = {
|
|
15
|
-
'(print 1' => /unclosed opening delimiter '\(' at line 1, column 1/,
|
|
16
|
-
'[1 2' => /unclosed opening delimiter '\[' at line 1, column 1/,
|
|
17
|
-
'{:name "A"' => /unclosed opening delimiter '\{' at line 1, column 1/
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
cases.each do |source, message|
|
|
21
|
-
expect { Kapusta.eval(source) }
|
|
22
|
-
.to raise_error(Kapusta::Reader::Error, message)
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|