rip-lang 3.15.4 → 3.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +6 -4
  2. package/bin/rip +167 -12
  3. package/docs/AGENTS.md +1 -1
  4. package/docs/RIP-APP.md +808 -0
  5. package/docs/RIP-DUCKDB.md +477 -0
  6. package/docs/RIP-INTRO.md +396 -0
  7. package/docs/RIP-LANG.md +59 -5
  8. package/docs/RIP-SCHEMA.md +191 -8
  9. package/docs/RIP-TYPES.md +74 -103
  10. package/docs/demo/README.md +4 -3
  11. package/docs/dist/rip.js +3627 -1470
  12. package/docs/dist/rip.min.js +671 -244
  13. package/docs/dist/rip.min.js.br +0 -0
  14. package/docs/example/index.json +7 -7
  15. package/docs/example/index.json.br +0 -0
  16. package/docs/extensions/duckdb/manifest.json +1 -1
  17. package/docs/extensions/duckdb/v1.5.2/linux_amd64/ripdb.duckdb_extension.gz +0 -0
  18. package/docs/extensions/duckdb/v1.5.2/osx_arm64/ripdb.duckdb_extension.gz +0 -0
  19. package/docs/extensions/vscode/print/index.html +2 -1
  20. package/docs/extensions/vscode/print/print-1.0.13.vsix +0 -0
  21. package/docs/extensions/vscode/print/print-1.0.14.vsix +0 -0
  22. package/docs/extensions/vscode/print/print-latest.vsix +0 -0
  23. package/docs/extensions/vscode/rip/rip-0.5.15.vsix +0 -0
  24. package/docs/extensions/vscode/rip/rip-latest.vsix +0 -0
  25. package/docs/ui/bundle.json +61 -0
  26. package/docs/ui/bundle.json.br +0 -0
  27. package/docs/ui/hljs-rip.js +0 -7
  28. package/docs/ui/index.css +66 -23
  29. package/docs/ui/index.html +6 -6
  30. package/package.json +9 -3
  31. package/rip-loader.js +64 -2
  32. package/src/AGENTS.md +63 -36
  33. package/src/browser.js +96 -14
  34. package/src/compiler.js +960 -143
  35. package/src/components.js +794 -88
  36. package/src/{types-emit.js → dts.js} +181 -71
  37. package/src/grammar/README.md +1 -1
  38. package/src/grammar/grammar.rip +111 -97
  39. package/src/lexer.js +132 -18
  40. package/src/parser.js +203 -205
  41. package/src/repl.js +74 -6
  42. package/src/schema/runtime-orm.js +168 -4
  43. package/src/schema/runtime-validate.js +146 -2
  44. package/src/schema/runtime.generated.js +314 -6
  45. package/src/schema/schema.js +5 -5
  46. package/src/sourcemaps.js +277 -1
  47. package/src/stdlib.js +253 -0
  48. package/src/typecheck.js +2023 -106
  49. package/src/types.js +127 -7
  50. package/docs/ui/accordion.rip +0 -103
  51. package/docs/ui/alert-dialog.rip +0 -53
  52. package/docs/ui/autocomplete.rip +0 -115
  53. package/docs/ui/avatar.rip +0 -37
  54. package/docs/ui/badge.rip +0 -15
  55. package/docs/ui/breadcrumb.rip +0 -47
  56. package/docs/ui/button-group.rip +0 -26
  57. package/docs/ui/button.rip +0 -23
  58. package/docs/ui/card.rip +0 -25
  59. package/docs/ui/carousel.rip +0 -110
  60. package/docs/ui/checkbox-group.rip +0 -61
  61. package/docs/ui/checkbox.rip +0 -33
  62. package/docs/ui/collapsible.rip +0 -50
  63. package/docs/ui/combobox.rip +0 -130
  64. package/docs/ui/context-menu.rip +0 -88
  65. package/docs/ui/date-picker.rip +0 -206
  66. package/docs/ui/dialog.rip +0 -60
  67. package/docs/ui/drawer.rip +0 -58
  68. package/docs/ui/editable-value.rip +0 -82
  69. package/docs/ui/field.rip +0 -53
  70. package/docs/ui/fieldset.rip +0 -22
  71. package/docs/ui/form.rip +0 -39
  72. package/docs/ui/grid.rip +0 -901
  73. package/docs/ui/input-group.rip +0 -28
  74. package/docs/ui/input.rip +0 -36
  75. package/docs/ui/label.rip +0 -16
  76. package/docs/ui/menu.rip +0 -134
  77. package/docs/ui/menubar.rip +0 -151
  78. package/docs/ui/meter.rip +0 -36
  79. package/docs/ui/multi-select.rip +0 -203
  80. package/docs/ui/native-select.rip +0 -33
  81. package/docs/ui/nav-menu.rip +0 -126
  82. package/docs/ui/number-field.rip +0 -162
  83. package/docs/ui/otp-field.rip +0 -89
  84. package/docs/ui/pagination.rip +0 -123
  85. package/docs/ui/popover.rip +0 -93
  86. package/docs/ui/preview-card.rip +0 -75
  87. package/docs/ui/progress.rip +0 -25
  88. package/docs/ui/radio-group.rip +0 -57
  89. package/docs/ui/resizable.rip +0 -123
  90. package/docs/ui/scroll-area.rip +0 -145
  91. package/docs/ui/select.rip +0 -151
  92. package/docs/ui/separator.rip +0 -17
  93. package/docs/ui/skeleton.rip +0 -22
  94. package/docs/ui/slider.rip +0 -165
  95. package/docs/ui/spinner.rip +0 -17
  96. package/docs/ui/table.rip +0 -27
  97. package/docs/ui/tabs.rip +0 -113
  98. package/docs/ui/textarea.rip +0 -48
  99. package/docs/ui/toast.rip +0 -87
  100. package/docs/ui/toggle-group.rip +0 -71
  101. package/docs/ui/toggle.rip +0 -24
  102. package/docs/ui/toolbar.rip +0 -38
  103. package/docs/ui/tooltip.rip +0 -85
  104. package/src/app.rip +0 -1571
  105. package/src/sourcemap-merge.js +0 -287
  106. /package/docs/demo/{components → routes}/_layout.rip +0 -0
  107. /package/docs/demo/{components → routes}/about.rip +0 -0
  108. /package/docs/demo/{components → routes}/card.rip +0 -0
  109. /package/docs/demo/{components → routes}/counter.rip +0 -0
  110. /package/docs/demo/{components → routes}/index.rip +0 -0
  111. /package/docs/demo/{components → routes}/todos.rip +0 -0
  112. /package/src/schema/{dts-emit.js → dts.js} +0 -0
