tree-sitter-bynk 0.447.0 → 0.504.3

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/grammar.js CHANGED
@@ -39,7 +39,7 @@ const PREC = {
39
39
  module.exports = grammar({
40
40
  name: "bynk",
41
41
 
42
- externals: ($) => [$.doc_block, $.doc_line],
42
+ externals: ($) => [$.doc_block, $.doc_line, $.raw_string, $._type_app_open, $._error_sentinel],
43
43
 
44
44
  extras: ($) => [/\s+/, $.doc_line, $.line_comment, $.doc_block],
45
45
 
@@ -48,6 +48,10 @@ module.exports = grammar({
48
48
  word: ($) => $.identifier,
49
49
 
50
50
  conflicts: ($) => [
51
+ // #2654: `let (T { … })` is a parenthesised pattern either way; only the
52
+ // end of the statement (an `else` or none) says whether the record
53
+ // pattern stood alone.
54
+ [$._pattern, $._let_paren_record_pattern],
51
55
  // `match e { … }` / `if e { … }`: after a bare identifier `e`,
52
56
  // the `{` is ambiguous between opening the match/if body and opening a
53
57
  // record construction/spread `e { … }`. Keep all parses alive; the body
@@ -58,6 +62,10 @@ module.exports = grammar({
58
62
  // parenthesised expression (and `()` the unit literal) until the
59
63
  // closing `)` is followed — or not — by `=>`.
60
64
  [$.lambda_param, $._primary],
65
+ // #2712: `(T { f, …` opens a record construction or a record-pattern lambda
66
+ // parameter; the pattern's mandatory `..` (never an expression) settles it.
67
+ [$.field_init, $._lambda_record_field],
68
+ [$._primary, $._lambda_field_binding],
61
69
  [$._primary, $.lambda_expr],
62
70
  // v0.145: a match-arm `if`-guard is an `_expression` immediately followed by
63
71
  // the arm's `=>`. When the guard opens with `(`, one token can't tell a unit
@@ -208,6 +216,7 @@ module.exports = grammar({
208
216
  $.event_decl,
209
217
  $.asset_decl,
210
218
  $.embed_decl,
219
+ $.const_decl,
211
220
  ),
212
221
 
213
222
  _context_body_item: ($) =>
@@ -226,6 +235,7 @@ module.exports = grammar({
226
235
  $.event_decl,
227
236
  $.asset_decl,
228
237
  $.embed_decl,
238
+ $.const_decl,
229
239
  $.removed_agent_decl,
230
240
  ),
231
241
 
@@ -251,6 +261,7 @@ module.exports = grammar({
251
261
  $.event_decl,
252
262
  $.asset_decl,
253
263
  $.embed_decl,
264
+ $.const_decl,
254
265
  ),
255
266
 
256
267
  _test_body_item: ($) =>
@@ -259,6 +270,8 @@ module.exports = grammar({
259
270
  $.consumes_decl,
260
271
  $.env_clause,
261
272
  $.stub_clause,
273
+ $.fixture_decl,
274
+ $.fn_decl,
262
275
  $.case,
263
276
  $.property_decl,
264
277
  ),
@@ -324,17 +337,49 @@ module.exports = grammar({
324
337
  "}",
325
338
  ),
326
339
 
340
+ // -- Type parameters and bounds (constrained generics slice 0) --
341
+
342
+ // `T` or `T: Eq + C`. The constraint names are validated by the checker.
343
+ type_param: ($) =>
344
+ seq(
345
+ field("name", $.identifier),
346
+ optional(seq(":", sep1($.constraint, "+"))),
347
+ ),
348
+
349
+ constraint: ($) => $.identifier,
350
+
351
+ where_clause: ($) => seq("where", sep1($.where_bound, ",")),
352
+
353
+ where_bound: ($) =>
354
+ seq(
355
+ field("param", $.identifier),
356
+ ":",
357
+ sep1($.constraint, "+"),
358
+ ),
359
+
327
360
  // -- Type declarations --
328
361
 
362
+ // #2618: `private` before a `fn`, `type`, `messages`, `asset`, `embed` or
363
+ // `const` declaration makes it unit-private. The word is contextual: with
364
+ // `word` set to `identifier`, it lexes as a keyword only where a
365
+ // declaration may start, so `@cache(scope: private)` and a binding named
366
+ // `private` keep parsing as identifiers. Placement (a commons or context, never an
367
+ // adapter) is the compiler's call (`bynk.visibility.private_not_allowed`).
368
+ visibility_modifier: ($) => "private",
369
+
329
370
  type_decl: ($) =>
330
371
  seq(
372
+ optional(field("visibility", $.visibility_modifier)),
331
373
  "type",
332
374
  field("name", $.identifier),
333
375
  // v0.157 (ADR 0183): optional `[A, B]` type parameters — only a record
334
376
  // body may be generic (enforced by the checker, not the grammar).
335
- optional(seq("[", sep1(field("type_param", $.identifier), ","), "]")),
377
+ optional(seq("[", sep1(field("type_param", $.type_param), ","), "]")),
336
378
  "=",
337
379
  field("body", $._type_body),
380
+ // Constrained generics slice 1: a trailing `derives Ord` clause, `+`
381
+ // separated like a bound list. `derives` is a contextual word.
382
+ optional(seq("derives", sep1(field("derive", $.constraint), "+"))),
338
383
  ),
339
384
 
340
385
  _type_body: ($) =>
@@ -404,6 +449,7 @@ module.exports = grammar({
404
449
  // (`bynk.asset.outside_context_or_commons` rejects it in an adapter).
405
450
  asset_decl: ($) =>
406
451
  seq(
452
+ optional(field("visibility", $.visibility_modifier)),
407
453
  "asset",
408
454
  field("name", $.identifier),
409
455
  "=",
@@ -419,12 +465,41 @@ module.exports = grammar({
419
465
  // rejects it in an adapter).
420
466
  embed_decl: ($) =>
421
467
  seq(
468
+ optional(field("visibility", $.visibility_modifier)),
422
469
  "embed",
423
470
  field("name", $.identifier),
424
471
  "=",
425
472
  field("path", $.string_literal),
426
473
  ),
427
474
 
475
+ // #2841: `fixture name = "fixtures/…"` in a suite body — a project file's
476
+ // text as a `String` constant for the suite's cases. Contextual `fixture`.
477
+ fixture_decl: ($) =>
478
+ seq(
479
+ "fixture",
480
+ field("name", $.identifier),
481
+ "=",
482
+ field("path", $.string_literal),
483
+ ),
484
+
485
+ // #2617 (named constants): `const Name: Type = <static expression>` — a
486
+ // named value. Admitted in every body like `embed`; placement
487
+ // (`bynk.resolve.const_outside_context_or_commons`), the name's case
488
+ // (`bynk.resolve.const_name_case`, upper-camel like `constant_name`) and
489
+ // the initialiser's static-ness are checker concerns. The name is an
490
+ // `identifier` because `word` unifies every word-shaped lexeme to it.
491
+ // A leading `private` makes it unit-private, as on `embed`.
492
+ const_decl: ($) =>
493
+ seq(
494
+ optional(field("visibility", $.visibility_modifier)),
495
+ "const",
496
+ field("name", $.identifier),
497
+ ":",
498
+ field("type", $._type_ref),
499
+ "=",
500
+ field("value", $._expression),
501
+ ),
502
+
428
503
  // v0.154 (ADR 0178): a sum body may carry a trailing `embeds E as V, …`
429
504
  // clause declaring error embeddings the `?` operator auto-converts.
430
505
  // `embeds`/`as` are contextual — matched only in this position.
@@ -498,6 +573,7 @@ module.exports = grammar({
498
573
  "NonNegative",
499
574
  "Positive",
500
575
  "NonEmpty",
576
+ "IsoDate",
501
577
  ),
502
578
  // v0.21/v0.142 (ADR 0166): a refinement-bound number may be negated —
503
579
  // `InRange(-1_000, 1_000_000)` — mirroring `parse_signed_num_literal`'s
@@ -507,12 +583,22 @@ module.exports = grammar({
507
583
  choice(
508
584
  seq(optional("-"), $.number_literal),
509
585
  seq(optional("-"), $.float_literal),
586
+ seq(optional("-"), $.decimal_literal),
510
587
  $.string_literal,
511
588
  ),
512
589
 
513
590
  _base_type: ($) => $.base_type,
514
591
  base_type: () =>
515
- choice("Int", "String", "Bool", "Float", "Duration", "Instant", "Bytes"),
592
+ choice(
593
+ "Int",
594
+ "String",
595
+ "Bool",
596
+ "Float",
597
+ "Duration",
598
+ "Instant",
599
+ "Bytes",
600
+ "Decimal",
601
+ ),
516
602
 
517
603
  _type_ref: ($) =>
518
604
  choice(
@@ -617,15 +703,21 @@ module.exports = grammar({
617
703
 
618
704
  fn_decl: ($) =>
619
705
  seq(
706
+ optional(field("visibility", $.visibility_modifier)),
620
707
  "fn",
621
708
  field("name", choice($.method_name, $.identifier)),
622
709
  // v0.20a: optional `[A, B]` type parameters (functions only).
623
- optional(seq("[", sep1(field("type_param", $.identifier), ","), "]")),
710
+ optional(seq("[", sep1(field("type_param", $.type_param), ","), "]")),
624
711
  "(",
625
712
  optional($._params),
626
713
  ")",
627
714
  "->",
628
715
  field("return_type", $._type_ref),
716
+ // #2868: a context function's capabilities, after the return type.
717
+ optional(field("given", $.given_clause)),
718
+ // Constrained generics slice 0: a method's `where A: Eq` clause bounds
719
+ // a receiver type's parameter.
720
+ optional($.where_clause),
629
721
  // v0.115: contract clauses between the return type and the body.
630
722
  repeat($.requires_clause),
631
723
  repeat($.ensures_clause),
@@ -681,7 +773,7 @@ module.exports = grammar({
681
773
  // #926: optional `[T, …]` type parameters on the op itself — resolved
682
774
  // only from an explicit type argument at the call site (mirroring
683
775
  // `fn_decl`'s own `[A, B]` slot, `grammar.js:525`).
684
- optional(seq("[", sep1(field("type_param", $.identifier), ","), "]")),
776
+ optional(seq("[", sep1(field("type_param", $.type_param), ","), "]")),
685
777
  "(",
686
778
  optional(sep1($.param, ",")),
687
779
  optional(","),
@@ -701,6 +793,7 @@ module.exports = grammar({
701
793
  // mirroring `service_decl`/`agent_decl`'s existing permissive placement.
702
794
  messages_decl: ($) =>
703
795
  seq(
796
+ optional(field("visibility", $.visibility_modifier)),
704
797
  "messages",
705
798
  field("tag", $.string_literal),
706
799
  repeat(field("annotation", $.store_annotation)),
@@ -763,6 +856,9 @@ module.exports = grammar({
763
856
  // v0.142 (ADR 0166): the optional `limits { }` policy, likewise a
764
857
  // header-position `from http` concern restricted by the checker.
765
858
  optional(field("limits", $.limits_policy)),
859
+ // #2858: the optional `json { }` policy, likewise a header-position
860
+ // `from http` concern restricted by the checker.
861
+ optional(field("json", $.json_policy)),
766
862
  // v0.140 (ADR 0163): a handler may carry one or more `@name(args)`
767
863
  // annotations (`@cache(maxAge: 5.minutes)`, `@limit(maxBody: …)`) —
768
864
  // the same shape `store_annotation` already gives store fields.
@@ -805,6 +901,18 @@ module.exports = grammar({
805
901
  ),
806
902
  limits_field: ($) =>
807
903
  seq(field("name", $.identifier), ":", field("value", $._expression)),
904
+ // #2858: `json { <name>: <value>, … }`. `json` is a contextual keyword (like
905
+ // `limits`); the one field name, `options`, is an ordinary identifier matched
906
+ // by the checker.
907
+ json_policy: ($) =>
908
+ seq(
909
+ "json",
910
+ "{",
911
+ repeat(seq($.json_field, optional(","))),
912
+ "}",
913
+ ),
914
+ json_field: ($) =>
915
+ seq(field("name", $.identifier), ":", field("value", $._expression)),
808
916
  // v0.44: `from <protocol>` on the service header.
809
917
  // v0.103: `from websocket(in: I, out: O)` binds the inbound/outbound frame
810
918
  // types. `websocket`, `in`, and `out` are contextual (resolved by `word`).
@@ -865,14 +973,21 @@ module.exports = grammar({
865
973
  // than nested inside it. Nested inside the `Events` choice arm, not a
866
974
  // free-standing clause any protocol could carry — `via` on `http`/
867
975
  // `cron`/`queue`/`websocket` is a syntax error, not a checker
868
- // diagnostic. Literal only (v1 of this clause); a range pattern
869
- // (`via schema(2..)`) is unbuilt grammar, left to a future slice.
976
+ // diagnostic. A literal, an integer range (`via schema(2..)`, #990 —
977
+ // the same `int_range` a match pattern uses, #2699) or `_`.
870
978
  schema_dispatch_clause: ($) =>
871
979
  seq(
872
980
  "via",
873
981
  "schema",
874
982
  "(",
875
- field("version", seq(optional("-"), $.number_literal)),
983
+ field(
984
+ "version",
985
+ choice(
986
+ $.int_range,
987
+ seq(optional("-"), $.number_literal),
988
+ $.wildcard_pattern,
989
+ ),
990
+ ),
876
991
  ")",
877
992
  ),
878
993
  event_pattern_field: ($) =>
@@ -1239,8 +1354,9 @@ module.exports = grammar({
1239
1354
  // v0.118 (testing track slice 6): a test-scope stub for one capability
1240
1355
  // operation — `stub Cap.op(<arg pattern>, …) <rhs>`. Only admitted inside a
1241
1356
  // suite/case body. An arg pattern is `_` (any) or a value expression; the
1242
- // right-hand side is `returns <expr>`, `returns each [ <outcome>, … ]` (a
1243
- // scripted sequence, where an outcome is `fails` or an expr), or `fails`.
1357
+ // right-hand side is `returns <expr> [after <expr>]`, `returns each [ <outcome>, … ]`
1358
+ // (a scripted sequence, where an outcome is `fails` or an expr), `fails`, or
1359
+ // `never`.
1244
1360
  // `returns`/`each`/`fails` are contextual words. Keyword `stub` since the
1245
1361
  // keyword-hygiene batch (#548); formerly a pun on `provides`.
1246
1362
  stub_clause: ($) =>
@@ -1270,8 +1386,16 @@ module.exports = grammar({
1270
1386
  ),
1271
1387
  "]",
1272
1388
  ),
1273
- seq("returns", field("value", $._expression)),
1389
+ // #2683: `returns <v> after <d>` completes on the test clock once
1390
+ // `d` has passed; `never` does not complete. `after` and `never` are
1391
+ // contextual words.
1392
+ seq(
1393
+ "returns",
1394
+ field("value", $._expression),
1395
+ optional(seq("after", field("after", $._expression))),
1396
+ ),
1274
1397
  "fails",
1398
+ "never",
1275
1399
  ),
1276
1400
  ),
1277
1401
  // #2134: a suite- or case-scoped `env { NAME = "value" … }` clause — the
@@ -1343,6 +1467,7 @@ module.exports = grammar({
1343
1467
  $.do_stmt,
1344
1468
  $.assign_stmt,
1345
1469
  $.advance_stmt,
1470
+ $.par_stmt,
1346
1471
  prec(1, $.expect_expr),
1347
1472
  ),
1348
1473
 
@@ -1374,28 +1499,50 @@ module.exports = grammar({
1374
1499
  // lenient (a bare-name `variant_pattern` is admitted) because the compiler,
1375
1500
  // not the highlighter, reports `bynk.parse.let_else_on_binding`.
1376
1501
  let_else_stmt: ($) =>
1377
- seq(
1378
- "let",
1379
- field("pattern", choice($._let_pattern, alias($._let_or_pattern, $.or_pattern))),
1380
- field("bind", choice("=", "<-")),
1381
- field("value", $._expression),
1382
- optional(field("principal", $.call_site_actor)),
1383
- "else",
1384
- field("else", choice($.block, $.let_else_match)),
1502
+ choice(
1503
+ seq(
1504
+ "let",
1505
+ field("pattern", choice($._let_pattern, alias($._let_or_pattern, $.or_pattern))),
1506
+ field("bind", choice("=", "<-")),
1507
+ field("value", $._expression),
1508
+ optional(field("principal", $.call_site_actor)),
1509
+ "else",
1510
+ field("else", choice($.block, $.let_else_match)),
1511
+ ),
1512
+ // #2654 (patterns track, slice 2): `let Item { sku, .. } = item` — the
1513
+ // `else` is optional when the pattern is a record pattern (the compiler further
1514
+ // requires it to be irrefutable, `let_pattern_without_else`; whether a
1515
+ // field's sub-pattern is a binding or a variant is a matter of
1516
+ // capitalisation, which this grammar cannot see). Parentheses around
1517
+ // the pattern are transparent.
1518
+ seq(
1519
+ "let",
1520
+ field("pattern", choice($.record_pattern, alias($._let_paren_record_pattern, $.paren_pattern))),
1521
+ field("bind", choice("=", "<-")),
1522
+ field("value", $._expression),
1523
+ optional(field("principal", $.call_site_actor)),
1524
+ optional(seq("else", field("else", choice($.block, $.let_else_match)))),
1525
+ ),
1385
1526
  ),
1527
+ _let_paren_record_pattern: ($) => seq("(", $.record_pattern, ")"),
1386
1528
  // #2010: the patterns that may open a `let … else`, mirroring the Rust
1387
1529
  // parser's `let_pattern_ahead`: a literal, a parenthesised pattern, or a
1388
1530
  // variant with a payload or a qualifier. A bare name stays a binding (and
1389
1531
  // a bare `_` a discard), so `let x = …` is never ambiguous here.
1390
1532
  _let_pattern: ($) =>
1391
1533
  choice(
1534
+ $.int_range,
1392
1535
  $.literal_pattern,
1393
1536
  $.paren_pattern,
1537
+ $.list_pattern,
1394
1538
  alias($._let_variant_pattern, $.variant_pattern),
1395
1539
  ),
1396
1540
  // `let Ok(x) | Err(x) = …`: an or-chain whose first alternative opens a
1397
1541
  // pattern; the rest are any patterns.
1398
- _let_or_pattern: ($) => prec.left(1, seq($._let_pattern, "|", $._pattern)),
1542
+ // #2654: a record pattern may open the chain too (`let Item { .. } | …`),
1543
+ // though on its own it is the `else`-less alternative of `let_else_stmt`.
1544
+ _let_or_pattern: ($) =>
1545
+ prec.left(1, seq(choice($._let_pattern, $.record_pattern), "|", $._pattern)),
1399
1546
  _let_variant_pattern: ($) => {
1400
1547
  const payload = seq("(", optional(sep1($._pattern_binding, ",")), optional(","), ")");
1401
1548
  return prec.right(
@@ -1456,6 +1603,28 @@ module.exports = grammar({
1456
1603
  field("amount", $._expression),
1457
1604
  ),
1458
1605
 
1606
+ // #2640 (concurrent effects, slice 0): `par { let a <- e1 let b <- e2 }` —
1607
+ // issue every effect, then wait for them all. `par` is contextual: it opens
1608
+ // a statement only before `{`. The grammar stays permissive about the
1609
+ // block's contents (any statement parses); the compiler admits only
1610
+ // `let x <- e` and `do e` (`bynk.par.invalid_statement`).
1611
+ par_stmt: ($) => seq("par", "{", repeat($._par_statement), "}"),
1612
+
1613
+ // Any statement but `let … else`, which would take the rest of the block as
1614
+ // its arm and so has no room inside a `par` (the compiler's parser refuses
1615
+ // it with `bynk.par.invalid_statement`).
1616
+ _par_statement: ($) =>
1617
+ choice(
1618
+ $.let_stmt,
1619
+ $.effect_let_stmt,
1620
+ $.effect_send_stmt,
1621
+ $.do_stmt,
1622
+ $.assign_stmt,
1623
+ $.advance_stmt,
1624
+ $.par_stmt,
1625
+ prec(1, $.expect_expr),
1626
+ ),
1627
+
1459
1628
  // A let/effect-let may bind the discard name `_`.
1460
1629
  _binding_name: ($) => choice($.identifier, alias("_", $.wildcard)),
1461
1630
 
@@ -1482,7 +1651,10 @@ module.exports = grammar({
1482
1651
  PREC.expect,
1483
1652
  seq(
1484
1653
  "expect",
1485
- field("cond", choice($.observation_expr, $.faults_expr, $._expression)),
1654
+ field(
1655
+ "cond",
1656
+ choice($.observation_expr, $.faults_expr, $.snapshot_expr, $._expression),
1657
+ ),
1486
1658
  ),
1487
1659
  ),
1488
1660
 
@@ -1499,6 +1671,18 @@ module.exports = grammar({
1499
1671
  "faults",
1500
1672
  ),
1501
1673
 
1674
+ // #2842: a snapshot claim — `expect <value> matches snapshot "<name>"`
1675
+ // compares the value's canonical JSON with a stored snapshot. `matches` and
1676
+ // `snapshot` are contextual, as `faults` is: an expression is never followed
1677
+ // by a bare identifier, so the words alone decide.
1678
+ snapshot_expr: ($) =>
1679
+ seq(
1680
+ field("value", $._expression),
1681
+ "matches",
1682
+ "snapshot",
1683
+ field("name", $.string_literal),
1684
+ ),
1685
+
1502
1686
  // v0.117: an observation — a `Cap.op` seam reference followed by one of the
1503
1687
  // sugar matchers. `called` / `never` / `once` / `times` / `with` / `before`
1504
1688
  // are contextual (state-lexed as keywords only here; ordinary identifiers
@@ -1601,11 +1785,62 @@ module.exports = grammar({
1601
1785
  _pattern: ($) =>
1602
1786
  choice(
1603
1787
  $.wildcard_pattern,
1788
+ $.int_range,
1604
1789
  $.literal_pattern,
1605
1790
  $.variant_pattern,
1791
+ $.record_pattern,
1792
+ $.list_pattern,
1606
1793
  $.or_pattern,
1607
1794
  $.paren_pattern,
1608
1795
  ),
1796
+ // #2641 (patterns track, slice 1): `Order { status: Paid, total, .. }`. Always
1797
+ // open, with the trailing `..` mandatory so that adding a field to the record
1798
+ // never breaks a pattern — the rule `event_pattern` already applies to
1799
+ // events. A field is a bare name (binding it to a local of the same name) or
1800
+ // `name: pattern`. After `is` a record pattern must be parenthesised (see
1801
+ // `_is_pattern`), because there the `{` would also open the `if`'s block.
1802
+ record_pattern: ($) =>
1803
+ seq(
1804
+ field("type", $.identifier),
1805
+ "{",
1806
+ repeat(seq($.record_pattern_field, ",")),
1807
+ "..",
1808
+ "}",
1809
+ ),
1810
+ // #2680 (patterns track, slice 4): `[]`, `[p, …]`, `[p, …, ..]`,
1811
+ // `[p, …, ..rest]`. The rest marker is last and at most once; `..` takes a
1812
+ // name or nothing (a literal after it is reserved for range patterns, and
1813
+ // the compiler reports it). It needs no parentheses after `is`, where the
1814
+ // pattern closes at `]`.
1815
+ list_pattern: ($) =>
1816
+ seq(
1817
+ "[",
1818
+ repeat(seq($._pattern, ",")),
1819
+ optional(choice($._pattern, seq($.list_rest, optional(",")))),
1820
+ "]",
1821
+ ),
1822
+ list_rest: ($) => seq("..", optional(field("name", $.identifier))),
1823
+ record_pattern_field: ($) =>
1824
+ choice(
1825
+ seq(field("name", $.identifier), ":", field("pattern", $._pattern)),
1826
+ field("name", $.identifier),
1827
+ ),
1828
+ // #2641: the patterns an `is` accepts without parentheses — `_pattern`
1829
+ // without a record pattern at its tail, at the top level or as an
1830
+ // or-alternative. Parenthesised, a record pattern is allowed anywhere (the
1831
+ // hand-written parser's `is_pattern_tail` restriction).
1832
+ _is_base_pattern: ($) =>
1833
+ choice(
1834
+ $.wildcard_pattern,
1835
+ $.literal_pattern,
1836
+ $.variant_pattern,
1837
+ $.list_pattern,
1838
+ $.paren_pattern,
1839
+ ),
1840
+ _is_pattern: ($) =>
1841
+ choice($._is_base_pattern, alias($._is_or_pattern, $.or_pattern)),
1842
+ _is_or_pattern: ($) =>
1843
+ prec.left(1, seq($._is_pattern, "|", $._is_base_pattern)),
1609
1844
  // #474 §2.3.4: `p₁ | p₂ | … | pₙ` — an or-pattern, left-associative;
1610
1845
  // matches if any alternative matches. `|` is a pattern-position operator
1611
1846
  // only — not a valid expression operator, and lexically distinct from
@@ -1621,6 +1856,29 @@ module.exports = grammar({
1621
1856
  // never nested, parenthesized, or one alternative of an outer `|`-chain).
1622
1857
  paren_pattern: ($) => seq("(", $._pattern, ")"),
1623
1858
  wildcard_pattern: () => "_",
1859
+ // #2699 (patterns track, slice 5; events slice 4b, #990): an inclusive
1860
+ // integer range — `lo..hi`, `lo..` or `..hi`, each bound an optionally
1861
+ // negative `Int` literal. One production shared by match patterns and
1862
+ // `via schema(...)`. A bare `..` is never a range (it is `list_rest`), and
1863
+ // `0..9` lexes as `number_literal ".." number_literal` since a float needs
1864
+ // a digit on both sides of its `.`.
1865
+ int_range: ($) => {
1866
+ // Spelled out in each position (not a shared rule), so the parser does
1867
+ // not have to reduce a bound before it can tell a range from a literal.
1868
+ const bound = seq(optional("-"), $.number_literal);
1869
+ return choice(
1870
+ // Right-associative: after `x is 5..` a `-` is the high bound's sign
1871
+ // (the hand-written parser reads it greedily), never a subtraction.
1872
+ prec.right(
1873
+ seq(
1874
+ field("lo", bound),
1875
+ "..",
1876
+ optional(field("hi", bound)),
1877
+ ),
1878
+ ),
1879
+ seq("..", field("hi", bound)),
1880
+ );
1881
+ },
1624
1882
  // v0.130 §2.3.4: a literal pattern — an integer (optionally negated), a
1625
1883
  // string, or a boolean. A closed set (ADR 0001); no `Float`, no `()`.
1626
1884
  literal_pattern: ($) =>
@@ -1666,7 +1924,7 @@ module.exports = grammar({
1666
1924
  is_expr: ($) =>
1667
1925
  prec.left(
1668
1926
  PREC.is,
1669
- seq(field("value", $._expression), "is", field("pattern", $._pattern)),
1927
+ seq(field("value", $._expression), "is", field("pattern", $._is_pattern)),
1670
1928
  ),
1671
1929
 
1672
1930
  binary_expr: ($) =>
@@ -1709,6 +1967,7 @@ module.exports = grammar({
1709
1967
  $.block,
1710
1968
  $.number_literal,
1711
1969
  $.float_literal,
1970
+ $.decimal_literal,
1712
1971
  $.string_literal,
1713
1972
  $.boolean_literal,
1714
1973
  $.unit_literal,
@@ -1717,7 +1976,7 @@ module.exports = grammar({
1717
1976
  $._contextual_identifier,
1718
1977
  ),
1719
1978
 
1720
- // `trace` and `advance` are contextual in the compiler — ordinary
1979
+ // `trace`, `advance` and `par` are contextual in the compiler — ordinary
1721
1980
  // identifiers outside `trace(Cap.op)` / `advance Clock by …`. Here, as
1722
1981
  // `word`-shaped literals, they are extracted as keyword tokens, and where an
1723
1982
  // identifier and the keyword are both valid (an expression or statement
@@ -1725,7 +1984,8 @@ module.exports = grammar({
1725
1984
  // `identifier` in exactly those positions. The next tokens decide: `advance`
1726
1985
  // followed by an identifier can only open an `advance_stmt`, and `trace (`
1727
1986
  // is a declared conflict that `trace_expr`'s dynamic precedence settles.
1728
- _contextual_identifier: ($) => alias(choice("trace", "advance"), $.identifier),
1987
+ _contextual_identifier: ($) =>
1988
+ alias(choice("trace", "advance", "par"), $.identifier),
1729
1989
 
1730
1990
  // v0.20a: a lambda — `(params) => expr | block`. Conflicts with
1731
1991
  // paren_expr/unit_literal until the `=>` disambiguates (GLR conflict
@@ -1739,11 +1999,44 @@ module.exports = grammar({
1739
1999
  field("body", choice($._expression, $.block)),
1740
2000
  ),
1741
2001
 
2002
+ // #2712 (patterns track, slice 6): a parameter is a name with an optional
2003
+ // annotation, or an unannotated record pattern whose fields are bindings.
1742
2004
  lambda_param: ($) =>
2005
+ choice(
2006
+ seq(
2007
+ field("name", choice($.identifier, $._contextual_identifier)),
2008
+ optional(seq(":", field("type", $._type_ref))),
2009
+ ),
2010
+ field("pattern", alias($._lambda_record_pattern, $.record_pattern)),
2011
+ ),
2012
+ // The irrefutable subset a lambda parameter admits: each field is a bare
2013
+ // name, `name: binding`, `name: _`, or a nested such pattern. (A bare
2014
+ // capitalised name is a variant, a refutable pattern the compiler reports.)
2015
+ _lambda_record_pattern: ($) =>
1743
2016
  seq(
1744
- field("name", choice($.identifier, $._contextual_identifier)),
1745
- optional(seq(":", field("type", $._type_ref))),
2017
+ field("type", $.identifier),
2018
+ "{",
2019
+ repeat(seq(alias($._lambda_record_field, $.record_pattern_field), ",")),
2020
+ "..",
2021
+ "}",
1746
2022
  ),
2023
+ _lambda_record_field: ($) =>
2024
+ choice(
2025
+ seq(
2026
+ field("name", $.identifier),
2027
+ ":",
2028
+ field(
2029
+ "pattern",
2030
+ choice(
2031
+ $._lambda_field_binding,
2032
+ $.wildcard_pattern,
2033
+ alias($._lambda_record_pattern, $.record_pattern),
2034
+ ),
2035
+ ),
2036
+ ),
2037
+ field("name", $.identifier),
2038
+ ),
2039
+ _lambda_field_binding: ($) => $.identifier,
1747
2040
 
1748
2041
  paren_expr: ($) => seq("(", $._expression, ")"),
1749
2042
 
@@ -1760,7 +2053,7 @@ module.exports = grammar({
1760
2053
  field("method", $.identifier),
1761
2054
  // v0.22b: optional explicit type arguments on a qualified static —
1762
2055
  // `Json.decode[T](…)` (same-line rule as `call` type application).
1763
- optional(seq("[", sep1(field("type_arg", $._type_ref), ","), "]")),
2056
+ optional(seq(alias($._type_app_open, "["), sep1(field("type_arg", $._type_ref), ","), "]")),
1764
2057
  "(",
1765
2058
  optional(sep1(field("arg", $._expression), ",")),
1766
2059
  optional(","),
@@ -1775,12 +2068,25 @@ module.exports = grammar({
1775
2068
  ),
1776
2069
 
1777
2070
  call: ($) =>
1778
- prec.left(
1779
- PREC.postfix,
2071
+ choice(
2072
+ prec.left(
2073
+ PREC.postfix,
2074
+ seq(
2075
+ field("name", choice($.identifier, $._contextual_identifier)),
2076
+ "(",
2077
+ optional(sep1(field("arg", $._expression), ",")),
2078
+ optional(","),
2079
+ ")",
2080
+ ),
2081
+ ),
2082
+ // v0.20a: explicit type arguments — `name[T, U](…)`. The `[` is the
2083
+ // same-line external token (#2700), so a list-pattern arm on the next
2084
+ // line is never read as a type application.
1780
2085
  seq(
1781
2086
  field("name", choice($.identifier, $._contextual_identifier)),
1782
- // v0.20a: optional explicit type arguments — `name[T, U](…)`.
1783
- optional(seq("[", sep1(field("type_arg", $._type_ref), ","), "]")),
2087
+ alias($._type_app_open, "["),
2088
+ sep1(field("type_arg", $._type_ref), ","),
2089
+ "]",
1784
2090
  "(",
1785
2091
  optional(sep1(field("arg", $._expression), ",")),
1786
2092
  optional(","),
@@ -1888,20 +2194,48 @@ module.exports = grammar({
1888
2194
  // as a method call on an integer literal.
1889
2195
  float_literal: () =>
1890
2196
  /[0-9]+(_[0-9]+)*\.[0-9]+(_[0-9]+)*([eE][+-]?[0-9]+(_[0-9]+)*)?|[0-9]+(_[0-9]+)*[eE][+-]?[0-9]+(_[0-9]+)*/,
2197
+ // #2622: a decimal literal - digits, an optional fraction, a trailing `d`.
2198
+ decimal_literal: () => /[0-9]+(_[0-9]+)*(\.[0-9]+(_[0-9]+)*)?d/,
1891
2199
  string_literal: ($) =>
1892
- seq(
1893
- '"',
1894
- // #2203: string text is one high-precedence immediate token, so a
1895
- // `//` inside a string (a URL) is text, not a line comment.
1896
- repeat(
1897
- choice(
1898
- token.immediate(prec(1, /[^"\\\n]+/)),
1899
- /\\[nt"\\]/,
1900
- $.string_interpolation,
2200
+ choice(
2201
+ seq(
2202
+ '"',
2203
+ // #2203: string text is one high-precedence immediate token, so a
2204
+ // `//` inside a string (a URL) is text, not a line comment.
2205
+ repeat(
2206
+ choice(
2207
+ token.immediate(prec(1, /[^"\\\n]+/)),
2208
+ $.escape_sequence,
2209
+ $.string_interpolation,
2210
+ ),
1901
2211
  ),
2212
+ '"',
1902
2213
  ),
1903
- '"',
1904
- ),
2214
+ // #2619: a multi-line string. The opening `"""` is followed by a
2215
+ // newline; the body may hold lone or doubled quotes; escapes and
2216
+ // interpolation are those of an ordinary string. (Indentation stripping
2217
+ // is a value rule, not a shape: the compiler checks it.)
2218
+ seq(
2219
+ token(/"""\r?\n/),
2220
+ repeat(
2221
+ choice(
2222
+ token.immediate(prec(1, /[^"\\]+/)),
2223
+ token.immediate(prec(1, /"{1,2}[^"\\]/)),
2224
+ $.escape_sequence,
2225
+ $.string_interpolation,
2226
+ ),
2227
+ ),
2228
+ '"""',
2229
+ ),
2230
+ // #2619: a raw string (`r"…"`, `r#"…"#`, `r"""…"""`): no escapes, no
2231
+ // interpolation. An external token — the `#` count is not regular.
2232
+ $.raw_string,
2233
+ ),
2234
+ // `\n \t \r \0 \" \\` and `\u{1–6 hex digits}` (#2619). Whether a `\u{…}`
2235
+ // names a scalar value (not a surrogate, at most 10FFFF) is the compiler's
2236
+ // check.
2237
+ escape_sequence: () =>
2238
+ token.immediate(choice(/\\[ntr0"\\]/, /\\u\{[0-9a-fA-F]{1,6}\}/)),
1905
2239
  // v0.43: an interpolation hole `\(expr)`. A bare `\(` was an invalid
1906
2240
  // escape, so this is backward-compatible; `\\(` is an escaped backslash
1907
2241
  // followed by a literal `(`, so it is not a hole.