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.
- package/README.md +6 -4
- package/bin/rip +167 -12
- package/docs/AGENTS.md +1 -1
- package/docs/RIP-APP.md +808 -0
- package/docs/RIP-DUCKDB.md +477 -0
- package/docs/RIP-INTRO.md +396 -0
- package/docs/RIP-LANG.md +59 -5
- package/docs/RIP-SCHEMA.md +191 -8
- package/docs/RIP-TYPES.md +74 -103
- package/docs/demo/README.md +4 -3
- package/docs/dist/rip.js +3627 -1470
- package/docs/dist/rip.min.js +671 -244
- package/docs/dist/rip.min.js.br +0 -0
- package/docs/example/index.json +7 -7
- package/docs/example/index.json.br +0 -0
- package/docs/extensions/duckdb/manifest.json +1 -1
- package/docs/extensions/duckdb/v1.5.2/linux_amd64/ripdb.duckdb_extension.gz +0 -0
- package/docs/extensions/duckdb/v1.5.2/osx_arm64/ripdb.duckdb_extension.gz +0 -0
- package/docs/extensions/vscode/print/index.html +2 -1
- package/docs/extensions/vscode/print/print-1.0.13.vsix +0 -0
- package/docs/extensions/vscode/print/print-1.0.14.vsix +0 -0
- package/docs/extensions/vscode/print/print-latest.vsix +0 -0
- package/docs/extensions/vscode/rip/rip-0.5.15.vsix +0 -0
- package/docs/extensions/vscode/rip/rip-latest.vsix +0 -0
- package/docs/ui/bundle.json +61 -0
- package/docs/ui/bundle.json.br +0 -0
- package/docs/ui/hljs-rip.js +0 -7
- package/docs/ui/index.css +66 -23
- package/docs/ui/index.html +6 -6
- package/package.json +9 -3
- package/rip-loader.js +64 -2
- package/src/AGENTS.md +63 -36
- package/src/browser.js +96 -14
- package/src/compiler.js +960 -143
- package/src/components.js +794 -88
- package/src/{types-emit.js → dts.js} +181 -71
- package/src/grammar/README.md +1 -1
- package/src/grammar/grammar.rip +111 -97
- package/src/lexer.js +132 -18
- package/src/parser.js +203 -205
- package/src/repl.js +74 -6
- package/src/schema/runtime-orm.js +168 -4
- package/src/schema/runtime-validate.js +146 -2
- package/src/schema/runtime.generated.js +314 -6
- package/src/schema/schema.js +5 -5
- package/src/sourcemaps.js +277 -1
- package/src/stdlib.js +253 -0
- package/src/typecheck.js +2023 -106
- package/src/types.js +127 -7
- package/docs/ui/accordion.rip +0 -103
- package/docs/ui/alert-dialog.rip +0 -53
- package/docs/ui/autocomplete.rip +0 -115
- package/docs/ui/avatar.rip +0 -37
- package/docs/ui/badge.rip +0 -15
- package/docs/ui/breadcrumb.rip +0 -47
- package/docs/ui/button-group.rip +0 -26
- package/docs/ui/button.rip +0 -23
- package/docs/ui/card.rip +0 -25
- package/docs/ui/carousel.rip +0 -110
- package/docs/ui/checkbox-group.rip +0 -61
- package/docs/ui/checkbox.rip +0 -33
- package/docs/ui/collapsible.rip +0 -50
- package/docs/ui/combobox.rip +0 -130
- package/docs/ui/context-menu.rip +0 -88
- package/docs/ui/date-picker.rip +0 -206
- package/docs/ui/dialog.rip +0 -60
- package/docs/ui/drawer.rip +0 -58
- package/docs/ui/editable-value.rip +0 -82
- package/docs/ui/field.rip +0 -53
- package/docs/ui/fieldset.rip +0 -22
- package/docs/ui/form.rip +0 -39
- package/docs/ui/grid.rip +0 -901
- package/docs/ui/input-group.rip +0 -28
- package/docs/ui/input.rip +0 -36
- package/docs/ui/label.rip +0 -16
- package/docs/ui/menu.rip +0 -134
- package/docs/ui/menubar.rip +0 -151
- package/docs/ui/meter.rip +0 -36
- package/docs/ui/multi-select.rip +0 -203
- package/docs/ui/native-select.rip +0 -33
- package/docs/ui/nav-menu.rip +0 -126
- package/docs/ui/number-field.rip +0 -162
- package/docs/ui/otp-field.rip +0 -89
- package/docs/ui/pagination.rip +0 -123
- package/docs/ui/popover.rip +0 -93
- package/docs/ui/preview-card.rip +0 -75
- package/docs/ui/progress.rip +0 -25
- package/docs/ui/radio-group.rip +0 -57
- package/docs/ui/resizable.rip +0 -123
- package/docs/ui/scroll-area.rip +0 -145
- package/docs/ui/select.rip +0 -151
- package/docs/ui/separator.rip +0 -17
- package/docs/ui/skeleton.rip +0 -22
- package/docs/ui/slider.rip +0 -165
- package/docs/ui/spinner.rip +0 -17
- package/docs/ui/table.rip +0 -27
- package/docs/ui/tabs.rip +0 -113
- package/docs/ui/textarea.rip +0 -48
- package/docs/ui/toast.rip +0 -87
- package/docs/ui/toggle-group.rip +0 -71
- package/docs/ui/toggle.rip +0 -24
- package/docs/ui/toolbar.rip +0 -38
- package/docs/ui/tooltip.rip +0 -85
- package/src/app.rip +0 -1571
- package/src/sourcemap-merge.js +0 -287
- /package/docs/demo/{components → routes}/_layout.rip +0 -0
- /package/docs/demo/{components → routes}/about.rip +0 -0
- /package/docs/demo/{components → routes}/card.rip +0 -0
- /package/docs/demo/{components → routes}/counter.rip +0 -0
- /package/docs/demo/{components → routes}/index.rip +0 -0
- /package/docs/demo/{components → routes}/todos.rip +0 -0
- /package/src/schema/{dts-emit.js → dts.js} +0 -0
package/src/grammar/grammar.rip
CHANGED
|
@@ -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 '
|
|
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
|
-
|
|
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'
|
|
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 '
|
|
240
|
-
o '
|
|
272
|
+
o 'Subjectable . Property' , '[".", 1, 3]'
|
|
273
|
+
o 'Subjectable ?. Property', '["?.", 1, 3]'
|
|
241
274
|
# Indexing
|
|
242
|
-
o '
|
|
243
|
-
o '
|
|
244
|
-
o '
|
|
245
|
-
o '
|
|
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 '
|
|
250
|
-
o '
|
|
251
|
-
o '
|
|
252
|
-
o '
|
|
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 '
|
|
287
|
+
o 'Subjectable INDEX_START RegexWithIndex INDEX_END' , '[$3[0], $1, ...$3.slice(1)]'
|
|
255
288
|
# ES6 optional indexing (?.[ )
|
|
256
|
-
o '
|
|
257
|
-
o '
|
|
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 '
|
|
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 '
|
|
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
|
|
481
|
-
o '
|
|
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
|
|
487
|
-
o '
|
|
497
|
+
o 'PARAM_START ParamList PARAM_END ArrowKind Line', '[4, 2, 5]'
|
|
498
|
+
o 'ArrowKind Line' , '[1, [], 2]'
|
|
488
499
|
]
|
|
489
500
|
|
|
490
|
-
|
|
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'
|
|
637
|
-
o '
|
|
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
|
|
674
|
-
o 'SWITCH Expression INDENT
|
|
675
|
-
o 'SWITCH INDENT
|
|
676
|
-
o 'SWITCH INDENT
|
|
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
|
-
|
|
693
|
+
Cases: [
|
|
680
694
|
o 'When' , '[1]'
|
|
681
|
-
o '
|
|
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'
|
|
790
|
-
o 'CLASS Block'
|
|
791
|
-
o 'CLASS EXTENDS Expression'
|
|
792
|
-
o 'CLASS EXTENDS Expression Block'
|
|
793
|
-
o 'CLASS
|
|
794
|
-
o 'CLASS
|
|
795
|
-
o 'CLASS
|
|
796
|
-
o 'CLASS
|
|
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'
|
|
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:
|
|
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
|
-
// ! —
|
|
27
|
-
// ? —
|
|
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 ! (
|
|
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
|
-
// ! →
|
|
508
|
-
//
|
|
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
|
|
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
|
-
// ---
|
|
660
|
+
// --- Bang: trailing ! → context-resolved (await at call site / void at def) ---
|
|
644
661
|
if (id.length > 1 && id.endsWith('!')) {
|
|
645
|
-
data.
|
|
662
|
+
data.bang = true;
|
|
646
663
|
id = id.slice(0, -1);
|
|
647
664
|
}
|
|
648
665
|
|
|
649
|
-
// ---
|
|
650
|
-
//
|
|
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.
|
|
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] === '
|
|
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
|
-
|
|
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 =
|
|
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[
|
|
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();
|