rip-lang 3.16.1 → 3.16.2
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 +2 -3
- package/bin/rip +39 -8
- package/bin/rip-schema +175 -0
- package/docs/RIP-APP.md +91 -2
- package/docs/RIP-DUCKDB.md +64 -1
- package/docs/RIP-INTRO.md +4 -4
- package/docs/RIP-LANG.md +32 -33
- package/docs/RIP-SCHEMA.md +1204 -364
- package/docs/dist/rip.js +3245 -611
- package/docs/dist/rip.min.js +1161 -289
- package/docs/dist/rip.min.js.br +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/index.html +2 -1
- package/docs/extensions/vscode/rip/rip-latest.vsix +0 -0
- package/docs/extensions/vscode/rip/vscode-rip-0.6.0.vsix +0 -0
- package/docs/index.html +1 -1
- package/docs/ui/hljs-rip.js +1 -1
- package/package.json +7 -4
- package/src/AGENTS.md +39 -8
- package/src/compiler.js +220 -36
- package/src/components.js +315 -14
- package/src/dts.js +18 -3
- package/src/grammar/README.md +29 -170
- package/src/grammar/grammar.rip +17 -12
- package/src/grammar/solar.rip +4 -17
- package/src/lexer.js +24 -17
- package/src/parser.js +229 -229
- package/src/schema/dts.js +328 -54
- package/src/schema/loader-server.js +2 -1
- package/src/schema/runtime-browser-stubs.js +20 -9
- package/src/schema/runtime-ddl.js +161 -44
- package/src/schema/runtime-migrate.js +681 -0
- package/src/schema/runtime-orm.js +698 -54
- package/src/schema/runtime-validate.js +808 -24
- package/src/schema/runtime.generated.js +2395 -135
- package/src/schema/schema.js +1049 -89
- package/src/typecheck.js +283 -55
- package/src/types.js +5 -1
- package/src/grammar/lunar.rip +0 -2412
package/src/grammar/README.md
CHANGED
|
@@ -1,27 +1,25 @@
|
|
|
1
|
-
# Solar
|
|
1
|
+
# Solar — Rip's Parser Generator
|
|
2
2
|
|
|
3
|
-
One grammar.
|
|
3
|
+
One grammar. One parser. Mathematically derived.
|
|
4
4
|
|
|
5
5
|
```
|
|
6
|
-
grammar.rip ──→ Solar ──→ parser.js
|
|
7
|
-
└→ Lunar ──→ parser-rd.js (predictive recursive descent, 110KB)
|
|
6
|
+
grammar.rip ──→ Solar ──→ parser.js (SLR(1) table-driven)
|
|
8
7
|
```
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
independent implementations derived from the same grammar specification.
|
|
13
|
-
|
|
14
|
-
**Test parity:** 1,162 / 1,182 tests passing (98.3%) — 20 files at 100%.
|
|
9
|
+
The parser accepts the token stream from the lexer and produces s-expression
|
|
10
|
+
ASTs — simple arrays like `["=", "x", 42]` — that the code emitter consumes.
|
|
15
11
|
|
|
16
12
|
---
|
|
17
13
|
|
|
18
14
|
## Files
|
|
19
15
|
|
|
20
|
-
| File |
|
|
21
|
-
|
|
22
|
-
| `grammar.rip` |
|
|
23
|
-
| `solar.rip` |
|
|
24
|
-
|
|
16
|
+
| File | Purpose |
|
|
17
|
+
|------|---------|
|
|
18
|
+
| `grammar.rip` | Grammar specification — defines all syntax rules |
|
|
19
|
+
| `solar.rip` | SLR(1) parser generator — produces the table-driven parser |
|
|
20
|
+
|
|
21
|
+
`src/parser.js` is the generated output. Never edit it by hand — regenerate
|
|
22
|
+
with `bun run parser` after any grammar change.
|
|
25
23
|
|
|
26
24
|
---
|
|
27
25
|
|
|
@@ -51,6 +49,13 @@ Operation: [
|
|
|
51
49
|
]
|
|
52
50
|
```
|
|
53
51
|
|
|
52
|
+
Action format:
|
|
53
|
+
|
|
54
|
+
- Numbers (`1`, `2`, `...3`) reference matched symbols by position
|
|
55
|
+
- `...N` spreads the array at position N
|
|
56
|
+
- String literals become s-expression nodes: `'["if", 2, 3]'`
|
|
57
|
+
- Default action (no action given) returns position 1
|
|
58
|
+
|
|
54
59
|
---
|
|
55
60
|
|
|
56
61
|
## Solar (`solar.rip`) — SLR(1) Table Parser Generator
|
|
@@ -66,169 +71,23 @@ Grammar → Process Rules → Build LR Automaton → Compute FIRST/FOLLOW
|
|
|
66
71
|
|
|
67
72
|
A table-driven parser where every parsing decision is a lookup:
|
|
68
73
|
`parseTable[state][symbol]` → shift, reduce, or accept. The parse table
|
|
69
|
-
encodes
|
|
74
|
+
encodes all states with transitions delta-compressed for minimal size.
|
|
70
75
|
|
|
71
76
|
### How to use
|
|
72
77
|
|
|
73
78
|
```bash
|
|
79
|
+
bun run parser # Regenerate src/parser.js
|
|
74
80
|
bun src/grammar/solar.rip grammar.rip # → parser.js (SLR table)
|
|
75
|
-
bun src/grammar/solar.rip -r grammar.rip # → parser-rd.js (PRD via Lunar)
|
|
76
81
|
bun src/grammar/solar.rip --info grammar.rip # Show grammar statistics
|
|
82
|
+
bun src/grammar/solar.rip --info --conflicts grammar.rip # Conflict details
|
|
77
83
|
bun src/grammar/solar.rip --sexpr grammar.rip # Show grammar as s-expression
|
|
84
|
+
bun src/grammar/solar.rip -o out.js grammar.rip # Custom output path
|
|
78
85
|
```
|
|
79
86
|
|
|
80
|
-
###
|
|
81
|
-
|
|
82
|
-
Solar imports Lunar and installs it with one line:
|
|
83
|
-
|
|
84
|
-
```coffee
|
|
85
|
-
import { install as installLunar } from './lunar.rip'
|
|
86
|
-
# ... (Generator class definition) ...
|
|
87
|
-
installLunar Generator
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
This adds `generateRD()` to the Generator prototype. When `-r` is passed,
|
|
91
|
-
Solar calls `generator.generateRD()` instead of `generator.generate()`.
|
|
92
|
-
|
|
93
|
-
---
|
|
94
|
-
|
|
95
|
-
## Lunar (`lunar.rip`) — Predictive Recursive Descent Generator
|
|
96
|
-
|
|
97
|
-
Lunar analyzes the same grammar that Solar processes and generates a
|
|
98
|
-
hand-rolled-looking recursive descent parser with Pratt expression parsing.
|
|
99
|
-
|
|
100
|
-
### Architecture
|
|
101
|
-
|
|
102
|
-
The generated parser has five layers:
|
|
103
|
-
|
|
104
|
-
1. **Token management** — `advance()`, `expect()`, `match()`, `loc()`, `withLoc()`
|
|
105
|
-
2. **Speculation** — `mark()`, `reset()`, `speculate()` for backtracking
|
|
106
|
-
3. **Pratt expression parser** — `parseExpression(minBP)` with binding powers
|
|
107
|
-
4. **Nonterminal functions** — one `parseX()` function per grammar nonterminal
|
|
108
|
-
5. **Parser shell** — same API as the table parser (`parser.parse()`, exports)
|
|
109
|
-
|
|
110
|
-
### How it works
|
|
111
|
-
|
|
112
|
-
Lunar derives everything from the grammar — no hardcoded token or nonterminal
|
|
113
|
-
names in the core generators:
|
|
114
|
-
|
|
115
|
-
**Expression analysis** (`_analyzeExpressionRules`) — walks the grammar to detect:
|
|
116
|
-
- Which nonterminal is the "expression" (contains Operation as an alternative)
|
|
117
|
-
- Which is the "operation" (has the most `NT OP NT` binary rules with precedence)
|
|
118
|
-
- Which is the "value" (pure choice nonterminal with the most atom alternatives)
|
|
119
|
-
- Which is "code" (FIRST set contains `->` or `=>`)
|
|
120
|
-
- Assignment operators (nonterminals where all rules are `LHS TOKEN RHS`)
|
|
121
|
-
- Prefix starters (keyword tokens that begin expression alternatives)
|
|
122
|
-
- Postfix chains (left-recursive property/index/call rules through Value chain)
|
|
123
|
-
- Atom types (terminals reachable through the Value nonterminal chain)
|
|
124
|
-
- Expression-handled tokens (for skipping redundant choice alternatives)
|
|
125
|
-
|
|
126
|
-
**Pratt parser generation** (`_generateRDExpression`) — builds the while loop:
|
|
127
|
-
- Prefix starters dispatch to keyword-led parsers (IF→parseIf, FOR→parseFor, etc.)
|
|
128
|
-
- Assignment operators checked at binding power 0
|
|
129
|
-
- Postfix operators from Operation rules (with INDENT/TERMINATOR variants)
|
|
130
|
-
- Postfix chains from property/index/call rules (resolved to FIRST sets)
|
|
131
|
-
- Infix binary operators with correct associativity and control-flow merging
|
|
132
|
-
- Ternary operators
|
|
133
|
-
- Postfix if/unless/while/until and comprehensions
|
|
134
|
-
- Statement tokens (RETURN, STATEMENT) enter the Pratt loop for postfix patterns
|
|
135
|
-
|
|
136
|
-
**Nonterminal classification** (`_classifyNonterminal`) — detects patterns:
|
|
137
|
-
|
|
138
|
-
| Pattern | Detection | Example |
|
|
139
|
-
|---------|-----------|---------|
|
|
140
|
-
| `root` | Grammar start symbol | Root |
|
|
141
|
-
| `body-list` | Left-recursive with TERMINATOR | Body, ComponentBody |
|
|
142
|
-
| `comma-list` | Left-recursive with `,` | ArgList, ParamList |
|
|
143
|
-
| `concat-list` | Left-recursive, no separator | Interpolations, Cases |
|
|
144
|
-
| `left-rec-loop` | Self-referential with terminal continuation | IfBlock |
|
|
145
|
-
| `expression` | Contains the operation nonterminal | Expression |
|
|
146
|
-
| `operation` | Has binary operator rules | Operation |
|
|
147
|
-
| `token` | Single rule, single terminal | Identifier, Property |
|
|
148
|
-
| `keyword` | All rules start with unique terminals | Return, Def, Enum |
|
|
149
|
-
| `choice` | All rules are single-nonterminal passthroughs | Value, Line, Statement |
|
|
150
|
-
| `sequence` | Everything else | Assign, Catch, Block |
|
|
151
|
-
|
|
152
|
-
**Shared prefix disambiguation** (`_generateRDSharedPrefix`) — handles rules
|
|
153
|
-
that share a common beginning:
|
|
154
|
-
- Optional chain detection with rule-length-based action dispatch
|
|
155
|
-
- Same-token grouping with deeper disambiguation
|
|
156
|
-
- Terminal and nonterminal suffix separation with FIRST set grouping
|
|
157
|
-
- Empty-rule-as-default when nonterminal suffixes have FIRST checks
|
|
158
|
-
|
|
159
|
-
**Speculation** — `mark()`/`reset()`/`speculate()` for grammar ambiguities:
|
|
160
|
-
- Range vs Array: `[1..10]` vs `[1, 2, 3]` — try Range first
|
|
161
|
-
- Slice vs Expression in INDEX_START: `arr[1..3]` vs `arr[0]`
|
|
162
|
-
- Range vs destructuring in For: `for [1..5]` vs `for [a, b] as iter`
|
|
163
|
-
- Terminal/nonterminal overlap: `...` as Splat vs expansion marker
|
|
164
|
-
- Left-rec-loop lookahead: `ELSE IF` vs `ELSE Block` in IfBlock
|
|
165
|
-
|
|
166
|
-
### Three specialized generators
|
|
167
|
-
|
|
168
|
-
Three nonterminals (out of 93) have patterns that require multi-token lookahead
|
|
169
|
-
and can't be handled by the generic generators:
|
|
170
|
-
|
|
171
|
-
- **For** (34 rules) — FORIN/FOROF/FORAS variants with optional WHEN/BY in both orders
|
|
172
|
-
- **Object** (5 rules) — comprehension vs regular object determined after parsing key:value
|
|
173
|
-
- **AssignObj** (6 rules) — key:value vs key=default vs shorthand vs rest after parsing key
|
|
174
|
-
|
|
175
|
-
90 of 93 nonterminals (96.8%) are generated purely from grammar analysis.
|
|
176
|
-
|
|
177
|
-
### Remaining 20 test failures (98.3% → 100%)
|
|
178
|
-
|
|
179
|
-
The remaining failures cluster into a few fixable categories:
|
|
180
|
-
|
|
181
|
-
| Category | Tests | Root Cause |
|
|
182
|
-
|----------|-------|------------|
|
|
183
|
-
| **Array elisions** | 5 | `[,1]`, `[1,,3]` — ArgElisionList/Elision not parsed |
|
|
184
|
-
| **Trailing comma** | 1 | `[1,2,]` — OptElisions at end of array |
|
|
185
|
-
| **Export/Import edge** | 3 | `export { x }` without FROM, `export x ~= ...`, `import x, * as m` — deeper shared-prefix disambiguation generates duplicate conditions |
|
|
186
|
-
| **Semicolons context** | 3 | `def foo(); 42` — Def/async without block body (CALL_END before Block) |
|
|
187
|
-
| **Class patterns** | 2 | Class expression without name, `@bar:` static in class body |
|
|
188
|
-
| **Postfix ternary** | 1 | `a = x if true else 0` — assignment context for postfix ternary |
|
|
189
|
-
| **Other edge cases** | 5 | `invalid extends`, `array destructuring skip`, type alias, typed runtime |
|
|
190
|
-
|
|
191
|
-
**Key fix needed for Export/Import:** The shared-prefix handler generates duplicate
|
|
192
|
-
`else if` branches with identical conditions when two rules share a common prefix
|
|
193
|
-
through nonterminals but diverge at a terminal after parsing (e.g., `} FROM` vs `}`).
|
|
194
|
-
The inner terminal group disambiguator needs to check terminal continuation instead
|
|
195
|
-
of generating separate branches.
|
|
196
|
-
|
|
197
|
-
**Key fix needed for elisions:** The Array parser routes `[` to Range speculation
|
|
198
|
-
then Array. Array uses ArgElisionList which calls Arg → Expression. But leading
|
|
199
|
-
commas `[,1]` and sparse `[1,,3]` need the Elision nonterminal which the generic
|
|
200
|
-
comma-list handler doesn't generate.
|
|
201
|
-
|
|
202
|
-
---
|
|
203
|
-
|
|
204
|
-
## Comparison
|
|
205
|
-
|
|
206
|
-
| Aspect | Solar (SLR) | Lunar (PRD) |
|
|
207
|
-
|--------|-------------|-------------|
|
|
208
|
-
| Strategy | Bottom-up table lookup | Top-down predictive |
|
|
209
|
-
| Output size | 215KB (encoded tables) | 110KB (readable code) |
|
|
210
|
-
| Startup | Decode table on load | Zero initialization |
|
|
211
|
-
| Debugging | "State 437" errors | Named function call stacks |
|
|
212
|
-
| Correctness | Mathematically derived | Grammar-derived + 3 specializations |
|
|
213
|
-
| Test parity | 1,235/1,235 (100%) | 1,162/1,182 (98.3%) |
|
|
214
|
-
| Speed | O(n) tight loop | O(n) function calls |
|
|
215
|
-
| Expressions | Shift/reduce with precedence | Pratt with binding powers |
|
|
216
|
-
|
|
217
|
-
Both produce identical s-expressions for 98.3% of the test suite. Having two
|
|
218
|
-
independent implementations from the same grammar provides cross-validation.
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
|
-
## The Innovation
|
|
223
|
-
|
|
224
|
-
Most parser generators commit to one strategy: Yacc produces LALR tables,
|
|
225
|
-
ANTLR produces LL recursive descent, PEG.js produces PEG parsers. Solar and
|
|
226
|
-
Lunar generate **both** from a single grammar specification.
|
|
227
|
-
|
|
228
|
-
The key insight: the FIRST/FOLLOW sets and operator precedence table that
|
|
229
|
-
Solar computes for SLR(1) contain all the information a Pratt-based recursive
|
|
230
|
-
descent parser needs. The data is the same — it's just read differently.
|
|
231
|
-
Solar reads it as table entries. Lunar reads it as dispatch conditions and
|
|
232
|
-
binding powers.
|
|
87
|
+
### Workflow for grammar changes
|
|
233
88
|
|
|
234
|
-
|
|
89
|
+
1. Edit `src/grammar/grammar.rip`
|
|
90
|
+
2. Run `bun run parser`
|
|
91
|
+
3. Verify the new parse shape with `echo 'code' | ./bin/rip -s`
|
|
92
|
+
4. Update codegen in `src/compiler.js` if the node shape changed
|
|
93
|
+
5. Run `bun run test`
|
package/src/grammar/grammar.rip
CHANGED
|
@@ -96,6 +96,7 @@ grammar =
|
|
|
96
96
|
o 'ComputedAssign'
|
|
97
97
|
o 'ReadonlyAssign'
|
|
98
98
|
o 'Effect'
|
|
99
|
+
o 'Gate'
|
|
99
100
|
o 'If'
|
|
100
101
|
o 'Try'
|
|
101
102
|
o 'While'
|
|
@@ -233,6 +234,13 @@ grammar =
|
|
|
233
234
|
o 'Assignable READONLY_ASSIGN INDENT Expression OUTDENT', '["readonly", 1, 4]'
|
|
234
235
|
]
|
|
235
236
|
|
|
237
|
+
# Render-ready gate (<~) — load-before-render binding; component bodies only.
|
|
238
|
+
# One form on purpose: the RHS must be a one-line literal stash path, so the
|
|
239
|
+
# compiler can hoist it into the component's static gate-set.
|
|
240
|
+
Gate: [
|
|
241
|
+
o 'Assignable GATE Expression', '["gate", 1, 3]'
|
|
242
|
+
]
|
|
243
|
+
|
|
236
244
|
# Reactive effect (~>) — side effects that run when dependencies change
|
|
237
245
|
Effect: [
|
|
238
246
|
o 'Assignable EFFECT Expression' , '["effect", 1, 3]'
|
|
@@ -592,14 +600,15 @@ grammar =
|
|
|
592
600
|
o 'INDENT Body OUTDENT', '["block", ...2]'
|
|
593
601
|
]
|
|
594
602
|
|
|
595
|
-
# Parenthetical mark-up:
|
|
596
|
-
#
|
|
597
|
-
#
|
|
598
|
-
#
|
|
599
|
-
#
|
|
603
|
+
# Parenthetical mark-up: tag a single parenthesized expression with
|
|
604
|
+
# `.parenthesized = true`. Consumers that respect explicit grouping
|
|
605
|
+
# consult it: the codegen ternary-hoist (so `(x = "a") if cond else "b"`
|
|
606
|
+
# stays a conditional assign) and comparison chaining (so
|
|
607
|
+
# `(a == b) == c` compares the boolean instead of chaining). The marker
|
|
608
|
+
# is harmless for any other consumer.
|
|
600
609
|
Parenthetical: [
|
|
601
|
-
o '( Body )' , '$2.length === 1 ? (Array.isArray($2[0]) &&
|
|
602
|
-
o '( INDENT Body OUTDENT )', '$3.length === 1 ? (Array.isArray($3[0]) &&
|
|
610
|
+
o '( Body )' , '$2.length === 1 ? (Array.isArray($2[0]) && ($2[0].parenthesized = true), $2[0]) : ["block", ...$2]'
|
|
611
|
+
o '( INDENT Body OUTDENT )', '$3.length === 1 ? (Array.isArray($3[0]) && ($3[0].parenthesized = true), $3[0]) : ["block", ...$3]'
|
|
603
612
|
]
|
|
604
613
|
|
|
605
614
|
OptComma: [
|
|
@@ -1004,9 +1013,6 @@ grammar =
|
|
|
1004
1013
|
o 'Expression || Expression' , '["||", 1, 3]'
|
|
1005
1014
|
o 'Expression ?? Expression' , '["??", 1, 3]'
|
|
1006
1015
|
|
|
1007
|
-
# Pipe
|
|
1008
|
-
o 'Expression PIPE Expression' , '["|>", 1, 3]'
|
|
1009
|
-
|
|
1010
1016
|
# Relational
|
|
1011
1017
|
o 'Expression RELATION Expression', '[2, 1, 3]' # in, of, instanceof
|
|
1012
1018
|
|
|
@@ -1046,8 +1052,7 @@ operators = """
|
|
|
1046
1052
|
left ^
|
|
1047
1053
|
left |
|
|
1048
1054
|
left &&
|
|
1049
|
-
left ||
|
|
1050
|
-
left PIPE
|
|
1055
|
+
left || ??
|
|
1051
1056
|
right TERNARY
|
|
1052
1057
|
nonassoc INDENT OUTDENT
|
|
1053
1058
|
right YIELD
|
package/src/grammar/solar.rip
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
import * as fs from 'fs'
|
|
11
11
|
import * as path from 'path'
|
|
12
12
|
import { fileURLToPath, pathToFileURL } from 'url'
|
|
13
|
-
import { install as installLunar } from './lunar.rip'
|
|
14
13
|
|
|
15
14
|
VERSION = '1.5.0'
|
|
16
15
|
|
|
@@ -90,11 +89,9 @@ class Generator
|
|
|
90
89
|
# Build parser
|
|
91
90
|
@timing '💥 Total time', =>
|
|
92
91
|
@timing 'processGrammar' , => @processGrammar grammar # Process grammar rules
|
|
93
|
-
|
|
94
|
-
@timing 'buildLRAutomaton' , => @buildLRAutomaton() # Build LR(0) automaton
|
|
92
|
+
@timing 'buildLRAutomaton' , => @buildLRAutomaton() # Build LR(0) automaton
|
|
95
93
|
@timing 'processLookaheads', => @processLookaheads() # Compute FIRST/FOLLOW and assign lookaheads
|
|
96
|
-
|
|
97
|
-
@timing 'buildParseTable' , => @buildParseTable() # Build parse table with default actions
|
|
94
|
+
@timing 'buildParseTable' , => @buildParseTable() # Build parse table with default actions
|
|
98
95
|
|
|
99
96
|
# ============================================================================
|
|
100
97
|
# Helper Functions
|
|
@@ -656,8 +653,6 @@ class Generator
|
|
|
656
653
|
# Decoder reconstructs [{key: val}, ...] at runtime
|
|
657
654
|
"(()=>{let d=[#{data.join ','}],t=[],p=0,n,o,k,a;while(p<d.length){n=d[p++];o={};k=0;a=[];while(n--)k+=d[p++],a.push(k);for(k of a)o[k]=d[p++];t.push(o)}return t})()"
|
|
658
655
|
|
|
659
|
-
# Recursive descent generation — installed by Lunar (see lunar.rip)
|
|
660
|
-
|
|
661
656
|
# ============================================================================
|
|
662
657
|
# Runtime Parser
|
|
663
658
|
# ============================================================================
|
|
@@ -772,8 +767,6 @@ class Generator
|
|
|
772
767
|
# Exports
|
|
773
768
|
# ==============================================================================
|
|
774
769
|
|
|
775
|
-
installLunar Generator
|
|
776
|
-
|
|
777
770
|
export { Generator }
|
|
778
771
|
|
|
779
772
|
# ==============================================================================
|
|
@@ -805,7 +798,6 @@ if isRunAsScript
|
|
|
805
798
|
-v, --version Show version
|
|
806
799
|
-i, --info Show grammar information
|
|
807
800
|
-s, --sexpr Show grammar as s-expression
|
|
808
|
-
-r, --rd Generate recursive descent parser (parser-rd.js)
|
|
809
801
|
-c, --conflicts Show conflict details (use with --info)
|
|
810
802
|
-o, --output <file> Output file (default: parser.js)
|
|
811
803
|
|
|
@@ -843,7 +835,7 @@ if isRunAsScript
|
|
|
843
835
|
console.log " Resolution: #{conflict.resolution} (by default)"
|
|
844
836
|
|
|
845
837
|
# Parse command line
|
|
846
|
-
options = {help: false, version: false, info: false, sexpr: false, conflicts: false,
|
|
838
|
+
options = {help: false, version: false, info: false, sexpr: false, conflicts: false, output: 'parser.js'}
|
|
847
839
|
grammarFile = null
|
|
848
840
|
i = 0
|
|
849
841
|
|
|
@@ -854,7 +846,6 @@ if isRunAsScript
|
|
|
854
846
|
when '-v', '--version' then options.version = true
|
|
855
847
|
when '-i', '--info' then options.info = true
|
|
856
848
|
when '-s', '--sexpr' then options.sexpr = true
|
|
857
|
-
when '-r', '--rd' then options.rd = true
|
|
858
849
|
when '-c', '--conflicts' then options.conflicts = true
|
|
859
850
|
when '-o', '--output' then options.output = process.argv[++i + 2]
|
|
860
851
|
else grammarFile = arg unless arg.startsWith('-')
|
|
@@ -915,11 +906,7 @@ if isRunAsScript
|
|
|
915
906
|
if options.info
|
|
916
907
|
showStats generator
|
|
917
908
|
else
|
|
918
|
-
|
|
919
|
-
options.output = 'parser-rd.js' if options.output is 'parser.js'
|
|
920
|
-
parserCode = generator.generateRD()
|
|
921
|
-
else
|
|
922
|
-
parserCode = generator.generate()
|
|
909
|
+
parserCode = generator.generate()
|
|
923
910
|
fs.writeFileSync options.output, parserCode
|
|
924
911
|
console.log "\nParser generated: #{options.output}"
|
|
925
912
|
|
package/src/lexer.js
CHANGED
|
@@ -129,7 +129,7 @@ let IMPLICIT_UNSPACED_CALL = new Set(['+', '-']);
|
|
|
129
129
|
// Tokens that end an implicit call
|
|
130
130
|
let IMPLICIT_END = new Set([
|
|
131
131
|
'POST_IF', 'POST_UNLESS', 'FOR', 'WHILE', 'UNTIL',
|
|
132
|
-
'WHEN', 'BY', 'LOOP', 'TERMINATOR', '||', '&&',
|
|
132
|
+
'WHEN', 'BY', 'LOOP', 'TERMINATOR', '||', '&&',
|
|
133
133
|
]);
|
|
134
134
|
|
|
135
135
|
// Tokens that trigger implicit comma insertion before arrows
|
|
@@ -251,7 +251,7 @@ let UNARY_MATH = new Set(['!', '~']);
|
|
|
251
251
|
// conflict with ?. (optional chaining), ?? (nullish), ?! (presence), ?.( and ?.[
|
|
252
252
|
let IDENTIFIER_RE = /^(?!\d)((?:(?!\s)[$\w\x7f-\uffff])+(?:!|[?](?![.?![(]))?)([^\n\S]*:(?![=:]))?/;
|
|
253
253
|
let NUMBER_RE = /^0b[01](?:_?[01])*n?|^0o[0-7](?:_?[0-7])*n?|^0x[\da-f](?:_?[\da-f])*n?|^\d+(?:_\d+)*n|^(?:\d+(?:_\d+)*)?\.?\d+(?:_\d+)*(?:e[+-]?\d+(?:_\d+)*)?/i;
|
|
254
|
-
let OPERATOR_RE = /^(
|
|
254
|
+
let OPERATOR_RE = /^(?:<=>|<~|::|\*>|[-=]>|~>|~=|:=|=!|===|!==|\?\!|\?\?|=~|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>*\/%])\2=?|\?\.?|\.{2,3})/;
|
|
255
255
|
let WHITESPACE_RE = /^[^\n\S]+/;
|
|
256
256
|
let NEWLINE_RE = /^(?:\n[^\n\S]*)+/;
|
|
257
257
|
let COMMENT_RE = /^(\s*)###([^#][\s\S]*?)(?:###([^\n\S]*)|###$)|^((?:\s*#(?!##[^#]).*)+)/;
|
|
@@ -748,18 +748,6 @@ export class Lexer {
|
|
|
748
748
|
}
|
|
749
749
|
if (/^\s+#[a-zA-Z_]/.test(this.chunk)) return 0; // let lineToken handle indentation first
|
|
750
750
|
}
|
|
751
|
-
// Schema field modifier: `#` adjacent (unspaced) to an identifier acts
|
|
752
|
-
// as the unique-marker inside schema bodies (e.g. `email!# email`).
|
|
753
|
-
// Return 0 so literalToken emits a standalone `#` token; rewriteSchema
|
|
754
|
-
// absorbs it. Outside schema bodies the `#` token is harmless because
|
|
755
|
-
// nothing else in the grammar accepts `IDENTIFIER #` without a space.
|
|
756
|
-
if (this.chunk[0] === '#') {
|
|
757
|
-
let prev = this.prev();
|
|
758
|
-
if (prev && !prev.spaced && !prev.newLine &&
|
|
759
|
-
(prev[0] === 'IDENTIFIER' || prev[0] === 'PROPERTY')) {
|
|
760
|
-
return 0;
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
751
|
let match = COMMENT_RE.exec(this.chunk);
|
|
764
752
|
if (!match) return 0;
|
|
765
753
|
return match[0].length;
|
|
@@ -1388,8 +1376,6 @@ export class Lexer {
|
|
|
1388
1376
|
this.inTypeAnnotation = false;
|
|
1389
1377
|
tag = 'TERMINATOR';
|
|
1390
1378
|
}
|
|
1391
|
-
// Pipe operator
|
|
1392
|
-
else if (val === '|>') tag = 'PIPE';
|
|
1393
1379
|
// Type operators
|
|
1394
1380
|
else if (val === '::' && /^[a-zA-Z_$]/.test(this.chunk[2] || '')) {
|
|
1395
1381
|
// Prototype access: String::trim → String.prototype.trim
|
|
@@ -1403,6 +1389,7 @@ export class Lexer {
|
|
|
1403
1389
|
else if (val === '~=') tag = 'COMPUTED_ASSIGN';
|
|
1404
1390
|
else if (val === ':=') tag = 'REACTIVE_ASSIGN';
|
|
1405
1391
|
else if (val === '<=>') tag = 'BIND';
|
|
1392
|
+
else if (val === '<~') { tag = 'GATE'; this.inTypeAnnotation = false; }
|
|
1406
1393
|
else if (val === '~>') { tag = 'EFFECT'; this.inTypeAnnotation = false; }
|
|
1407
1394
|
else if (val === '=!') { tag = 'READONLY_ASSIGN'; this.inTypeAnnotation = false; }
|
|
1408
1395
|
// Merge assignment: *>config = {a: 1} → Object.assign(config, {a: 1})
|
|
@@ -2063,12 +2050,32 @@ export class Lexer {
|
|
|
2063
2050
|
} else if (t && t !== 'TERMINATOR' && t !== 'OUTDENT' && t !== ',') j++;
|
|
2064
2051
|
logicalKeep = tokens[j]?.[0] === ',';
|
|
2065
2052
|
}
|
|
2053
|
+
// A postfix conditional on the FIRST property line of a multiline
|
|
2054
|
+
// implicit object must bind to the property's value, not close the
|
|
2055
|
+
// object — otherwise `a: 1 if c` followed by more properties splits
|
|
2056
|
+
// the object into separate arguments. Later property lines already
|
|
2057
|
+
// bind to the value (the sameLine flag is false by then), so only
|
|
2058
|
+
// first-line conditionals need the lookahead: keep the object open
|
|
2059
|
+
// when the property list continues after this line.
|
|
2060
|
+
let objectContinues = (j) => {
|
|
2061
|
+
for (let d = 0; j < tokens.length; j++) {
|
|
2062
|
+
let t = tokens[j][0];
|
|
2063
|
+
if (t === '(' || t === '[' || t === '{' || t === 'CALL_START' || t === 'INDEX_START' || t === 'INDENT') d++;
|
|
2064
|
+
else if (t === ')' || t === ']' || t === '}' || t === 'CALL_END' || t === 'INDEX_END' || t === 'OUTDENT') {
|
|
2065
|
+
if (d === 0) return false;
|
|
2066
|
+
d--;
|
|
2067
|
+
}
|
|
2068
|
+
else if (t === 'TERMINATOR' && d === 0) return this.looksObjectish(j + 1);
|
|
2069
|
+
}
|
|
2070
|
+
return false;
|
|
2071
|
+
};
|
|
2066
2072
|
if ((IMPLICIT_END.has(tag) && !logicalKeep) || (CALL_CLOSERS.has(tag) && newLine)) {
|
|
2067
2073
|
while (isImplicit(stackTop())) {
|
|
2068
2074
|
let [stackTag, , {sameLine, startsLine}] = stackTop();
|
|
2069
2075
|
if (inImplicitCall() && prevTag !== ',') {
|
|
2070
2076
|
endImplicitCall();
|
|
2071
|
-
} else if (inImplicitObject() && !isLogicalOp && sameLine && tag !== 'TERMINATOR' && prevTag !== ':'
|
|
2077
|
+
} else if (inImplicitObject() && !isLogicalOp && sameLine && tag !== 'TERMINATOR' && prevTag !== ':' &&
|
|
2078
|
+
!((tag === 'POST_IF' || tag === 'POST_UNLESS') && objectContinues(i + 1))) {
|
|
2072
2079
|
endImplicitObject();
|
|
2073
2080
|
} else if (inImplicitObject() && tag === 'TERMINATOR' && prevTag !== ',' && !(startsLine && this.looksObjectish(i + 1))) {
|
|
2074
2081
|
endImplicitObject();
|