tree-sitter-bynk 0.55.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.
- package/binding.gyp +30 -0
- package/bindings/node/binding.cc +20 -0
- package/bindings/node/binding_test.js +9 -0
- package/bindings/node/index.d.ts +28 -0
- package/bindings/node/index.js +11 -0
- package/grammar.js +1062 -0
- package/package.json +62 -0
- package/queries/highlights.scm +176 -0
- package/queries/injections.scm +1 -0
- package/src/grammar.json +5573 -0
- package/src/node-types.json +6801 -0
- package/src/parser.c +48207 -0
- package/src/scanner.c +156 -0
- package/src/tree_sitter/alloc.h +54 -0
- package/src/tree_sitter/array.h +291 -0
- package/src/tree_sitter/parser.h +266 -0
- package/tree-sitter.json +28 -0
package/grammar.js
ADDED
|
@@ -0,0 +1,1062 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tree-sitter grammar for Bynk.
|
|
3
|
+
*
|
|
4
|
+
* Covers the syntactic surface defined by the normative specification
|
|
5
|
+
* (docs/src/spec/, §3–§4), which is generated from this grammar and kept
|
|
6
|
+
* current per increment. Implements the highlighting / structural shape the
|
|
7
|
+
* editor needs; semantic rules (type checking, exhaustiveness, effect
|
|
8
|
+
* propagation, `given` matching) are intentionally left to the LSP.
|
|
9
|
+
*
|
|
10
|
+
* The grammar is permissive in the places where the type checker would
|
|
11
|
+
* reject code anyway — e.g., `capability` declarations parse inside any
|
|
12
|
+
* declaration body; the LSP surfaces the placement error.
|
|
13
|
+
*
|
|
14
|
+
* Validated by parsing the bynkc fixture corpus (tests/fixtures) to zero
|
|
15
|
+
* ERROR/MISSING nodes.
|
|
16
|
+
*
|
|
17
|
+
* @author Bynk project
|
|
18
|
+
* @license see project root
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/// <reference types="tree-sitter-cli/dsl" />
|
|
22
|
+
// @ts-check
|
|
23
|
+
|
|
24
|
+
const PREC = {
|
|
25
|
+
assert: 0,
|
|
26
|
+
or: 1,
|
|
27
|
+
and: 2,
|
|
28
|
+
is: 3,
|
|
29
|
+
cmp: 4,
|
|
30
|
+
rel: 5,
|
|
31
|
+
add: 6,
|
|
32
|
+
mul: 7,
|
|
33
|
+
unary: 8,
|
|
34
|
+
postfix: 9,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
module.exports = grammar({
|
|
38
|
+
name: "bynk",
|
|
39
|
+
|
|
40
|
+
externals: ($) => [$.doc_block],
|
|
41
|
+
|
|
42
|
+
extras: ($) => [/\s+/, $.line_comment, $.doc_block],
|
|
43
|
+
|
|
44
|
+
// The DSL allows reserved words to be referenced inside specific rules via
|
|
45
|
+
// tokens; we declare keywords so they take precedence over `identifier`.
|
|
46
|
+
word: ($) => $.identifier,
|
|
47
|
+
|
|
48
|
+
conflicts: ($) => [
|
|
49
|
+
// `match e { … }` / `if e { … }` / `commit e`: after a bare identifier `e`,
|
|
50
|
+
// the `{` is ambiguous between opening the match/if body and opening a
|
|
51
|
+
// record construction/spread `e { … }`. Keep all parses alive; the body
|
|
52
|
+
// content (match arms / block statements vs field inits) decides which one
|
|
53
|
+
// survives at parse time.
|
|
54
|
+
[$.record_construction, $.record_spread, $._primary],
|
|
55
|
+
// v0.20a: `(x …` is ambiguous between a lambda parameter list and a
|
|
56
|
+
// parenthesised expression (and `()` the unit literal) until the
|
|
57
|
+
// closing `)` is followed — or not — by `=>`.
|
|
58
|
+
[$.lambda_param, $._primary],
|
|
59
|
+
[$._primary, $.lambda_expr],
|
|
60
|
+
],
|
|
61
|
+
|
|
62
|
+
rules: {
|
|
63
|
+
// -- Top level --
|
|
64
|
+
|
|
65
|
+
// A source file is normally one or more top-level units. To keep the
|
|
66
|
+
// highlighter from painting every documentation snippet as one big ERROR,
|
|
67
|
+
// we also parse *fragments* — pieces lifted out of their enclosing unit,
|
|
68
|
+
// which never occur in a real `.bynk` file but appear throughout the docs
|
|
69
|
+
// and in editor scratch buffers. The LSP still flags the structural
|
|
70
|
+
// placement error semantically.
|
|
71
|
+
//
|
|
72
|
+
// The three branches are disjoint on their first token, so no real input
|
|
73
|
+
// is ambiguous between them: a unit opens with `commons`/`context`/`test`;
|
|
74
|
+
// an item fragment opens with another declaration keyword (`type`, `fn`,
|
|
75
|
+
// `service`, `on`, …); and a statement/expression fragment opens with a
|
|
76
|
+
// value keyword (`let`, `if`, `match`, `assert`) or a bare term. Keeping
|
|
77
|
+
// item and expression fragments in separate branches (rather than one
|
|
78
|
+
// mixed `repeat1`) avoids spurious boundary conflicts where a completed
|
|
79
|
+
// declaration abuts a following parenthesised expression.
|
|
80
|
+
source_file: ($) =>
|
|
81
|
+
choice(
|
|
82
|
+
repeat1(
|
|
83
|
+
choice(
|
|
84
|
+
$.commons_decl,
|
|
85
|
+
$.context_decl,
|
|
86
|
+
$.adapter_decl,
|
|
87
|
+
$.integration_decl,
|
|
88
|
+
$.test_decl,
|
|
89
|
+
),
|
|
90
|
+
),
|
|
91
|
+
repeat1($._item_fragment),
|
|
92
|
+
$._expr_fragment,
|
|
93
|
+
),
|
|
94
|
+
|
|
95
|
+
_item_fragment: ($) =>
|
|
96
|
+
choice($._context_body_item, $.handler, $.state_decl, $.key_decl),
|
|
97
|
+
|
|
98
|
+
// The body of a fragment block: statements then an optional tail value,
|
|
99
|
+
// mirroring `block` so multi-statement and bare-expression snippets parse.
|
|
100
|
+
// Spelled as a non-empty choice so the rule never matches the empty input.
|
|
101
|
+
_expr_fragment: ($) =>
|
|
102
|
+
choice(
|
|
103
|
+
seq(repeat1($._statement), optional(field("tail", $._expression))),
|
|
104
|
+
field("tail", $._expression),
|
|
105
|
+
),
|
|
106
|
+
|
|
107
|
+
commons_decl: ($) =>
|
|
108
|
+
seq(
|
|
109
|
+
"commons",
|
|
110
|
+
field("name", $.qualified_name),
|
|
111
|
+
choice(
|
|
112
|
+
seq("{", repeat($._commons_body_item), "}"),
|
|
113
|
+
// Fragment form: header followed by items to EOF (or the next
|
|
114
|
+
// top-level header).
|
|
115
|
+
repeat($._commons_body_item),
|
|
116
|
+
),
|
|
117
|
+
),
|
|
118
|
+
|
|
119
|
+
context_decl: ($) =>
|
|
120
|
+
seq(
|
|
121
|
+
"context",
|
|
122
|
+
field("name", $.qualified_name),
|
|
123
|
+
choice(
|
|
124
|
+
seq("{", repeat($._context_body_item), "}"),
|
|
125
|
+
repeat($._context_body_item),
|
|
126
|
+
),
|
|
127
|
+
),
|
|
128
|
+
|
|
129
|
+
// v0.17: an adapter — the host boundary (capability contract + binding).
|
|
130
|
+
// Brace or fragment form, like commons/context.
|
|
131
|
+
adapter_decl: ($) =>
|
|
132
|
+
seq(
|
|
133
|
+
"adapter",
|
|
134
|
+
field("name", $.qualified_name),
|
|
135
|
+
choice(
|
|
136
|
+
seq("{", repeat($._adapter_body_item), "}"),
|
|
137
|
+
repeat($._adapter_body_item),
|
|
138
|
+
),
|
|
139
|
+
),
|
|
140
|
+
|
|
141
|
+
// v0.7: test unit targeting a commons or context. Brace or fragment form
|
|
142
|
+
// (a test case is `test "string" …`, so it never collides with a new
|
|
143
|
+
// `test <qualified_name>` unit — but the disambiguation needs one extra
|
|
144
|
+
// token of lookahead, handled by a declared conflict).
|
|
145
|
+
test_decl: ($) =>
|
|
146
|
+
prec.right(
|
|
147
|
+
seq(
|
|
148
|
+
"test",
|
|
149
|
+
field("target", $.qualified_name),
|
|
150
|
+
choice(
|
|
151
|
+
seq("{", repeat($._test_body_item), "}"),
|
|
152
|
+
repeat($._test_body_item),
|
|
153
|
+
),
|
|
154
|
+
),
|
|
155
|
+
),
|
|
156
|
+
|
|
157
|
+
// v0.16: a multi-Worker integration test. `integration` is contextual
|
|
158
|
+
// after `test` and before the suite-name string; `wires` lists the
|
|
159
|
+
// participating contexts. Body holds `uses` and `test "name"` cases (no
|
|
160
|
+
// `mocks` — integration tests wire real implementations).
|
|
161
|
+
integration_decl: ($) =>
|
|
162
|
+
prec.right(
|
|
163
|
+
seq(
|
|
164
|
+
"test",
|
|
165
|
+
"integration",
|
|
166
|
+
field("name", $.string_literal),
|
|
167
|
+
choice(
|
|
168
|
+
seq("{", $.wires_decl, repeat($._integration_body_item), "}"),
|
|
169
|
+
seq($.wires_decl, repeat($._integration_body_item)),
|
|
170
|
+
),
|
|
171
|
+
),
|
|
172
|
+
),
|
|
173
|
+
|
|
174
|
+
wires_decl: ($) => seq("wires", sep1(field("participant", $.qualified_name), ",")),
|
|
175
|
+
|
|
176
|
+
_integration_body_item: ($) => choice($.uses_decl, $.test_case),
|
|
177
|
+
|
|
178
|
+
qualified_name: ($) => sep1($.identifier, "."),
|
|
179
|
+
|
|
180
|
+
_commons_body_item: ($) =>
|
|
181
|
+
choice(
|
|
182
|
+
$.uses_decl,
|
|
183
|
+
$.type_decl,
|
|
184
|
+
$.fn_decl,
|
|
185
|
+
// Permissive: capability/service/etc. can syntactically appear
|
|
186
|
+
// anywhere — the LSP reports semantic placement errors.
|
|
187
|
+
$.capability_decl,
|
|
188
|
+
$.provider_decl,
|
|
189
|
+
$.service_decl,
|
|
190
|
+
$.agent_decl,
|
|
191
|
+
$.actor_decl,
|
|
192
|
+
),
|
|
193
|
+
|
|
194
|
+
_context_body_item: ($) =>
|
|
195
|
+
choice(
|
|
196
|
+
$.uses_decl,
|
|
197
|
+
$.consumes_decl,
|
|
198
|
+
$.exports_decl,
|
|
199
|
+
$.type_decl,
|
|
200
|
+
$.fn_decl,
|
|
201
|
+
$.capability_decl,
|
|
202
|
+
$.provider_decl,
|
|
203
|
+
$.service_decl,
|
|
204
|
+
$.agent_decl,
|
|
205
|
+
$.actor_decl,
|
|
206
|
+
),
|
|
207
|
+
|
|
208
|
+
// v0.17: adapter items — a binding clause, capabilities, boundary types,
|
|
209
|
+
// inline pure helpers and `uses`, external providers, and `exports`.
|
|
210
|
+
// v0.18 adds `consumes` (adapter-to-adapter capability dependencies).
|
|
211
|
+
// Permissive (service/agent parse here too); the LSP reports the semantic
|
|
212
|
+
// placement error.
|
|
213
|
+
_adapter_body_item: ($) =>
|
|
214
|
+
choice(
|
|
215
|
+
$.binding_decl,
|
|
216
|
+
$.uses_decl,
|
|
217
|
+
$.consumes_decl,
|
|
218
|
+
$.exports_decl,
|
|
219
|
+
$.type_decl,
|
|
220
|
+
$.fn_decl,
|
|
221
|
+
$.capability_decl,
|
|
222
|
+
$.provider_decl,
|
|
223
|
+
$.service_decl,
|
|
224
|
+
$.agent_decl,
|
|
225
|
+
$.actor_decl,
|
|
226
|
+
),
|
|
227
|
+
|
|
228
|
+
_test_body_item: ($) =>
|
|
229
|
+
choice($.uses_decl, $.consumes_decl, $.mocks_decl, $.test_case),
|
|
230
|
+
|
|
231
|
+
// -- Headers / clauses --
|
|
232
|
+
|
|
233
|
+
uses_decl: ($) => seq("uses", field("target", $.qualified_name)),
|
|
234
|
+
consumes_decl: ($) =>
|
|
235
|
+
seq(
|
|
236
|
+
"consumes",
|
|
237
|
+
field("target", $.qualified_name),
|
|
238
|
+
optional(
|
|
239
|
+
choice(
|
|
240
|
+
// v0.6: `consumes a.b as Alias`.
|
|
241
|
+
seq("as", field("alias", $.identifier)),
|
|
242
|
+
// v0.17: `consumes a.b { Cap, … }` — selected capabilities.
|
|
243
|
+
seq(
|
|
244
|
+
"{",
|
|
245
|
+
optional(sep1(field("capability", $.identifier), ",")),
|
|
246
|
+
optional(","),
|
|
247
|
+
"}",
|
|
248
|
+
),
|
|
249
|
+
),
|
|
250
|
+
),
|
|
251
|
+
),
|
|
252
|
+
|
|
253
|
+
// v0.17: an adapter's binding module + optional npm dependency map.
|
|
254
|
+
binding_decl: ($) =>
|
|
255
|
+
seq(
|
|
256
|
+
"binding",
|
|
257
|
+
field("module", $.string_literal),
|
|
258
|
+
optional(
|
|
259
|
+
seq(
|
|
260
|
+
"requires",
|
|
261
|
+
"{",
|
|
262
|
+
optional(sep1($.binding_requirement, ",")),
|
|
263
|
+
optional(","),
|
|
264
|
+
"}",
|
|
265
|
+
),
|
|
266
|
+
),
|
|
267
|
+
),
|
|
268
|
+
binding_requirement: ($) =>
|
|
269
|
+
seq(field("package", $.string_literal), ":", field("range", $.string_literal)),
|
|
270
|
+
exports_decl: ($) =>
|
|
271
|
+
seq(
|
|
272
|
+
"exports",
|
|
273
|
+
// v0.15: `capability` joins the type-visibility keywords here.
|
|
274
|
+
field("kind", choice("opaque", "transparent", "capability")),
|
|
275
|
+
"{",
|
|
276
|
+
optional(sep1(field("name", $.identifier), ",")),
|
|
277
|
+
optional(","),
|
|
278
|
+
"}",
|
|
279
|
+
),
|
|
280
|
+
|
|
281
|
+
// -- Type declarations --
|
|
282
|
+
|
|
283
|
+
type_decl: ($) =>
|
|
284
|
+
seq(
|
|
285
|
+
"type",
|
|
286
|
+
field("name", $.identifier),
|
|
287
|
+
"=",
|
|
288
|
+
field("body", $._type_body),
|
|
289
|
+
),
|
|
290
|
+
|
|
291
|
+
_type_body: ($) =>
|
|
292
|
+
choice(
|
|
293
|
+
$.opaque_type,
|
|
294
|
+
$.refined_type,
|
|
295
|
+
$.record_type,
|
|
296
|
+
$.sum_type,
|
|
297
|
+
$.enum_type,
|
|
298
|
+
),
|
|
299
|
+
|
|
300
|
+
opaque_type: ($) =>
|
|
301
|
+
seq(
|
|
302
|
+
"opaque",
|
|
303
|
+
field("base", $._base_type),
|
|
304
|
+
optional(seq("where", field("refinement", $.refinement))),
|
|
305
|
+
),
|
|
306
|
+
|
|
307
|
+
refined_type: ($) =>
|
|
308
|
+
prec(
|
|
309
|
+
1,
|
|
310
|
+
seq(
|
|
311
|
+
field("base", $._base_type),
|
|
312
|
+
optional(seq("where", field("refinement", $.refinement))),
|
|
313
|
+
),
|
|
314
|
+
),
|
|
315
|
+
|
|
316
|
+
record_type: ($) =>
|
|
317
|
+
seq(
|
|
318
|
+
"{",
|
|
319
|
+
optional(sep1($.record_field, ",")),
|
|
320
|
+
optional(","),
|
|
321
|
+
"}",
|
|
322
|
+
),
|
|
323
|
+
|
|
324
|
+
record_field: ($) =>
|
|
325
|
+
seq(
|
|
326
|
+
field("name", $.identifier),
|
|
327
|
+
":",
|
|
328
|
+
field("type", $._type_ref),
|
|
329
|
+
optional(seq("where", field("refinement", $.refinement))),
|
|
330
|
+
// v0.11: an optional initial-value expression. Meaningful on agent
|
|
331
|
+
// `state` fields; the checker restricts where it applies.
|
|
332
|
+
optional(seq("=", field("init", $._expression))),
|
|
333
|
+
),
|
|
334
|
+
|
|
335
|
+
sum_type: ($) => prec.right(repeat1($.sum_variant)),
|
|
336
|
+
sum_variant: ($) =>
|
|
337
|
+
seq(
|
|
338
|
+
"|",
|
|
339
|
+
field("name", $.constant_name),
|
|
340
|
+
optional(
|
|
341
|
+
seq(
|
|
342
|
+
"(",
|
|
343
|
+
optional(sep1($.variant_payload_field, ",")),
|
|
344
|
+
optional(","),
|
|
345
|
+
")",
|
|
346
|
+
),
|
|
347
|
+
),
|
|
348
|
+
),
|
|
349
|
+
variant_payload_field: ($) =>
|
|
350
|
+
seq(
|
|
351
|
+
field("name", $.identifier),
|
|
352
|
+
":",
|
|
353
|
+
field("type", $._type_ref),
|
|
354
|
+
),
|
|
355
|
+
|
|
356
|
+
enum_type: ($) =>
|
|
357
|
+
seq(
|
|
358
|
+
"enum",
|
|
359
|
+
"{",
|
|
360
|
+
optional(sep1(field("variant", $.constant_name), ",")),
|
|
361
|
+
optional(","),
|
|
362
|
+
"}",
|
|
363
|
+
),
|
|
364
|
+
|
|
365
|
+
refinement: ($) => sep1($._refinement_pred, "and"),
|
|
366
|
+
_refinement_pred: ($) =>
|
|
367
|
+
choice(
|
|
368
|
+
$.pred_call,
|
|
369
|
+
$.pred_atom,
|
|
370
|
+
),
|
|
371
|
+
pred_call: ($) =>
|
|
372
|
+
seq(field("name", $.predicate_name), "(", optional(sep1($._pred_arg, ",")), ")"),
|
|
373
|
+
pred_atom: ($) => $.predicate_name,
|
|
374
|
+
predicate_name: ($) =>
|
|
375
|
+
choice(
|
|
376
|
+
"Matches",
|
|
377
|
+
"InRange",
|
|
378
|
+
"MinLength",
|
|
379
|
+
"MaxLength",
|
|
380
|
+
"Length",
|
|
381
|
+
"NonNegative",
|
|
382
|
+
"Positive",
|
|
383
|
+
"NonEmpty",
|
|
384
|
+
),
|
|
385
|
+
_pred_arg: ($) => choice($.number_literal, $.float_literal, $.string_literal),
|
|
386
|
+
|
|
387
|
+
_base_type: ($) => $.base_type,
|
|
388
|
+
base_type: () => choice("Int", "String", "Bool", "Float"),
|
|
389
|
+
|
|
390
|
+
_type_ref: ($) =>
|
|
391
|
+
choice(
|
|
392
|
+
$.function_type_ref,
|
|
393
|
+
$._base_type,
|
|
394
|
+
$.unit_type,
|
|
395
|
+
$.validation_error_type,
|
|
396
|
+
$.generic_type_ref,
|
|
397
|
+
$.identifier,
|
|
398
|
+
),
|
|
399
|
+
|
|
400
|
+
// v0.20a: a function type — `A -> B`, `(A, B) -> C`, `() -> B`,
|
|
401
|
+
// right-associative (prec.right). A parenthesised parameter list is only
|
|
402
|
+
// a function type when followed by `->`; bare `()` stays the unit type.
|
|
403
|
+
function_type_ref: ($) =>
|
|
404
|
+
prec.right(
|
|
405
|
+
seq(
|
|
406
|
+
field(
|
|
407
|
+
"params",
|
|
408
|
+
choice(
|
|
409
|
+
$._base_type,
|
|
410
|
+
$.unit_type,
|
|
411
|
+
$.validation_error_type,
|
|
412
|
+
$.generic_type_ref,
|
|
413
|
+
$.identifier,
|
|
414
|
+
seq("(", sep1($._type_ref, ","), optional(","), ")"),
|
|
415
|
+
),
|
|
416
|
+
),
|
|
417
|
+
"->",
|
|
418
|
+
field("return_type", $._type_ref),
|
|
419
|
+
),
|
|
420
|
+
),
|
|
421
|
+
|
|
422
|
+
unit_type: () => seq("(", ")"),
|
|
423
|
+
validation_error_type: () => "ValidationError",
|
|
424
|
+
generic_type_ref: ($) =>
|
|
425
|
+
seq(
|
|
426
|
+
field(
|
|
427
|
+
"name",
|
|
428
|
+
choice(
|
|
429
|
+
alias("Result", $.builtin_type),
|
|
430
|
+
alias("Option", $.builtin_type),
|
|
431
|
+
alias("Effect", $.builtin_type),
|
|
432
|
+
// v0.9: HTTP result type.
|
|
433
|
+
alias("HttpResult", $.builtin_type),
|
|
434
|
+
// v0.20b: the built-in collection types.
|
|
435
|
+
alias("List", $.builtin_type),
|
|
436
|
+
alias("Map", $.builtin_type),
|
|
437
|
+
),
|
|
438
|
+
),
|
|
439
|
+
"[",
|
|
440
|
+
sep1(field("arg", $._type_ref), ","),
|
|
441
|
+
"]",
|
|
442
|
+
),
|
|
443
|
+
|
|
444
|
+
// -- Function declarations --
|
|
445
|
+
|
|
446
|
+
fn_decl: ($) =>
|
|
447
|
+
seq(
|
|
448
|
+
"fn",
|
|
449
|
+
field("name", choice($.method_name, $.identifier)),
|
|
450
|
+
// v0.20a: optional `[A, B]` type parameters (functions only).
|
|
451
|
+
optional(seq("[", sep1(field("type_param", $.identifier), ","), "]")),
|
|
452
|
+
"(",
|
|
453
|
+
optional($._params),
|
|
454
|
+
")",
|
|
455
|
+
"->",
|
|
456
|
+
field("return_type", $._type_ref),
|
|
457
|
+
field("body", $.block),
|
|
458
|
+
),
|
|
459
|
+
method_name: ($) =>
|
|
460
|
+
seq(field("type", $.identifier), ".", field("method", $.identifier)),
|
|
461
|
+
|
|
462
|
+
_params: ($) =>
|
|
463
|
+
seq(
|
|
464
|
+
choice($.self_param, $.param),
|
|
465
|
+
repeat(seq(",", $.param)),
|
|
466
|
+
optional(","),
|
|
467
|
+
),
|
|
468
|
+
self_param: () => "self",
|
|
469
|
+
param: ($) =>
|
|
470
|
+
seq(
|
|
471
|
+
field("name", $.identifier),
|
|
472
|
+
":",
|
|
473
|
+
field("type", $._type_ref),
|
|
474
|
+
),
|
|
475
|
+
|
|
476
|
+
// -- v0.5: capabilities, providers, services, agents --
|
|
477
|
+
|
|
478
|
+
capability_decl: ($) =>
|
|
479
|
+
seq(
|
|
480
|
+
"capability",
|
|
481
|
+
field("name", $.identifier),
|
|
482
|
+
"{",
|
|
483
|
+
repeat($.capability_op),
|
|
484
|
+
"}",
|
|
485
|
+
),
|
|
486
|
+
capability_op: ($) =>
|
|
487
|
+
seq(
|
|
488
|
+
"fn",
|
|
489
|
+
field("name", $.identifier),
|
|
490
|
+
"(",
|
|
491
|
+
optional(sep1($.param, ",")),
|
|
492
|
+
optional(","),
|
|
493
|
+
")",
|
|
494
|
+
"->",
|
|
495
|
+
field("return_type", $._type_ref),
|
|
496
|
+
),
|
|
497
|
+
|
|
498
|
+
provider_decl: ($) =>
|
|
499
|
+
seq(
|
|
500
|
+
"provides",
|
|
501
|
+
field("capability", $.identifier),
|
|
502
|
+
"=",
|
|
503
|
+
field("provider", $.identifier),
|
|
504
|
+
// v0.12: a provider may depend on other capabilities.
|
|
505
|
+
optional(field("given", $.given_clause)),
|
|
506
|
+
// v0.17: a bodiless provider is *external* — supplied by an adapter's
|
|
507
|
+
// binding. The absence of the brace block (not an empty one) is the
|
|
508
|
+
// signal; the LSP reports placement errors (body in an adapter, or
|
|
509
|
+
// bodiless outside one).
|
|
510
|
+
optional(seq("{", repeat($.provider_op), "}")),
|
|
511
|
+
),
|
|
512
|
+
provider_op: ($) =>
|
|
513
|
+
seq(
|
|
514
|
+
"fn",
|
|
515
|
+
field("name", $.identifier),
|
|
516
|
+
"(",
|
|
517
|
+
optional(sep1($.param, ",")),
|
|
518
|
+
optional(","),
|
|
519
|
+
")",
|
|
520
|
+
"->",
|
|
521
|
+
field("return_type", $._type_ref),
|
|
522
|
+
field("body", $.block),
|
|
523
|
+
),
|
|
524
|
+
|
|
525
|
+
service_decl: ($) =>
|
|
526
|
+
seq(
|
|
527
|
+
"service",
|
|
528
|
+
field("name", $.identifier),
|
|
529
|
+
optional(field("protocol", $.service_protocol)),
|
|
530
|
+
"{",
|
|
531
|
+
repeat($.handler),
|
|
532
|
+
"}",
|
|
533
|
+
),
|
|
534
|
+
// v0.44: `from <protocol>` on the service header.
|
|
535
|
+
service_protocol: ($) =>
|
|
536
|
+
seq(
|
|
537
|
+
"from",
|
|
538
|
+
choice(
|
|
539
|
+
"http",
|
|
540
|
+
"cron",
|
|
541
|
+
seq("queue", "(", field("name", $.string_literal), ")"),
|
|
542
|
+
),
|
|
543
|
+
),
|
|
544
|
+
|
|
545
|
+
agent_decl: ($) =>
|
|
546
|
+
seq(
|
|
547
|
+
"agent",
|
|
548
|
+
field("name", $.identifier),
|
|
549
|
+
"{",
|
|
550
|
+
field("key", $.key_decl),
|
|
551
|
+
field("state", $.state_decl),
|
|
552
|
+
repeat($.handler),
|
|
553
|
+
"}",
|
|
554
|
+
),
|
|
555
|
+
key_decl: ($) =>
|
|
556
|
+
seq("key", field("name", $.identifier), ":", field("type", $._type_ref)),
|
|
557
|
+
state_decl: ($) =>
|
|
558
|
+
seq(
|
|
559
|
+
"state",
|
|
560
|
+
"{",
|
|
561
|
+
optional(sep1($.record_field, ",")),
|
|
562
|
+
optional(","),
|
|
563
|
+
"}",
|
|
564
|
+
),
|
|
565
|
+
|
|
566
|
+
// v0.5 `on call`; v0.44 `on GET("path")` / `on schedule("expr")` /
|
|
567
|
+
// `on message(...)` — the protocol lives on the service header.
|
|
568
|
+
handler: ($) =>
|
|
569
|
+
choice($.call_handler, $.http_handler, $.cron_handler, $.queue_handler),
|
|
570
|
+
call_handler: ($) =>
|
|
571
|
+
seq(
|
|
572
|
+
"on",
|
|
573
|
+
"call",
|
|
574
|
+
optional(field("method", $.identifier)),
|
|
575
|
+
optional(field("by", $.by_clause)),
|
|
576
|
+
"(",
|
|
577
|
+
optional(sep1($.param, ",")),
|
|
578
|
+
optional(","),
|
|
579
|
+
")",
|
|
580
|
+
"->",
|
|
581
|
+
field("return_type", $._type_ref),
|
|
582
|
+
optional(field("given", $.given_clause)),
|
|
583
|
+
field("body", $.block),
|
|
584
|
+
),
|
|
585
|
+
// v0.44: `on GET("/path") (params) -> …` — the method-builder collapses
|
|
586
|
+
// verb+route into one config expression in the handler-config slot.
|
|
587
|
+
http_handler: ($) =>
|
|
588
|
+
seq(
|
|
589
|
+
"on",
|
|
590
|
+
field("method", $.http_method),
|
|
591
|
+
"(",
|
|
592
|
+
field("path", $.string_literal),
|
|
593
|
+
")",
|
|
594
|
+
optional(field("by", $.by_clause)),
|
|
595
|
+
"(",
|
|
596
|
+
optional(sep1($.param, ",")),
|
|
597
|
+
optional(","),
|
|
598
|
+
")",
|
|
599
|
+
"->",
|
|
600
|
+
field("return_type", $._type_ref),
|
|
601
|
+
optional(field("given", $.given_clause)),
|
|
602
|
+
field("body", $.block),
|
|
603
|
+
),
|
|
604
|
+
http_method: () => choice("GET", "POST", "PUT", "PATCH", "DELETE"),
|
|
605
|
+
// v0.44: `on schedule("<expr>") (at: Int?) -> Effect[Result[(), E]] { … }`.
|
|
606
|
+
cron_handler: ($) =>
|
|
607
|
+
seq(
|
|
608
|
+
"on",
|
|
609
|
+
"schedule",
|
|
610
|
+
"(",
|
|
611
|
+
field("schedule", $.string_literal),
|
|
612
|
+
")",
|
|
613
|
+
optional(field("by", $.by_clause)),
|
|
614
|
+
"(",
|
|
615
|
+
optional(sep1($.param, ",")),
|
|
616
|
+
optional(","),
|
|
617
|
+
")",
|
|
618
|
+
"->",
|
|
619
|
+
field("return_type", $._type_ref),
|
|
620
|
+
optional(field("given", $.given_clause)),
|
|
621
|
+
field("body", $.block),
|
|
622
|
+
),
|
|
623
|
+
// v0.44: `on message(m: T) -> Effect[QueueResult] { … }`. The queue binding
|
|
624
|
+
// lives on the service header (`from queue("name")`).
|
|
625
|
+
queue_handler: ($) =>
|
|
626
|
+
seq(
|
|
627
|
+
"on",
|
|
628
|
+
"message",
|
|
629
|
+
optional(field("by", $.by_clause)),
|
|
630
|
+
"(",
|
|
631
|
+
optional(sep1($.param, ",")),
|
|
632
|
+
optional(","),
|
|
633
|
+
")",
|
|
634
|
+
"->",
|
|
635
|
+
field("return_type", $._type_ref),
|
|
636
|
+
optional(field("given", $.given_clause)),
|
|
637
|
+
field("body", $.block),
|
|
638
|
+
),
|
|
639
|
+
given_clause: ($) =>
|
|
640
|
+
// v0.15: a capability may be a bare local name or a dotted cross-context
|
|
641
|
+
// reference (`B.Cap` / `platform.time.Clock`).
|
|
642
|
+
seq("given", sep1(field("capability", $.qualified_name), ",")),
|
|
643
|
+
|
|
644
|
+
// v0.45: an actor declaration — a boundary contract. Normal form
|
|
645
|
+
// `actor Name { auth = Scheme (, identity = Type)? }`; the reserved
|
|
646
|
+
// refinement form `actor Admin = Base where <predicate>` is parsed (and
|
|
647
|
+
// rejected by the checker in Foundations).
|
|
648
|
+
actor_decl: ($) =>
|
|
649
|
+
seq(
|
|
650
|
+
"actor",
|
|
651
|
+
field("name", $.identifier),
|
|
652
|
+
choice(
|
|
653
|
+
seq(
|
|
654
|
+
"{",
|
|
655
|
+
"auth",
|
|
656
|
+
"=",
|
|
657
|
+
field("scheme", $.scheme),
|
|
658
|
+
// v0.47: the Bearer scheme takes a `(secret = "<env>")` config.
|
|
659
|
+
// v0.51: generalised to keyed args — `Scheme(key = value, …)` with
|
|
660
|
+
// string or integer values (Signature: secret/header/timestamp +
|
|
661
|
+
// integer tolerance).
|
|
662
|
+
optional(field("config", $.scheme_config)),
|
|
663
|
+
optional(seq(",", "identity", "=", field("identity", $._type_ref))),
|
|
664
|
+
"}",
|
|
665
|
+
),
|
|
666
|
+
seq(
|
|
667
|
+
"=",
|
|
668
|
+
field("base", $.identifier),
|
|
669
|
+
"where",
|
|
670
|
+
field("predicate", $.refinement),
|
|
671
|
+
),
|
|
672
|
+
),
|
|
673
|
+
),
|
|
674
|
+
// The closed, compiler-known scheme set. `None`/`Internal` are admitted in
|
|
675
|
+
// Foundations; `Bearer`/`Signature` are reserved-and-rejected.
|
|
676
|
+
scheme: () => choice("None", "Internal", "Bearer", "Signature"),
|
|
677
|
+
// v0.51: the keyed-args scheme config — `(key = value, …)`. Values are
|
|
678
|
+
// string or integer literals; the checker validates which keys each scheme
|
|
679
|
+
// admits (Bearer: secret; Signature: secret/header/timestamp/tolerance).
|
|
680
|
+
scheme_config: ($) =>
|
|
681
|
+
seq(
|
|
682
|
+
"(",
|
|
683
|
+
sep1($.scheme_arg, ","),
|
|
684
|
+
")",
|
|
685
|
+
),
|
|
686
|
+
scheme_arg: ($) =>
|
|
687
|
+
seq(
|
|
688
|
+
field("key", $.identifier),
|
|
689
|
+
"=",
|
|
690
|
+
field("value", choice($.string_literal, $.number_literal)),
|
|
691
|
+
),
|
|
692
|
+
// v0.45: the handler `by <binder>: <Actor>` clause — sits after the protocol
|
|
693
|
+
// config and before the parameters.
|
|
694
|
+
// v0.50: the binder is optional — `by <Actor>` (verify, don't capture the
|
|
695
|
+
// identity) or `by <binder>: <Actor>` (capture it).
|
|
696
|
+
// v0.52: the actor position is a `|`-separated list. One name is the
|
|
697
|
+
// ordinary single-actor handler; more than one is an ordered sum of peer
|
|
698
|
+
// actors (`by who: User | Visitor`), resolved first-wins.
|
|
699
|
+
by_clause: ($) =>
|
|
700
|
+
seq(
|
|
701
|
+
"by",
|
|
702
|
+
optional(seq(field("binder", $.identifier), ":")),
|
|
703
|
+
field("actor", $.identifier),
|
|
704
|
+
repeat(seq("|", field("actor", $.identifier))),
|
|
705
|
+
),
|
|
706
|
+
|
|
707
|
+
// -- v0.7: test bodies --
|
|
708
|
+
|
|
709
|
+
mocks_decl: ($) =>
|
|
710
|
+
seq(
|
|
711
|
+
"mocks",
|
|
712
|
+
field("capability", $.identifier),
|
|
713
|
+
"=",
|
|
714
|
+
field("impl", $.identifier),
|
|
715
|
+
"{",
|
|
716
|
+
repeat($.provider_op),
|
|
717
|
+
"}",
|
|
718
|
+
),
|
|
719
|
+
test_case: ($) =>
|
|
720
|
+
seq("test", field("name", $.string_literal), field("body", $.block)),
|
|
721
|
+
|
|
722
|
+
// -- Block & statements --
|
|
723
|
+
|
|
724
|
+
// A block is a run of statements ending in a tail expression (the block's
|
|
725
|
+
// value). The tail is optional because a test body may end in a statement
|
|
726
|
+
// such as a bare `assert`.
|
|
727
|
+
block: ($) =>
|
|
728
|
+
seq("{", repeat($._statement), optional(field("tail", $._expression)), "}"),
|
|
729
|
+
|
|
730
|
+
// `assert` is an expression (v0.9.1) but also appears in statement
|
|
731
|
+
// position within a test body (an expression-statement of type `()`).
|
|
732
|
+
_statement: ($) =>
|
|
733
|
+
choice($.let_stmt, $.effect_let_stmt, $.commit_stmt, prec(1, $.assert_expr)),
|
|
734
|
+
|
|
735
|
+
let_stmt: ($) =>
|
|
736
|
+
seq(
|
|
737
|
+
"let",
|
|
738
|
+
field("name", $._binding_name),
|
|
739
|
+
optional(seq(":", field("type", $._type_ref))),
|
|
740
|
+
"=",
|
|
741
|
+
field("value", $._expression),
|
|
742
|
+
),
|
|
743
|
+
effect_let_stmt: ($) =>
|
|
744
|
+
seq(
|
|
745
|
+
"let",
|
|
746
|
+
field("name", $._binding_name),
|
|
747
|
+
optional(seq(":", field("type", $._type_ref))),
|
|
748
|
+
"<-",
|
|
749
|
+
field("value", $._expression),
|
|
750
|
+
),
|
|
751
|
+
commit_stmt: ($) => seq("commit", field("value", $._expression)),
|
|
752
|
+
|
|
753
|
+
// A let/effect-let may bind the discard name `_`.
|
|
754
|
+
_binding_name: ($) => choice($.identifier, alias("_", $.wildcard)),
|
|
755
|
+
|
|
756
|
+
// -- Expressions --
|
|
757
|
+
|
|
758
|
+
_expression: ($) =>
|
|
759
|
+
choice(
|
|
760
|
+
$.if_expr,
|
|
761
|
+
$.match_expr,
|
|
762
|
+
$.is_expr,
|
|
763
|
+
$.assert_expr,
|
|
764
|
+
$.binary_expr,
|
|
765
|
+
$.unary_expr,
|
|
766
|
+
$._primary,
|
|
767
|
+
),
|
|
768
|
+
|
|
769
|
+
// v0.9.1: `assert` is an expression of type `()`.
|
|
770
|
+
assert_expr: ($) =>
|
|
771
|
+
prec.right(PREC.assert, seq("assert", field("cond", $._expression))),
|
|
772
|
+
|
|
773
|
+
if_expr: ($) =>
|
|
774
|
+
seq(
|
|
775
|
+
"if",
|
|
776
|
+
field("cond", $._expression),
|
|
777
|
+
field("then", $.block),
|
|
778
|
+
"else",
|
|
779
|
+
field("else", choice($.if_expr, $.block)),
|
|
780
|
+
),
|
|
781
|
+
|
|
782
|
+
match_expr: ($) =>
|
|
783
|
+
seq(
|
|
784
|
+
"match",
|
|
785
|
+
field("disc", $._expression),
|
|
786
|
+
"{",
|
|
787
|
+
repeat($.match_arm),
|
|
788
|
+
"}",
|
|
789
|
+
),
|
|
790
|
+
// Arms are newline-separated; a trailing comma is permitted but not
|
|
791
|
+
// required.
|
|
792
|
+
match_arm: ($) =>
|
|
793
|
+
prec.right(
|
|
794
|
+
seq(
|
|
795
|
+
field("pattern", $._pattern),
|
|
796
|
+
"=>",
|
|
797
|
+
// `_expression` already includes `block` via `_primary`, so a
|
|
798
|
+
// `{ … }` arm body is covered without a separate alternative.
|
|
799
|
+
field("body", $._expression),
|
|
800
|
+
optional(","),
|
|
801
|
+
),
|
|
802
|
+
),
|
|
803
|
+
|
|
804
|
+
_pattern: ($) => choice($.wildcard_pattern, $.variant_pattern),
|
|
805
|
+
wildcard_pattern: () => "_",
|
|
806
|
+
// The variant name is an `identifier`, not `constant_name`: because
|
|
807
|
+
// `word` unifies all word-shaped lexemes to `identifier`, a `constant_name`
|
|
808
|
+
// here can never out-lex `identifier` after `is`/`=>`, which previously
|
|
809
|
+
// mis-parsed `x is Miss` as a `type.` prefix awaiting a dot. Capitalisation
|
|
810
|
+
// is recovered in the highlight query, not the grammar.
|
|
811
|
+
variant_pattern: ($) =>
|
|
812
|
+
prec.right(
|
|
813
|
+
seq(
|
|
814
|
+
optional(seq(field("type", $.identifier), ".")),
|
|
815
|
+
field("variant", $.identifier),
|
|
816
|
+
optional(
|
|
817
|
+
seq(
|
|
818
|
+
"(",
|
|
819
|
+
optional(sep1($._pattern_binding, ",")),
|
|
820
|
+
optional(","),
|
|
821
|
+
")",
|
|
822
|
+
),
|
|
823
|
+
),
|
|
824
|
+
),
|
|
825
|
+
),
|
|
826
|
+
_pattern_binding: ($) =>
|
|
827
|
+
choice(
|
|
828
|
+
$.named_binding,
|
|
829
|
+
$.positional_binding,
|
|
830
|
+
),
|
|
831
|
+
named_binding: ($) =>
|
|
832
|
+
seq(field("field", $.identifier), ":", field("name", choice($.identifier, "_"))),
|
|
833
|
+
positional_binding: ($) => choice($.identifier, "_"),
|
|
834
|
+
|
|
835
|
+
is_expr: ($) =>
|
|
836
|
+
prec.left(
|
|
837
|
+
PREC.is,
|
|
838
|
+
seq(field("value", $._expression), "is", field("pattern", $._pattern)),
|
|
839
|
+
),
|
|
840
|
+
|
|
841
|
+
binary_expr: ($) =>
|
|
842
|
+
choice(
|
|
843
|
+
prec.left(PREC.or, seq($._expression, "||", $._expression)),
|
|
844
|
+
prec.left(PREC.and, seq($._expression, "&&", $._expression)),
|
|
845
|
+
prec.left(PREC.cmp, seq($._expression, choice("==", "!="), $._expression)),
|
|
846
|
+
prec.left(
|
|
847
|
+
PREC.rel,
|
|
848
|
+
seq($._expression, choice("<", "<=", ">", ">="), $._expression),
|
|
849
|
+
),
|
|
850
|
+
prec.left(PREC.add, seq($._expression, choice("+", "-"), $._expression)),
|
|
851
|
+
prec.left(PREC.mul, seq($._expression, choice("*", "/"), $._expression)),
|
|
852
|
+
),
|
|
853
|
+
|
|
854
|
+
unary_expr: ($) =>
|
|
855
|
+
prec.right(PREC.unary, seq(choice("!", "-"), $._expression)),
|
|
856
|
+
|
|
857
|
+
_primary: ($) =>
|
|
858
|
+
choice(
|
|
859
|
+
$.lambda_expr,
|
|
860
|
+
$.paren_expr,
|
|
861
|
+
$.method_call,
|
|
862
|
+
$.field_access,
|
|
863
|
+
$.call,
|
|
864
|
+
$.record_construction,
|
|
865
|
+
$.record_spread,
|
|
866
|
+
$.question_expr,
|
|
867
|
+
$.ok_expr,
|
|
868
|
+
$.err_expr,
|
|
869
|
+
$.some_expr,
|
|
870
|
+
$.none_expr,
|
|
871
|
+
$.effect_pure_expr,
|
|
872
|
+
$.mock_expr,
|
|
873
|
+
$.list_literal,
|
|
874
|
+
$.block,
|
|
875
|
+
$.number_literal,
|
|
876
|
+
$.float_literal,
|
|
877
|
+
$.string_literal,
|
|
878
|
+
$.boolean_literal,
|
|
879
|
+
$.unit_literal,
|
|
880
|
+
$.self_expr,
|
|
881
|
+
$.identifier,
|
|
882
|
+
),
|
|
883
|
+
|
|
884
|
+
// v0.20a: a lambda — `(params) => expr | block`. Conflicts with
|
|
885
|
+
// paren_expr/unit_literal until the `=>` disambiguates (GLR conflict
|
|
886
|
+
// declared below).
|
|
887
|
+
lambda_expr: ($) =>
|
|
888
|
+
seq(
|
|
889
|
+
"(",
|
|
890
|
+
optional(sep1($.lambda_param, ",")),
|
|
891
|
+
")",
|
|
892
|
+
"=>",
|
|
893
|
+
field("body", choice($._expression, $.block)),
|
|
894
|
+
),
|
|
895
|
+
|
|
896
|
+
lambda_param: ($) =>
|
|
897
|
+
seq(field("name", $.identifier), optional(seq(":", field("type", $._type_ref)))),
|
|
898
|
+
|
|
899
|
+
paren_expr: ($) => seq("(", $._expression, ")"),
|
|
900
|
+
|
|
901
|
+
// Higher precedence than `field_access`: when a `(` follows `recv.name`,
|
|
902
|
+
// shift into the call; when it doesn't, fall back to field access. This is
|
|
903
|
+
// what lets `Reps.of(n)` be a call while `p.x` is a field access.
|
|
904
|
+
// `method_call` also subsumes the old `constructor_call` (`Type.method(…)`).
|
|
905
|
+
method_call: ($) =>
|
|
906
|
+
prec.left(
|
|
907
|
+
PREC.postfix + 1,
|
|
908
|
+
seq(
|
|
909
|
+
field("receiver", $._primary),
|
|
910
|
+
".",
|
|
911
|
+
field("method", $.identifier),
|
|
912
|
+
// v0.22b: optional explicit type arguments on a qualified static —
|
|
913
|
+
// `Json.decode[T](…)` (same-line rule as `call` type application).
|
|
914
|
+
optional(seq("[", sep1(field("type_arg", $._type_ref), ","), "]")),
|
|
915
|
+
"(",
|
|
916
|
+
optional(sep1(field("arg", $._expression), ",")),
|
|
917
|
+
optional(","),
|
|
918
|
+
")",
|
|
919
|
+
),
|
|
920
|
+
),
|
|
921
|
+
|
|
922
|
+
field_access: ($) =>
|
|
923
|
+
prec.left(
|
|
924
|
+
PREC.postfix,
|
|
925
|
+
seq(field("receiver", $._primary), ".", field("field", $.identifier)),
|
|
926
|
+
),
|
|
927
|
+
|
|
928
|
+
call: ($) =>
|
|
929
|
+
prec.left(
|
|
930
|
+
PREC.postfix,
|
|
931
|
+
seq(
|
|
932
|
+
field("name", $.identifier),
|
|
933
|
+
// v0.20a: optional explicit type arguments — `name[T, U](…)`.
|
|
934
|
+
optional(seq("[", sep1(field("type_arg", $._type_ref), ","), "]")),
|
|
935
|
+
"(",
|
|
936
|
+
optional(sep1(field("arg", $._expression), ",")),
|
|
937
|
+
optional(","),
|
|
938
|
+
")",
|
|
939
|
+
),
|
|
940
|
+
),
|
|
941
|
+
|
|
942
|
+
// v0.20b: a list literal — `[a, b, c]`, optional trailing comma. A
|
|
943
|
+
// *leading* `[` only; type application stays a postfix form on a callee
|
|
944
|
+
// identifier (see `call`).
|
|
945
|
+
list_literal: ($) =>
|
|
946
|
+
seq(
|
|
947
|
+
"[",
|
|
948
|
+
optional(seq(sep1(field("element", $._expression), ","), optional(","))),
|
|
949
|
+
"]",
|
|
950
|
+
),
|
|
951
|
+
|
|
952
|
+
record_construction: ($) =>
|
|
953
|
+
seq(
|
|
954
|
+
field("type", $.identifier),
|
|
955
|
+
"{",
|
|
956
|
+
optional(sep1($.field_init, ",")),
|
|
957
|
+
optional(","),
|
|
958
|
+
"}",
|
|
959
|
+
),
|
|
960
|
+
field_init: ($) =>
|
|
961
|
+
choice(
|
|
962
|
+
seq(field("name", $.identifier), ":", field("value", $._expression)),
|
|
963
|
+
field("shorthand", $.identifier),
|
|
964
|
+
),
|
|
965
|
+
|
|
966
|
+
record_spread: ($) =>
|
|
967
|
+
choice(
|
|
968
|
+
seq(
|
|
969
|
+
field("type", $.identifier),
|
|
970
|
+
"{",
|
|
971
|
+
"...",
|
|
972
|
+
field("base", $._expression),
|
|
973
|
+
repeat(seq(",", $.field_init)),
|
|
974
|
+
optional(","),
|
|
975
|
+
"}",
|
|
976
|
+
),
|
|
977
|
+
seq(
|
|
978
|
+
"{",
|
|
979
|
+
"...",
|
|
980
|
+
field("base", $._expression),
|
|
981
|
+
repeat(seq(",", $.field_init)),
|
|
982
|
+
optional(","),
|
|
983
|
+
"}",
|
|
984
|
+
),
|
|
985
|
+
),
|
|
986
|
+
|
|
987
|
+
question_expr: ($) =>
|
|
988
|
+
prec.left(PREC.postfix, seq($._expression, "?")),
|
|
989
|
+
|
|
990
|
+
ok_expr: ($) => seq("Ok", "(", $._expression, ")"),
|
|
991
|
+
err_expr: ($) => seq("Err", "(", $._expression, ")"),
|
|
992
|
+
some_expr: ($) => seq("Some", "(", $._expression, ")"),
|
|
993
|
+
none_expr: () => "None",
|
|
994
|
+
effect_pure_expr: ($) =>
|
|
995
|
+
seq("Effect", ".", "pure", "(", $._expression, ")"),
|
|
996
|
+
|
|
997
|
+
// v0.9.4 Part B: `Mock[T]` test-context construction. The `[ … ]` here is
|
|
998
|
+
// the bracket syntax otherwise reserved for generics; in expression
|
|
999
|
+
// position it carries the mocked type. The optional argument is either a
|
|
1000
|
+
// parenthesised literal-/variant-pin or a brace record-override (the latter
|
|
1001
|
+
// reuses `field_init`, identical to record construction). The test-context
|
|
1002
|
+
// restriction is semantic and left to the LSP.
|
|
1003
|
+
mock_expr: ($) =>
|
|
1004
|
+
prec.right(
|
|
1005
|
+
seq(
|
|
1006
|
+
"Mock",
|
|
1007
|
+
"[",
|
|
1008
|
+
field("type", $._type_ref),
|
|
1009
|
+
"]",
|
|
1010
|
+
optional(field("arg", $.mock_arg)),
|
|
1011
|
+
),
|
|
1012
|
+
),
|
|
1013
|
+
mock_arg: ($) =>
|
|
1014
|
+
choice(
|
|
1015
|
+
seq("(", sep1(field("pin", $._expression), ","), optional(","), ")"),
|
|
1016
|
+
seq("{", optional(sep1($.field_init, ",")), optional(","), "}"),
|
|
1017
|
+
),
|
|
1018
|
+
|
|
1019
|
+
self_expr: () => "self",
|
|
1020
|
+
|
|
1021
|
+
// -- Tokens --
|
|
1022
|
+
|
|
1023
|
+
identifier: () => /[A-Za-z][A-Za-z0-9_]*/,
|
|
1024
|
+
|
|
1025
|
+
// Constant-style names (capitalised idents used as variant names).
|
|
1026
|
+
// Tree-sitter cannot enforce capitalisation at the lex level without
|
|
1027
|
+
// overshadowing `identifier`; we use a regex.
|
|
1028
|
+
constant_name: () => /[A-Z][A-Za-z0-9_]*/,
|
|
1029
|
+
|
|
1030
|
+
number_literal: () => /[0-9]+/,
|
|
1031
|
+
// v0.21: a float literal — fraction with a digit on both sides of the
|
|
1032
|
+
// `.`, an exponent, or both. Longest-match keeps `1.toFloat()` lexing
|
|
1033
|
+
// as a method call on an integer literal.
|
|
1034
|
+
float_literal: () =>
|
|
1035
|
+
/[0-9]+\.[0-9]+([eE][+-]?[0-9]+)?|[0-9]+[eE][+-]?[0-9]+/,
|
|
1036
|
+
string_literal: ($) =>
|
|
1037
|
+
seq(
|
|
1038
|
+
'"',
|
|
1039
|
+
repeat(choice(/[^"\\\n]/, /\\[nt"\\]/, $.string_interpolation)),
|
|
1040
|
+
'"',
|
|
1041
|
+
),
|
|
1042
|
+
// v0.43: an interpolation hole `\(expr)`. A bare `\(` was an invalid
|
|
1043
|
+
// escape, so this is backward-compatible; `\\(` is an escaped backslash
|
|
1044
|
+
// followed by a literal `(`, so it is not a hole.
|
|
1045
|
+
string_interpolation: ($) => seq('\\(', $._expression, ')'),
|
|
1046
|
+
boolean_literal: () => choice("true", "false"),
|
|
1047
|
+
unit_literal: () => seq("(", ")"),
|
|
1048
|
+
|
|
1049
|
+
// `--` line comment. The external scanner consumes `---+`-starting
|
|
1050
|
+
// lines as `doc_block` tokens before the regex tokenizer runs, so
|
|
1051
|
+
// line_comment sees only `--` (or `-` chars followed by a non-third
|
|
1052
|
+
// dash sequence that's already been gobbled by the external scanner).
|
|
1053
|
+
line_comment: () => token(seq("--", /[^\n]*/)),
|
|
1054
|
+
|
|
1055
|
+
// `doc_block` is provided by the external scanner — see src/scanner.c.
|
|
1056
|
+
// The grammar references it via the `externals` array above.
|
|
1057
|
+
},
|
|
1058
|
+
});
|
|
1059
|
+
|
|
1060
|
+
function sep1(rule, separator) {
|
|
1061
|
+
return seq(rule, repeat(seq(separator, rule)));
|
|
1062
|
+
}
|