@@ -23,6 +23,28 @@ o = (pattern, action, options) ->
23
23
  pattern = pattern.trim().replace /\s{2,}/g, ' '
24
24
  [pattern, action ?? 1, options]
25
25
 
26
+ # Generate the canonical "list-with-indent" production set used by
27
+ # AssignList, MapAssignList, ParamList, ArgList, ImportSpecifierList,
28
+ # ExportSpecifierList, and PickList.
29
+ #
30
+ # Self — the list non-terminal's own name (used in recursive refs)
31
+ # Item — the element non-terminal name
32
+ # empty — when true, an empty input reduces to []
33
+ # bareIndent — when true, an INDENT-wrapped form unwraps via "return inner"
34
+ #
35
+ # Output is identical to what the seven sites used to write by hand. Any new
36
+ # list non-terminal added in the future inherits the same shape automatically.
37
+ listRules = (Self, Item, opts) ->
38
+ opts ??= {}
39
+ rules = []
40
+ rules.push(o '', '[]') if opts.empty
41
+ rules.push o Item, '[1]'
42
+ rules.push o "#{Self} , #{Item}", '[...1, 3]'
43
+ rules.push o "#{Self} OptComma TERMINATOR #{Item}", '[...1, 4]'
44
+ rules.push(o "INDENT #{Self} OptComma OUTDENT", 2) if opts.bareIndent
45
+ rules.push o "#{Self} OptComma INDENT #{Self} OptComma OUTDENT", '[...1, ...4]'
46
+ rules
47
+
26
48
  mode = 'sexp'
27
49
 
28
50
  grammar =
@@ -115,7 +137,7 @@ grammar =
115
137
 
116
138
  # Immediate values — numbers, strings, booleans, etc.
117
139
  Literal: [
118
- o 'AlphaNumeric'
140
+ o 'Atom'
119
141
  o 'JS' # Embedded JavaScript (backticks)
120
142
  o 'Regex'
121
143
  o 'UNDEFINED' , '"undefined"'
@@ -126,7 +148,8 @@ grammar =
126
148
  o 'SYMBOL' , '["symbol", 1]'
127
149
  ]
128
150
 
129
- AlphaNumeric: [
151
+ # A primitive literal whose code is its own representation.
152
+ Atom: [
130
153
  o 'NUMBER'
131
154
  o 'String'
132
155
  ]
@@ -153,7 +176,7 @@ grammar =
153
176
  ]
154
177
 
155
178
  Interpolations: [
156
- o 'InterpolationChunk' , '[1]'
179
+ o 'InterpolationChunk' , '[1]'
157
180
  o 'Interpolations InterpolationChunk', '[...1, 2]'
158
181
  ]
159
182
 
@@ -231,35 +254,40 @@ grammar =
231
254
  o 'Object' # Object destructuring
232
255
  ]
233
256
 
257
+ # Subjectable — anything that can carry a postfix accessor chain (`.prop`,
258
+ # `[idx]`, `.{a,b}`, etc.). Value covers ordinary expressions; Code lets
259
+ # an arrow function be the subject too, so `(-> 5).foo`, `(=> x).{a, b}`,
260
+ # and `(-> 5)[i]` all parse via the same rules below — no asymmetry
261
+ # between the two subject paths.
262
+ Subjectable: [
263
+ o 'Value'
264
+ o 'Code'
265
+ ]
266
+
234
267
  # Simple assignable targets — identifiers, properties, and indexed values.
235
268
  SimpleAssignable: [
236
269
  o 'Identifier'
237
270
  o 'ThisProperty'
238
271
  # Property access
239
- o 'Value . Property' , '[".", 1, 3]'
240
- o 'Value ?. Property', '["?.", 1, 3]'
272
+ o 'Subjectable . Property' , '[".", 1, 3]'
273
+ o 'Subjectable ?. Property', '["?.", 1, 3]'
241
274
  # Indexing
242
- o 'Value INDEX_START Expression INDEX_END' , '["[]", 1, 3]'
243
- o 'Value INDEX_START INDENT Expression OUTDENT INDEX_END' , '["[]", 1, 4]'
244
- o 'Value INDEX_START Slice INDEX_END' , '["[]", 1, 3]'
245
- o 'Value INDEX_START INDENT Slice OUTDENT INDEX_END' , '["[]", 1, 4]'
275
+ o 'Subjectable INDEX_START Expression INDEX_END' , '["[]", 1, 3]'
276
+ o 'Subjectable INDEX_START INDENT Expression OUTDENT INDEX_END' , '["[]", 1, 4]'
277
+ o 'Subjectable INDEX_START Slice INDEX_END' , '["[]", 1, 3]'
278
+ o 'Subjectable INDEX_START INDENT Slice OUTDENT INDEX_END' , '["[]", 1, 4]'
246
279
  # Pick operator: obj.{a, b: c, d = default} → { a: obj.a, c: obj.b, d: obj.d ?? default }
247
280
  # Heads `.{}` and `?.{}` are syntax-shape strings that can't collide with
248
281
  # a user function named `pick` (Rip encodes calls as [name, ...args]).
249
- o 'Value PICK_START PickList OptComma PICK_END' , '[".{}", 1, ...3]'
250
- o 'Value OPTPICK_START PickList OptComma PICK_END' , '["?.{}", 1, ...3]'
251
- o 'Value PICK_START INDENT PickList OptComma OUTDENT PICK_END', '[".{}", 1, ...4]'
252
- o 'Value OPTPICK_START INDENT PickList OptComma OUTDENT PICK_END', '["?.{}", 1, ...4]'
282
+ o 'Subjectable PICK_START PickList OptComma PICK_END' , '[".{}", 1, ...3]'
283
+ o 'Subjectable OPTPICK_START PickList OptComma PICK_END' , '["?.{}", 1, ...3]'
284
+ o 'Subjectable PICK_START INDENT PickList OptComma OUTDENT PICK_END', '[".{}", 1, ...4]'
285
+ o 'Subjectable OPTPICK_START INDENT PickList OptComma OUTDENT PICK_END', '["?.{}", 1, ...4]'
253
286
  # Regex indexing with capture group
254
- o 'Value INDEX_START RegexWithIndex INDEX_END' , '[$3[0], $1, ...$3.slice(1)]'
287
+ o 'Subjectable INDEX_START RegexWithIndex INDEX_END' , '[$3[0], $1, ...$3.slice(1)]'
255
288
  # ES6 optional indexing (?.[ )
256
- o 'Value ES6_OPTIONAL_INDEX INDEX_START Expression INDEX_END' , '["optindex", 1, 4]'
257
- o 'Value ES6_OPTIONAL_INDEX INDEX_START INDENT Expression OUTDENT INDEX_END', '["optindex", 1, 5]'
258
- # Arrow function with accessor
259
- o 'Code . Property' , '[".", 1, 3]'
260
- o 'Code ?. Property' , '["?.", 1, 3]'
261
- o 'Code INDEX_START Expression INDEX_END' , '["[]", 1, 3]'
262
- o 'Code INDEX_START INDENT Expression OUTDENT INDEX_END' , '["[]", 1, 4]'
289
+ o 'Subjectable ES6_OPTIONAL_INDEX INDEX_START Expression INDEX_END' , '["optindex", 1, 4]'
290
+ o 'Subjectable ES6_OPTIONAL_INDEX INDEX_START INDENT Expression OUTDENT INDEX_END', '["optindex", 1, 5]'
263
291
  ]
264
292
 
265
293
  # ============================================================================
@@ -276,13 +304,7 @@ grammar =
276
304
  o '{ AssignList OptComma }', '["object", ...2]'
277
305
  ]
278
306
 
279
- AssignList: [
280
- o '' , '[]'
281
- o 'AssignObj' , '[1]'
282
- o 'AssignList , AssignObj' , '[...1, 3]'
283
- o 'AssignList OptComma TERMINATOR AssignObj' , '[...1, 4]'
284
- o 'AssignList OptComma INDENT AssignList OptComma OUTDENT', '[...1, ...4]'
285
- ]
307
+ AssignList: listRules('AssignList', 'AssignObj', empty: true)
286
308
 
287
309
  AssignObj: [
288
310
  o 'ObjAssignable' , '[null, 1, 1]' # Shorthand: {x}
@@ -303,7 +325,7 @@ grammar =
303
325
  o 'SimpleObjAssignable'
304
326
  o '[ Expression ]' , '["dynamicKey", 2]'
305
327
  o '@ [ Expression ]', '["[]", "this", 3]'
306
- o 'AlphaNumeric'
328
+ o 'Atom'
307
329
  ]
308
330
 
309
331
  # Object spread/rest — ES6 prefix only
@@ -342,17 +364,10 @@ grammar =
342
364
  # ============================================================================
343
365
 
344
366
  MapLiteral: [
345
- o 'MAP_START MAP_END' , '["map-literal"]'
346
367
  o 'MAP_START MapAssignList OptComma MAP_END' , '["map-literal", ...2]'
347
368
  ]
348
369
 
349
- MapAssignList: [
350
- o '' , '[]'
351
- o 'MapAssignObj' , '[1]'
352
- o 'MapAssignList , MapAssignObj' , '[...1, 3]'
353
- o 'MapAssignList OptComma TERMINATOR MapAssignObj' , '[...1, 4]'
354
- o 'MapAssignList OptComma INDENT MapAssignList OptComma OUTDENT' , '[...1, ...4]'
355
- ]
370
+ MapAssignList: listRules('MapAssignList', 'MapAssignObj', empty: true)
356
371
 
357
372
  MapAssignObj: [
358
373
  o 'MapAssignable : Expression' , '[":", 1, 3]'
@@ -371,11 +386,7 @@ grammar =
371
386
  # numbers, or computed keys in v1.
372
387
  # ============================================================================
373
388
 
374
- PickList: [
375
- o 'PickItem' , '[1]'
376
- o 'PickList , PickItem' , '[...1, 3]'
377
- o 'PickList OptComma TERMINATOR PickItem', '[...1, 4]'
378
- ]
389
+ PickList: listRules('PickList', 'PickItem')
379
390
 
380
391
  PickItem: [
381
392
  o 'PickKey' , '[1, 1, null]' # shorthand {a} → ["a", "a", null]
@@ -393,7 +404,7 @@ grammar =
393
404
  o 'Identifier'
394
405
  o 'Property'
395
406
  o 'ThisProperty'
396
- o 'AlphaNumeric'
407
+ o 'Atom'
397
408
  o 'Regex'
398
409
  o 'BOOL'
399
410
  o 'NULL' , '"null"'
@@ -477,29 +488,25 @@ grammar =
477
488
 
478
489
  # Arrow functions: (params) -> body or (params) => body
479
490
  Code: [
480
- o 'PARAM_START ParamList PARAM_END FuncGlyph Block', '[4, 2, 5]'
481
- o 'FuncGlyph Block' , '[1, [], 2]'
491
+ o 'PARAM_START ParamList PARAM_END ArrowKind Block', '[4, 2, 5]'
492
+ o 'ArrowKind Block' , '[1, [], 2]'
482
493
  ]
483
494
 
484
495
  # Single-line arrow functions
485
496
  CodeLine: [
486
- o 'PARAM_START ParamList PARAM_END FuncGlyph Line', '[4, 2, 5]'
487
- o 'FuncGlyph Line' , '[1, [], 2]'
497
+ o 'PARAM_START ParamList PARAM_END ArrowKind Line', '[4, 2, 5]'
498
+ o 'ArrowKind Line' , '[1, [], 2]'
488
499
  ]
489
500
 
490
- FuncGlyph: [
501
+ # `->` is a regular function (binds its own `this`).
502
+ # `=>` is an arrow function (lexical `this`).
503
+ ArrowKind: [
491
504
  o '->'
492
505
  o '=>'
493
506
  ]
494
507
 
495
508
  # Parameter list
496
- ParamList: [
497
- o '' , '[]'
498
- o 'Param' , '[1]'
499
- o 'ParamList , Param' , '[...1, 3]'
500
- o 'ParamList OptComma TERMINATOR Param' , '[...1, 4]'
501
- o 'ParamList OptComma INDENT ParamList OptComma OUTDENT', '[...1, ...4]'
502
- ]
509
+ ParamList: listRules('ParamList', 'Param', empty: true)
503
510
 
504
511
  Param: [
505
512
  o 'ParamVar'
@@ -537,13 +544,7 @@ grammar =
537
544
  o 'CALL_START ArgList OptComma CALL_END', 2
538
545
  ]
539
546
 
540
- ArgList: [
541
- o 'Arg' , '[1]'
542
- o 'ArgList , Arg' , '[...1, 3]'
543
- o 'ArgList OptComma TERMINATOR Arg' , '[...1, 4]'
544
- o 'INDENT ArgList OptComma OUTDENT' , 2
545
- o 'ArgList OptComma INDENT ArgList OptComma OUTDENT', '[...1, ...4]'
546
- ]
547
+ ArgList: listRules('ArgList', 'Arg', bareIndent: true)
547
548
 
548
549
  Arg: [
549
550
  o 'Expression'
@@ -591,9 +592,14 @@ grammar =
591
592
  o 'INDENT Body OUTDENT', '["block", ...2]'
592
593
  ]
593
594
 
595
+ # Parenthetical mark-up: when the inner expression is a single `=` assignment,
596
+ # tag it with `.parenthesized = true` so the codegen ternary-hoist doesn't
597
+ # silently turn `(x = "a") if cond else "b"` (conditional assign) into
598
+ # `x = (cond ? "a" : "b")` (unconditional). The marker is harmless for any
599
+ # other consumer; it's just a flag the hoist consults.
594
600
  Parenthetical: [
595
- o '( Body )' , '$2.length === 1 ? $2[0] : ["block", ...$2]'
596
- o '( INDENT Body OUTDENT )', '$3.length === 1 ? $3[0] : ["block", ...$3]'
601
+ o '( Body )' , '$2.length === 1 ? (Array.isArray($2[0]) && $2[0][0] === "=" && ($2[0].parenthesized = true), $2[0]) : ["block", ...$2]'
602
+ o '( INDENT Body OUTDENT )', '$3.length === 1 ? (Array.isArray($3[0]) && $3[0][0] === "=" && ($3[0].parenthesized = true), $3[0]) : ["block", ...$3]'
597
603
  ]
598
604
 
599
605
  OptComma: [
@@ -631,10 +637,18 @@ grammar =
631
637
  # Control Flow
632
638
  # ============================================================================
633
639
 
634
- # If/Unless
640
+ # If/Unless — produces canonical right-recursive shape that matches the
641
+ # documented AST: ["if", cond, then, else?] where `else` may itself be
642
+ # an `["if", ...]` for elseif chains.
635
643
  IfBlock: [
636
- o 'IF Expression Block' , '["if", 2, 3]'
637
- o 'IfBlock ELSE IF Expression Block' , '$1.length === 3 ? ["if", $1[1], $1[2], ["if", $4, $5]] : [...$1, ["if", $4, $5]]'
644
+ o 'IF Expression Block' , '["if", 2, 3]'
645
+ o 'IF Expression Block IfElseTail' , '["if", 2, 3, 4]'
646
+ ]
647
+
648
+ IfElseTail: [
649
+ o 'ELSE IF Expression Block' , '["if", 3, 4]'
650
+ o 'ELSE IF Expression Block IfElseTail' , '["if", 3, 4, 5]'
651
+ o 'ELSE Block' , 2
638
652
  ]
639
653
 
640
654
  UnlessBlock: [
@@ -644,7 +658,6 @@ grammar =
644
658
 
645
659
  If: [
646
660
  o 'IfBlock'
647
- o 'IfBlock ELSE Block' , '$1.length === 3 ? ["if", $1[1], $1[2], $3] : [...$1, $3]'
648
661
  o 'UnlessBlock'
649
662
  o 'Statement POST_IF Expression' , '["if", 3, [1]]'
650
663
  o 'Expression POST_IF Expression' , '["if", 3, [1]]'
@@ -665,20 +678,21 @@ grammar =
665
678
  Catch: [
666
679
  o 'CATCH Identifier Block', '[2, 3]'
667
680
  o 'CATCH Object Block' , '[2, 3]'
681
+ o 'CATCH Array Block' , '[2, 3]'
668
682
  o 'CATCH Block' , '[null, 2]'
669
683
  ]
670
684
 
671
685
  # Switch/When
672
686
  Switch: [
673
- o 'SWITCH Expression INDENT Whens OUTDENT' , '["switch", 2, 4, null]'
674
- o 'SWITCH Expression INDENT Whens ELSE Block OUTDENT' , '["switch", 2, 4, 6]'
675
- o 'SWITCH INDENT Whens OUTDENT' , '["switch", null, 3, null]'
676
- o 'SWITCH INDENT Whens ELSE Block OUTDENT' , '["switch", null, 3, 5]'
687
+ o 'SWITCH Expression INDENT Cases OUTDENT' , '["switch", 2, 4, null]'
688
+ o 'SWITCH Expression INDENT Cases ELSE Block OUTDENT' , '["switch", 2, 4, 6]'
689
+ o 'SWITCH INDENT Cases OUTDENT' , '["switch", null, 3, null]'
690
+ o 'SWITCH INDENT Cases ELSE Block OUTDENT' , '["switch", null, 3, 5]'
677
691
  ]
678
692
 
679
- Whens: [
693
+ Cases: [
680
694
  o 'When' , '[1]'
681
- o 'Whens When', '[...1, 2]'
695
+ o 'Cases When', '[...1, 2]'
682
696
  ]
683
697
 
684
698
  When: [
@@ -785,15 +799,27 @@ grammar =
785
799
  # Classes
786
800
  # ============================================================================
787
801
 
802
+ # Things that can appear as a class name. `Identifier` covers ordinary
803
+ # `class Foo`; `ThisProperty` covers nested static classes inside a class
804
+ # body (`class @Inner` → `static Inner = class { ... }`). The previous
805
+ # rule used `SimpleAssignable`, which also accepted `class App.Sub` and
806
+ # `class arr[0]` — both produced broken JS (`class .,App,Sub`) because
807
+ # the codegen interpolated the array-shaped name into a `class ${name}`
808
+ # template.
809
+ ClassName: [
810
+ o 'Identifier'
811
+ o 'ThisProperty'
812
+ ]
813
+
788
814
  Class: [
789
- o 'CLASS' , '["class", null, null]'
790
- o 'CLASS Block' , '["class", null, null, 2]'
791
- o 'CLASS EXTENDS Expression' , '["class", null, 3]'
792
- o 'CLASS EXTENDS Expression Block' , '["class", null, 3, 4]'
793
- o 'CLASS SimpleAssignable' , '["class", 2, null]'
794
- o 'CLASS SimpleAssignable Block' , '["class", 2, null, 3]'
795
- o 'CLASS SimpleAssignable EXTENDS Expression' , '["class", 2, 4]'
796
- o 'CLASS SimpleAssignable EXTENDS Expression Block', '["class", 2, 4, 5]'
815
+ o 'CLASS' , '["class", null, null]'
816
+ o 'CLASS Block' , '["class", null, null, 2]'
817
+ o 'CLASS EXTENDS Expression' , '["class", null, 3]'
818
+ o 'CLASS EXTENDS Expression Block' , '["class", null, 3, 4]'
819
+ o 'CLASS ClassName' , '["class", 2, null]'
820
+ o 'CLASS ClassName Block' , '["class", 2, null, 3]'
821
+ o 'CLASS ClassName EXTENDS Expression' , '["class", 2, 4]'
822
+ o 'CLASS ClassName EXTENDS Expression Block' , '["class", 2, 4, 5]'
797
823
  ]
798
824
 
799
825
  # ============================================================================
@@ -870,13 +896,7 @@ grammar =
870
896
  o 'IMPORT ImportDefaultSpecifier , { ImportSpecifierList OptComma } FROM String', '["import", 2, 5, 9]'
871
897
  ]
872
898
 
873
- ImportSpecifierList: [
874
- o 'ImportSpecifier' , '[1]'
875
- o 'ImportSpecifierList , ImportSpecifier' , '[...1, 3]'
876
- o 'ImportSpecifierList OptComma TERMINATOR ImportSpecifier' , '[...1, 4]'
877
- o 'INDENT ImportSpecifierList OptComma OUTDENT' , 2
878
- o 'ImportSpecifierList OptComma INDENT ImportSpecifierList OptComma OUTDENT', '[...1, ...4]'
879
- ]
899
+ ImportSpecifierList: listRules('ImportSpecifierList', 'ImportSpecifier', bareIndent: true)
880
900
 
881
901
  ImportSpecifier: [
882
902
  o 'Identifier'
@@ -906,7 +926,7 @@ grammar =
906
926
  o 'EXPORT ReactiveAssign' , '["export", 2]'
907
927
  o 'EXPORT ComputedAssign' , '["export", 2]'
908
928
  o 'EXPORT ReadonlyAssign' , '["export", 2]'
909
- o 'EXPORT Effect' , '["export", 2]'
929
+ o 'EXPORT Effect' , '["export", 2]'
910
930
  o 'EXPORT DEFAULT Expression' , '["export-default", 3]'
911
931
  o 'EXPORT DEFAULT INDENT Object OUTDENT' , '["export-default", 4]'
912
932
  o 'EXPORT EXPORT_ALL FROM String' , '["export-all", 4]'
@@ -914,13 +934,7 @@ grammar =
914
934
  o 'EXPORT { ExportSpecifierList OptComma } FROM String', '["export-from", 3, 7]'
915
935
  ]
916
936
 
917
- ExportSpecifierList: [
918
- o 'ExportSpecifier' , '[1]'
919
- o 'ExportSpecifierList , ExportSpecifier' , '[...1, 3]'
920
- o 'ExportSpecifierList OptComma TERMINATOR ExportSpecifier' , '[...1, 4]'
921
- o 'INDENT ExportSpecifierList OptComma OUTDENT' , 2
922
- o 'ExportSpecifierList OptComma INDENT ExportSpecifierList OptComma OUTDENT', '[...1, ...4]'
923
- ]
937
+ ExportSpecifierList: listRules('ExportSpecifierList', 'ExportSpecifier', bareIndent: true)
924
938
 
925
939
  ExportSpecifier: [
926
940
  o 'Identifier'
package/src/lexer.js CHANGED
@@ -7,7 +7,7 @@
7
7
  //
8
8
  // Design principles:
9
9
  // - Every token carries .pre (whitespace count before it)
10
- // - Every token carries .data (metadata: await, predicate, quote, etc.)
10
+ // - Every token carries .data (metadata: bang, optional, quote, etc.)
11
11
  // - Every token carries .loc (location: row, col, len)
12
12
  // - Indentation is derived from .pre, not tracked during lexing
13
13
  // - Token categories use Sets for O(1) membership tests
@@ -23,8 +23,8 @@
23
23
  // token.newLine — true if preceded by a newline
24
24
  //
25
25
  // Identifier suffixes:
26
- // ! — dammit operator: fetch!() → await fetch()
27
- // ? — predicate: empty? → isEmpty (returns boolean convention)
26
+ // ! — bang: fetch!() → await fetch() (call site) | foo! = -> → void (definition)
27
+ // ? — optional: empty? → (empty != null) (existence check / optional marker)
28
28
  //
29
29
  // The 9 tokenizer methods (in priority order):
30
30
  // 1. identifier — variables, keywords, properties, ! and ? suffixes
@@ -246,7 +246,7 @@ let UNARY_MATH = new Set(['!', '~']);
246
246
  // Regex Patterns
247
247
  // ==========================================================================
248
248
 
249
- // Identifier: word chars + optional trailing ! (await) or ? (predicate)
249
+ // Identifier: word chars + optional trailing ! (bang) or ? (optional)
250
250
  // The ? suffix is only captured when NOT followed by . ? ! [ ( to avoid
251
251
  // conflict with ?. (optional chaining), ?? (nullish), ?! (presence), ?.( and ?.[
252
252
  let IDENTIFIER_RE = /^(?!\d)((?:(?!\s)[$\w\x7f-\uffff])+(?:!|[?](?![.?![(]))?)([^\n\S]*:(?![=:]))?/;
@@ -456,6 +456,21 @@ export class Lexer {
456
456
  return p ? p[1] : undefined;
457
457
  }
458
458
 
459
+ // True when the next type identifier sits in a method-shorthand return-type
460
+ // slot — i.e. previous token is `:` and the one before that closes a
461
+ // parameter list (`)`, `CALL_END`, `PARAM_END`). This lets reserved words
462
+ // like `void` appear as a return type on an interface/type method.
463
+ isReturnTypeSlot() {
464
+ let n = this.tokens.length;
465
+ if (n < 2) return false;
466
+ let t1 = this.tokens[n - 1];
467
+ if (!t1 || t1[1] !== ':') return false;
468
+ let t2 = this.tokens[n - 2];
469
+ if (!t2) return false;
470
+ let tag2 = t2[0];
471
+ return tag2 === ')' || tag2 === 'CALL_END' || tag2 === 'PARAM_END';
472
+ }
473
+
459
474
  // True when the next identifier/keyword-shaped token would sit in a
460
475
  // pick-key position of a `.{` or `?.{` body. Used by identifierToken()
461
476
  // to tag reserved-word keys (`default`, `class`, `delete`, …) as
@@ -504,8 +519,10 @@ export class Lexer {
504
519
  // Handles: variables, keywords, properties, aliases
505
520
  //
506
521
  // Suffix operators on identifiers:
507
- // ! → dammit operator (await): fetch!() await fetch()
508
- // ? → predicate (boolean): empty? isEmpty
522
+ // ! → bang: a neutral "trailing !" flag. Resolved by context downstream —
523
+ // dammit/await at a call site (fetch!() await fetch()), or the void
524
+ // marker at a function definition (foo! = -> / def foo! → no return).
525
+ // ? → optional (existence): empty? → (empty != null)
509
526
  //
510
527
  // The ? suffix is captured by IDENTIFIER_RE only when NOT followed by
511
528
  // . ? [ ( — so x?.y (optional chaining) and x?? (nullish coalescing)
@@ -621,8 +638,8 @@ export class Lexer {
621
638
 
622
639
  // Reserved words (check the base form, not the suffixed form)
623
640
  if (tag === 'IDENTIFIER' && RESERVED.has(baseId)) {
624
- if (baseId === 'void' && (this.inTypeAnnotation || this.prevTag() === '=>')) {
625
- // ok — void used as a type (after :: or =>)
641
+ if (baseId === 'void' && (this.inTypeAnnotation || this.prevTag() === '=>' || this.isReturnTypeSlot())) {
642
+ // ok — void used as a type (after ::, =>, or `):` method-shorthand return slot)
626
643
  } else {
627
644
  syntaxError(`reserved word '${baseId}'`, {row: this.row, col: this.col, len: idLen});
628
645
  }
@@ -640,16 +657,20 @@ export class Lexer {
640
657
  }
641
658
  }
642
659
 
643
- // --- Dammit operator: trailing ! → await ---
660
+ // --- Bang: trailing ! → context-resolved (await at call site / void at def) ---
644
661
  if (id.length > 1 && id.endsWith('!')) {
645
- data.await = true;
662
+ data.bang = true;
646
663
  id = id.slice(0, -1);
647
664
  }
648
665
 
649
- // --- Predicate operator: trailing ? → boolean convention ---
650
- // empty? isEmpty, active?isActive, valid? → isValid
666
+ // --- Optional marker: trailing ? ---
667
+ // Identifier-position: existence check (`empty?``(empty != null)`).
668
+ // Property-name position: optional prop / type field (`@label?:: T`,
669
+ // `{ x?:: T }`, `def f(x?:: T)`). The flag is consumed by the type/
670
+ // component/schema emitters; the runtime semantics for `name?` as an
671
+ // existence check are unchanged.
651
672
  if (id.length > 1 && id.endsWith('?')) {
652
- data.predicate = true;
673
+ data.optional = true;
653
674
  id = id.slice(0, -1);
654
675
  }
655
676
 
@@ -885,7 +906,40 @@ export class Lexer {
885
906
  else if (tk[0] === 'COMPARE' && tk[1] === '<') depth--;
886
907
  if (depth === 0 && tk[0] === 'TYPE_ANNOTATION') return false;
887
908
  if (depth === 0 && tk[0] === 'IDENTIFIER' && tk[1] === 'type') return false;
888
- if (tk[0] === 'TERMINATOR' || tk[0] === 'INDENT' || tk[0] === 'OUTDENT') break;
909
+ if (tk[0] === 'INTERFACE') return false;
910
+ if (tk[0] === 'TERMINATOR' || tk[0] === 'INDENT' || tk[0] === 'OUTDENT') {
911
+ // The matching `<` is on the current line, but the `type`/`interface`
912
+ // keyword may be on a previous line (multi-line type alias body).
913
+ // Walk back to the INDENT that opens our containing block and check
914
+ // what introduced it.
915
+ let openerIdx = -1;
916
+ if (tk[0] === 'INDENT') {
917
+ openerIdx = k;
918
+ } else {
919
+ let blockDepth = (tk[0] === 'OUTDENT') ? 1 : 0;
920
+ for (let m = k - 1; m >= 0; m--) {
921
+ let mt = this.tokens[m];
922
+ if (mt[0] === 'OUTDENT') blockDepth++;
923
+ else if (mt[0] === 'INDENT') {
924
+ if (blockDepth === 0) { openerIdx = m; break; }
925
+ blockDepth--;
926
+ }
927
+ }
928
+ }
929
+ if (openerIdx > 0) {
930
+ for (let s = openerIdx - 1; s >= 0; s--) {
931
+ let st = this.tokens[s];
932
+ if (st[0] === 'TERMINATOR' || st[0] === 'INDENT' || st[0] === 'OUTDENT') break;
933
+ if (st[0] === 'INTERFACE') return false;
934
+ if (st[0] === 'IDENTIFIER' && st[1] === 'type') {
935
+ let before = this.tokens[s - 1];
936
+ if (!before || before[0] === 'TERMINATOR' || before[0] === 'EXPORT') return false;
937
+ break;
938
+ }
939
+ }
940
+ }
941
+ break;
942
+ }
889
943
  }
890
944
  }
891
945
  return LINE_CONTINUER_RE.test(this.chunk) || UNFINISHED.has(this.prevTag());
@@ -1435,6 +1489,29 @@ export class Lexer {
1435
1489
  this.inTypeAnnotation = false;
1436
1490
  }
1437
1491
 
1492
+ // Enter type-alias body context: `type IDENTIFIER =` is followed by a
1493
+ // type expression where reserved-word type tokens like `void` should be
1494
+ // accepted. Without this, `type Foo = T | void` and `Promise<void>` in
1495
+ // the body trip the reserved-word check at identifier time. The `=`
1496
+ // reset above runs first; here we re-enable inTypeAnnotation when the
1497
+ // statement-start lookback sees `type IDENTIFIER`. (`interface` bodies
1498
+ // already work because they use `name:: T` per-property, which sets
1499
+ // inTypeAnnotation via `::`.)
1500
+ if (val === '=') {
1501
+ let n = this.tokens.length;
1502
+ let isTypeAlias =
1503
+ n >= 2 &&
1504
+ this.tokens[n - 1][0] === 'IDENTIFIER' &&
1505
+ this.tokens[n - 2][0] === 'IDENTIFIER' &&
1506
+ this.tokens[n - 2][1] === 'type' &&
1507
+ (n === 2 ||
1508
+ this.tokens[n - 3][0] === 'TERMINATOR' ||
1509
+ this.tokens[n - 3][0] === 'INDENT' ||
1510
+ this.tokens[n - 3][0] === 'OUTDENT' ||
1511
+ this.tokens[n - 3][0] === 'EXPORT');
1512
+ if (isTypeAlias) this.inTypeAnnotation = true;
1513
+ }
1514
+
1438
1515
  // Balanced pair tracking
1439
1516
  if (val === '(' || val === '{' || val === '[') {
1440
1517
  this.ends.push({tag: INVERSES[val], origin: [tag, val]});
@@ -1448,9 +1525,42 @@ export class Lexer {
1448
1525
 
1449
1526
  // Walk back to tag parameters for arrow functions
1450
1527
  tagParameters() {
1451
- if (this.prevTag() !== ')') return this.tagDoIife();
1528
+ let closeIdx = this.tokens.length - 1;
1529
+ if (this.tokens[closeIdx]?.[0] !== ')') {
1530
+ // Maybe a return-type annotation sits between `)` and the arrow:
1531
+ // `(x:: T):: R ->`. Scan backward over the type expression (balanced
1532
+ // brackets) looking for the trailing `TYPE_ANNOTATION` whose previous
1533
+ // token is `)`. If found, treat that `)` as the param-list close.
1534
+ let n = this.tokens.length;
1535
+ let depth = 0;
1536
+ let j = n - 1;
1537
+ let found = -1;
1538
+ while (j >= 0) {
1539
+ let tk = this.tokens[j];
1540
+ let tg = tk[0];
1541
+ if (tg === ')' || tg === ']' || tg === '}' || tg === 'CALL_END' ||
1542
+ tg === 'PARAM_END' || tg === 'INDEX_END' ||
1543
+ (tg === 'COMPARE' && tk[1] === '>')) {
1544
+ depth++;
1545
+ } else if (tg === '(' || tg === '[' || tg === '{' || tg === 'CALL_START' ||
1546
+ tg === 'PARAM_START' || tg === 'INDEX_START' ||
1547
+ (tg === 'COMPARE' && tk[1] === '<')) {
1548
+ depth--;
1549
+ } else if (depth === 0) {
1550
+ if (tg === 'TYPE_ANNOTATION') {
1551
+ if (j > 0 && this.tokens[j - 1][0] === ')') found = j - 1;
1552
+ break;
1553
+ }
1554
+ if (tg === 'TERMINATOR' || tg === 'INDENT' || tg === 'OUTDENT' ||
1555
+ tg === '=' || tg === '->' || tg === '=>') break;
1556
+ }
1557
+ j--;
1558
+ }
1559
+ if (found < 0) return this.tagDoIife();
1560
+ closeIdx = found;
1561
+ }
1452
1562
 
1453
- let i = this.tokens.length - 1;
1563
+ let i = closeIdx;
1454
1564
  let stack = [];
1455
1565
  this.tokens[i][0] = 'PARAM_END';
1456
1566
 
@@ -1465,7 +1575,7 @@ export class Lexer {
1465
1575
  tok[0] = 'PARAM_START';
1466
1576
  return this.tagDoIife(i - 1);
1467
1577
  } else {
1468
- this.tokens[this.tokens.length - 1][0] = 'CALL_END';
1578
+ this.tokens[closeIdx][0] = 'CALL_END';
1469
1579
  return;
1470
1580
  }
1471
1581
  }
@@ -1491,10 +1601,14 @@ export class Lexer {
1491
1601
  this.closeMergeAssignments();
1492
1602
  this.closeOpenCalls();
1493
1603
  this.closeOpenIndexes();
1604
+ // rewriteTypes must run BEFORE normalizeLines: otherwise a type-arrow
1605
+ // `=>` inside `(...) => T` gets treated as a single-liner function and
1606
+ // wrapped in spurious INDENT/OUTDENT, which then derails the type
1607
+ // collector (it slurps the whole assignment body as part of the type).
1608
+ this.rewriteTypes();
1494
1609
  this.normalizeLines();
1495
1610
  this.rewriteRender?.();
1496
1611
  this.rewriteSchema?.();
1497
- this.rewriteTypes();
1498
1612
  this.tagPostfixConditionals();
1499
1613
  this.rewriteTaggedTemplates();
1500
1614
  this.addImplicitBracesAndParens();