@manifesto-ai/compiler 1.6.2 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -18
- package/dist/chunk-BH25NHMN.js +74 -0
- package/dist/chunk-BH25NHMN.js.map +1 -0
- package/dist/chunk-D62NIFP4.js +33 -0
- package/dist/chunk-D62NIFP4.js.map +1 -0
- package/dist/chunk-MKLDAZ2Z.js +6920 -0
- package/dist/chunk-MKLDAZ2Z.js.map +1 -0
- package/dist/esbuild.d.ts +8 -0
- package/dist/esbuild.js +14 -0
- package/dist/esbuild.js.map +1 -0
- package/dist/index.d.ts +2810 -11
- package/dist/index.js +2425 -43
- package/dist/index.js.map +1 -1
- package/dist/node-loader.d.ts +18 -0
- package/dist/node-loader.js +47 -0
- package/dist/node-loader.js.map +1 -0
- package/dist/rollup.d.ts +8 -0
- package/dist/rollup.js +14 -0
- package/dist/rollup.js.map +1 -0
- package/dist/rspack.d.ts +7 -0
- package/dist/rspack.js +14 -0
- package/dist/rspack.js.map +1 -0
- package/dist/unplugin-6wnvFiEo.d.ts +17 -0
- package/dist/vite.d.ts +8 -17
- package/dist/vite.js +13 -33
- package/dist/vite.js.map +1 -1
- package/dist/webpack.d.ts +8 -0
- package/dist/webpack.js +14 -0
- package/dist/webpack.js.map +1 -0
- package/package.json +40 -22
- package/dist/analyzer/index.d.ts +0 -6
- package/dist/analyzer/index.d.ts.map +0 -1
- package/dist/analyzer/index.js +0 -6
- package/dist/analyzer/index.js.map +0 -1
- package/dist/analyzer/scope.d.ts +0 -77
- package/dist/analyzer/scope.d.ts.map +0 -1
- package/dist/analyzer/scope.js +0 -296
- package/dist/analyzer/scope.js.map +0 -1
- package/dist/analyzer/validator.d.ts +0 -60
- package/dist/analyzer/validator.d.ts.map +0 -1
- package/dist/analyzer/validator.js +0 -439
- package/dist/analyzer/validator.js.map +0 -1
- package/dist/api/compile-mel-patch-collector.d.ts +0 -32
- package/dist/api/compile-mel-patch-collector.d.ts.map +0 -1
- package/dist/api/compile-mel-patch-collector.js +0 -425
- package/dist/api/compile-mel-patch-collector.js.map +0 -1
- package/dist/api/compile-mel-patch-expr.d.ts +0 -9
- package/dist/api/compile-mel-patch-expr.d.ts.map +0 -1
- package/dist/api/compile-mel-patch-expr.js +0 -179
- package/dist/api/compile-mel-patch-expr.js.map +0 -1
- package/dist/api/compile-mel-patch-location.d.ts +0 -10
- package/dist/api/compile-mel-patch-location.d.ts.map +0 -1
- package/dist/api/compile-mel-patch-location.js +0 -48
- package/dist/api/compile-mel-patch-location.js.map +0 -1
- package/dist/api/compile-mel-patch.d.ts +0 -6
- package/dist/api/compile-mel-patch.d.ts.map +0 -1
- package/dist/api/compile-mel-patch.js +0 -244
- package/dist/api/compile-mel-patch.js.map +0 -1
- package/dist/api/compile-mel.d.ts +0 -126
- package/dist/api/compile-mel.d.ts.map +0 -1
- package/dist/api/compile-mel.js +0 -114
- package/dist/api/compile-mel.js.map +0 -1
- package/dist/api/index.d.ts +0 -10
- package/dist/api/index.d.ts.map +0 -1
- package/dist/api/index.js +0 -9
- package/dist/api/index.js.map +0 -1
- package/dist/diagnostics/codes.d.ts +0 -25
- package/dist/diagnostics/codes.d.ts.map +0 -1
- package/dist/diagnostics/codes.js +0 -154
- package/dist/diagnostics/codes.js.map +0 -1
- package/dist/diagnostics/index.d.ts +0 -6
- package/dist/diagnostics/index.d.ts.map +0 -1
- package/dist/diagnostics/index.js +0 -6
- package/dist/diagnostics/index.js.map +0 -1
- package/dist/diagnostics/types.d.ts +0 -67
- package/dist/diagnostics/types.d.ts.map +0 -1
- package/dist/diagnostics/types.js +0 -58
- package/dist/diagnostics/types.js.map +0 -1
- package/dist/evaluation/context.d.ts +0 -91
- package/dist/evaluation/context.d.ts.map +0 -1
- package/dist/evaluation/context.js +0 -53
- package/dist/evaluation/context.js.map +0 -1
- package/dist/evaluation/evaluate-expr.d.ts +0 -24
- package/dist/evaluation/evaluate-expr.d.ts.map +0 -1
- package/dist/evaluation/evaluate-expr.js +0 -577
- package/dist/evaluation/evaluate-expr.js.map +0 -1
- package/dist/evaluation/evaluate-patch.d.ts +0 -123
- package/dist/evaluation/evaluate-patch.d.ts.map +0 -1
- package/dist/evaluation/evaluate-patch.js +0 -202
- package/dist/evaluation/evaluate-patch.js.map +0 -1
- package/dist/evaluation/evaluate-runtime-patch.d.ts +0 -86
- package/dist/evaluation/evaluate-runtime-patch.d.ts.map +0 -1
- package/dist/evaluation/evaluate-runtime-patch.js +0 -185
- package/dist/evaluation/evaluate-runtime-patch.js.map +0 -1
- package/dist/evaluation/index.d.ts +0 -15
- package/dist/evaluation/index.d.ts.map +0 -1
- package/dist/evaluation/index.js +0 -13
- package/dist/evaluation/index.js.map +0 -1
- package/dist/generator/index.d.ts +0 -7
- package/dist/generator/index.d.ts.map +0 -1
- package/dist/generator/index.js +0 -7
- package/dist/generator/index.js.map +0 -1
- package/dist/generator/ir.d.ts +0 -348
- package/dist/generator/ir.d.ts.map +0 -1
- package/dist/generator/ir.js +0 -715
- package/dist/generator/ir.js.map +0 -1
- package/dist/generator/lowering.d.ts +0 -11
- package/dist/generator/lowering.d.ts.map +0 -1
- package/dist/generator/lowering.js +0 -369
- package/dist/generator/lowering.js.map +0 -1
- package/dist/generator/normalizer.d.ts +0 -16
- package/dist/generator/normalizer.d.ts.map +0 -1
- package/dist/generator/normalizer.js +0 -181
- package/dist/generator/normalizer.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/lexer/index.d.ts +0 -7
- package/dist/lexer/index.d.ts.map +0 -1
- package/dist/lexer/index.js +0 -7
- package/dist/lexer/index.js.map +0 -1
- package/dist/lexer/lexer.d.ts +0 -59
- package/dist/lexer/lexer.d.ts.map +0 -1
- package/dist/lexer/lexer.js +0 -433
- package/dist/lexer/lexer.js.map +0 -1
- package/dist/lexer/source-location.d.ts +0 -41
- package/dist/lexer/source-location.d.ts.map +0 -1
- package/dist/lexer/source-location.js +0 -33
- package/dist/lexer/source-location.js.map +0 -1
- package/dist/lexer/tokens.d.ts +0 -47
- package/dist/lexer/tokens.d.ts.map +0 -1
- package/dist/lexer/tokens.js +0 -73
- package/dist/lexer/tokens.js.map +0 -1
- package/dist/loader.d.ts +0 -23
- package/dist/loader.d.ts.map +0 -1
- package/dist/loader.js +0 -62
- package/dist/loader.js.map +0 -1
- package/dist/lowering/context.d.ts +0 -96
- package/dist/lowering/context.d.ts.map +0 -1
- package/dist/lowering/context.js +0 -42
- package/dist/lowering/context.js.map +0 -1
- package/dist/lowering/errors.d.ts +0 -84
- package/dist/lowering/errors.d.ts.map +0 -1
- package/dist/lowering/errors.js +0 -81
- package/dist/lowering/errors.js.map +0 -1
- package/dist/lowering/index.d.ts +0 -20
- package/dist/lowering/index.d.ts.map +0 -1
- package/dist/lowering/index.js +0 -13
- package/dist/lowering/index.js.map +0 -1
- package/dist/lowering/lower-expr.d.ts +0 -76
- package/dist/lowering/lower-expr.d.ts.map +0 -1
- package/dist/lowering/lower-expr.js +0 -366
- package/dist/lowering/lower-expr.js.map +0 -1
- package/dist/lowering/lower-patch.d.ts +0 -231
- package/dist/lowering/lower-patch.d.ts.map +0 -1
- package/dist/lowering/lower-patch.js +0 -146
- package/dist/lowering/lower-patch.js.map +0 -1
- package/dist/lowering/lower-runtime-patch.d.ts +0 -100
- package/dist/lowering/lower-runtime-patch.d.ts.map +0 -1
- package/dist/lowering/lower-runtime-patch.js +0 -49
- package/dist/lowering/lower-runtime-patch.js.map +0 -1
- package/dist/mel-module.d.ts +0 -13
- package/dist/mel-module.d.ts.map +0 -1
- package/dist/mel-module.js +0 -33
- package/dist/mel-module.js.map +0 -1
- package/dist/parser/ast.d.ts +0 -344
- package/dist/parser/ast.d.ts.map +0 -1
- package/dist/parser/ast.js +0 -24
- package/dist/parser/ast.js.map +0 -1
- package/dist/parser/index.d.ts +0 -7
- package/dist/parser/index.d.ts.map +0 -1
- package/dist/parser/index.js +0 -7
- package/dist/parser/index.js.map +0 -1
- package/dist/parser/parser.d.ts +0 -92
- package/dist/parser/parser.d.ts.map +0 -1
- package/dist/parser/parser.js +0 -892
- package/dist/parser/parser.js.map +0 -1
- package/dist/parser/precedence.d.ts +0 -44
- package/dist/parser/precedence.d.ts.map +0 -1
- package/dist/parser/precedence.js +0 -69
- package/dist/parser/precedence.js.map +0 -1
- package/dist/renderer/expr-node.d.ts +0 -172
- package/dist/renderer/expr-node.d.ts.map +0 -1
- package/dist/renderer/expr-node.js +0 -218
- package/dist/renderer/expr-node.js.map +0 -1
- package/dist/renderer/fragment.d.ts +0 -84
- package/dist/renderer/fragment.d.ts.map +0 -1
- package/dist/renderer/fragment.js +0 -172
- package/dist/renderer/fragment.js.map +0 -1
- package/dist/renderer/index.d.ts +0 -23
- package/dist/renderer/index.d.ts.map +0 -1
- package/dist/renderer/index.js +0 -27
- package/dist/renderer/index.js.map +0 -1
- package/dist/renderer/patch-op.d.ts +0 -82
- package/dist/renderer/patch-op.d.ts.map +0 -1
- package/dist/renderer/patch-op.js +0 -204
- package/dist/renderer/patch-op.js.map +0 -1
- package/dist/renderer/type-expr.d.ts +0 -61
- package/dist/renderer/type-expr.d.ts.map +0 -1
- package/dist/renderer/type-expr.js +0 -131
- package/dist/renderer/type-expr.js.map +0 -1
- package/dist/vite.d.ts.map +0 -1
- package/loader.cjs +0 -22
package/dist/parser/parser.js
DELETED
|
@@ -1,892 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MEL Parser
|
|
3
|
-
* Recursive descent parser with Pratt parsing for expressions
|
|
4
|
-
* Based on MEL SPEC v0.3.3 Section 4
|
|
5
|
-
*/
|
|
6
|
-
import { mergeLocations } from "../lexer/source-location.js";
|
|
7
|
-
import { getBinaryPrecedence, tokenToBinaryOp, isRightAssociative, } from "./precedence.js";
|
|
8
|
-
/**
|
|
9
|
-
* Parser for MEL source code
|
|
10
|
-
*/
|
|
11
|
-
export class Parser {
|
|
12
|
-
tokens;
|
|
13
|
-
current = 0;
|
|
14
|
-
diagnostics = [];
|
|
15
|
-
constructor(tokens) {
|
|
16
|
-
this.tokens = tokens;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Parse tokens into an AST
|
|
20
|
-
*/
|
|
21
|
-
parse() {
|
|
22
|
-
try {
|
|
23
|
-
const program = this.parseProgram();
|
|
24
|
-
return { program, diagnostics: this.diagnostics };
|
|
25
|
-
}
|
|
26
|
-
catch (e) {
|
|
27
|
-
// Unrecoverable error
|
|
28
|
-
return { program: null, diagnostics: this.diagnostics };
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
// ============ Program Structure ============
|
|
32
|
-
parseProgram() {
|
|
33
|
-
const start = this.peek().location;
|
|
34
|
-
const imports = [];
|
|
35
|
-
// Parse imports (if any)
|
|
36
|
-
while (this.check("IMPORT")) {
|
|
37
|
-
imports.push(this.parseImport());
|
|
38
|
-
}
|
|
39
|
-
// Parse domain
|
|
40
|
-
const domain = this.parseDomain();
|
|
41
|
-
return {
|
|
42
|
-
kind: "program",
|
|
43
|
-
imports,
|
|
44
|
-
domain,
|
|
45
|
-
location: mergeLocations(start, domain.location),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
parseImport() {
|
|
49
|
-
const start = this.consume("IMPORT", "Expected 'import'").location;
|
|
50
|
-
this.consume("LBRACE", "Expected '{' after 'import'");
|
|
51
|
-
const names = [];
|
|
52
|
-
do {
|
|
53
|
-
names.push(this.consume("IDENTIFIER", "Expected identifier").lexeme);
|
|
54
|
-
} while (this.match("COMMA"));
|
|
55
|
-
this.consume("RBRACE", "Expected '}' after import names");
|
|
56
|
-
this.consume("FROM", "Expected 'from' after import names");
|
|
57
|
-
const fromToken = this.consume("STRING", "Expected string after 'from'");
|
|
58
|
-
return {
|
|
59
|
-
kind: "import",
|
|
60
|
-
names,
|
|
61
|
-
from: fromToken.value,
|
|
62
|
-
location: mergeLocations(start, fromToken.location),
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
parseDomain() {
|
|
66
|
-
const start = this.consume("DOMAIN", "Expected 'domain'").location;
|
|
67
|
-
const name = this.consume("IDENTIFIER", "Expected domain name").lexeme;
|
|
68
|
-
this.consume("LBRACE", "Expected '{' after domain name");
|
|
69
|
-
// v0.3.3: Parse type declarations first
|
|
70
|
-
const types = [];
|
|
71
|
-
while (this.check("TYPE") && !this.isAtEnd()) {
|
|
72
|
-
types.push(this.parseTypeDecl());
|
|
73
|
-
}
|
|
74
|
-
const members = [];
|
|
75
|
-
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
76
|
-
// v0.3.3: type declarations can appear anywhere in domain
|
|
77
|
-
if (this.check("TYPE")) {
|
|
78
|
-
types.push(this.parseTypeDecl());
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
const member = this.parseDomainMember();
|
|
82
|
-
if (member)
|
|
83
|
-
members.push(member);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
const end = this.consume("RBRACE", "Expected '}' to close domain").location;
|
|
87
|
-
return {
|
|
88
|
-
kind: "domain",
|
|
89
|
-
name,
|
|
90
|
-
types,
|
|
91
|
-
members,
|
|
92
|
-
location: mergeLocations(start, end),
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* v0.3.3: Parse type declaration
|
|
97
|
-
* Syntax: type Name = TypeExpr
|
|
98
|
-
*/
|
|
99
|
-
parseTypeDecl() {
|
|
100
|
-
const start = this.consume("TYPE", "Expected 'type'").location;
|
|
101
|
-
const name = this.consume("IDENTIFIER", "Expected type name").lexeme;
|
|
102
|
-
this.consume("EQ", "Expected '=' after type name");
|
|
103
|
-
const typeExpr = this.parseTypeExpr();
|
|
104
|
-
return {
|
|
105
|
-
kind: "typeDecl",
|
|
106
|
-
name,
|
|
107
|
-
typeExpr,
|
|
108
|
-
location: mergeLocations(start, typeExpr.location),
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
parseDomainMember() {
|
|
112
|
-
if (this.check("STATE"))
|
|
113
|
-
return this.parseState();
|
|
114
|
-
if (this.check("COMPUTED"))
|
|
115
|
-
return this.parseComputed();
|
|
116
|
-
if (this.check("ACTION"))
|
|
117
|
-
return this.parseAction();
|
|
118
|
-
this.error(`Unexpected token '${this.peek().lexeme}'. Expected 'state', 'computed', or 'action'.`);
|
|
119
|
-
this.advance(); // Skip the bad token
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
// ============ State ============
|
|
123
|
-
parseState() {
|
|
124
|
-
const start = this.consume("STATE", "Expected 'state'").location;
|
|
125
|
-
this.consume("LBRACE", "Expected '{' after 'state'");
|
|
126
|
-
const fields = [];
|
|
127
|
-
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
128
|
-
fields.push(this.parseStateField());
|
|
129
|
-
}
|
|
130
|
-
const end = this.consume("RBRACE", "Expected '}' to close state block").location;
|
|
131
|
-
return {
|
|
132
|
-
kind: "state",
|
|
133
|
-
fields,
|
|
134
|
-
location: mergeLocations(start, end),
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
parseStateField() {
|
|
138
|
-
const nameToken = this.consume("IDENTIFIER", "Expected field name");
|
|
139
|
-
this.consume("COLON", "Expected ':' after field name");
|
|
140
|
-
const typeExpr = this.parseTypeExpr();
|
|
141
|
-
let initializer;
|
|
142
|
-
if (this.match("EQ")) {
|
|
143
|
-
initializer = this.parseExpression();
|
|
144
|
-
}
|
|
145
|
-
return {
|
|
146
|
-
kind: "stateField",
|
|
147
|
-
name: nameToken.lexeme,
|
|
148
|
-
typeExpr,
|
|
149
|
-
initializer,
|
|
150
|
-
location: mergeLocations(nameToken.location, initializer?.location ?? typeExpr.location),
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
// ============ Computed ============
|
|
154
|
-
parseComputed() {
|
|
155
|
-
const start = this.consume("COMPUTED", "Expected 'computed'").location;
|
|
156
|
-
const name = this.consume("IDENTIFIER", "Expected computed name").lexeme;
|
|
157
|
-
this.consume("EQ", "Expected '=' after computed name");
|
|
158
|
-
const expression = this.parseExpression();
|
|
159
|
-
return {
|
|
160
|
-
kind: "computed",
|
|
161
|
-
name,
|
|
162
|
-
expression,
|
|
163
|
-
location: mergeLocations(start, expression.location),
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
// ============ Action ============
|
|
167
|
-
parseAction() {
|
|
168
|
-
const start = this.consume("ACTION", "Expected 'action'").location;
|
|
169
|
-
const name = this.consume("IDENTIFIER", "Expected action name").lexeme;
|
|
170
|
-
this.consume("LPAREN", "Expected '(' after action name");
|
|
171
|
-
const params = [];
|
|
172
|
-
if (!this.check("RPAREN")) {
|
|
173
|
-
do {
|
|
174
|
-
params.push(this.parseParam());
|
|
175
|
-
} while (this.match("COMMA"));
|
|
176
|
-
}
|
|
177
|
-
this.consume("RPAREN", "Expected ')' after parameters");
|
|
178
|
-
// v0.3.2: Parse optional 'available when <Expr>'
|
|
179
|
-
let available;
|
|
180
|
-
if (this.match("AVAILABLE")) {
|
|
181
|
-
this.consume("WHEN", "Expected 'when' after 'available'");
|
|
182
|
-
available = this.parseExpression();
|
|
183
|
-
}
|
|
184
|
-
this.consume("LBRACE", "Expected '{' to start action body");
|
|
185
|
-
const body = [];
|
|
186
|
-
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
187
|
-
const stmt = this.parseGuardedStmt();
|
|
188
|
-
if (stmt)
|
|
189
|
-
body.push(stmt);
|
|
190
|
-
}
|
|
191
|
-
const end = this.consume("RBRACE", "Expected '}' to close action").location;
|
|
192
|
-
return {
|
|
193
|
-
kind: "action",
|
|
194
|
-
name,
|
|
195
|
-
params,
|
|
196
|
-
available,
|
|
197
|
-
body,
|
|
198
|
-
location: mergeLocations(start, end),
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
parseParam() {
|
|
202
|
-
const nameToken = this.consume("IDENTIFIER", "Expected parameter name");
|
|
203
|
-
this.consume("COLON", "Expected ':' after parameter name");
|
|
204
|
-
const typeExpr = this.parseTypeExpr();
|
|
205
|
-
return {
|
|
206
|
-
kind: "param",
|
|
207
|
-
name: nameToken.lexeme,
|
|
208
|
-
typeExpr,
|
|
209
|
-
location: mergeLocations(nameToken.location, typeExpr.location),
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
// ============ Statements ============
|
|
213
|
-
parseGuardedStmt() {
|
|
214
|
-
if (this.check("WHEN"))
|
|
215
|
-
return this.parseWhenStmt();
|
|
216
|
-
if (this.check("ONCE"))
|
|
217
|
-
return this.parseOnceStmt();
|
|
218
|
-
if (this.isOnceIntentContext())
|
|
219
|
-
return this.parseOnceIntentStmt();
|
|
220
|
-
this.error(`Unexpected token '${this.peek().lexeme}'. Expected 'when', 'once', or 'onceIntent'.`);
|
|
221
|
-
this.advance();
|
|
222
|
-
return null;
|
|
223
|
-
}
|
|
224
|
-
parseWhenStmt() {
|
|
225
|
-
const start = this.consume("WHEN", "Expected 'when'").location;
|
|
226
|
-
const condition = this.parseExpression();
|
|
227
|
-
this.consume("LBRACE", "Expected '{' after when condition");
|
|
228
|
-
const body = [];
|
|
229
|
-
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
230
|
-
const stmt = this.parseInnerStmt();
|
|
231
|
-
if (stmt)
|
|
232
|
-
body.push(stmt);
|
|
233
|
-
}
|
|
234
|
-
const end = this.consume("RBRACE", "Expected '}' to close when block").location;
|
|
235
|
-
return {
|
|
236
|
-
kind: "when",
|
|
237
|
-
condition,
|
|
238
|
-
body,
|
|
239
|
-
location: mergeLocations(start, end),
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
parseOnceStmt() {
|
|
243
|
-
const start = this.consume("ONCE", "Expected 'once'").location;
|
|
244
|
-
this.consume("LPAREN", "Expected '(' after 'once'");
|
|
245
|
-
const marker = this.parsePath();
|
|
246
|
-
this.consume("RPAREN", "Expected ')' after marker");
|
|
247
|
-
let condition;
|
|
248
|
-
if (this.match("WHEN")) {
|
|
249
|
-
condition = this.parseExpression();
|
|
250
|
-
}
|
|
251
|
-
this.consume("LBRACE", "Expected '{' to start once block");
|
|
252
|
-
const body = [];
|
|
253
|
-
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
254
|
-
const stmt = this.parseInnerStmt();
|
|
255
|
-
if (stmt)
|
|
256
|
-
body.push(stmt);
|
|
257
|
-
}
|
|
258
|
-
const end = this.consume("RBRACE", "Expected '}' to close once block").location;
|
|
259
|
-
return {
|
|
260
|
-
kind: "once",
|
|
261
|
-
marker,
|
|
262
|
-
condition,
|
|
263
|
-
body,
|
|
264
|
-
location: mergeLocations(start, end),
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
parseOnceIntentStmt() {
|
|
268
|
-
const startToken = this.consume("IDENTIFIER", "Expected 'onceIntent'");
|
|
269
|
-
let condition;
|
|
270
|
-
if (this.match("WHEN")) {
|
|
271
|
-
condition = this.parseExpression();
|
|
272
|
-
}
|
|
273
|
-
this.consume("LBRACE", "Expected '{' to start onceIntent block");
|
|
274
|
-
const body = [];
|
|
275
|
-
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
276
|
-
const stmt = this.parseInnerStmt();
|
|
277
|
-
if (stmt)
|
|
278
|
-
body.push(stmt);
|
|
279
|
-
}
|
|
280
|
-
const end = this.consume("RBRACE", "Expected '}' to close onceIntent block").location;
|
|
281
|
-
return {
|
|
282
|
-
kind: "onceIntent",
|
|
283
|
-
condition,
|
|
284
|
-
body,
|
|
285
|
-
location: mergeLocations(startToken.location, end),
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
parseInnerStmt() {
|
|
289
|
-
if (this.check("PATCH"))
|
|
290
|
-
return this.parsePatchStmt();
|
|
291
|
-
if (this.check("EFFECT"))
|
|
292
|
-
return this.parseEffectStmt();
|
|
293
|
-
if (this.check("WHEN"))
|
|
294
|
-
return this.parseWhenStmt();
|
|
295
|
-
if (this.check("ONCE"))
|
|
296
|
-
return this.parseOnceStmt();
|
|
297
|
-
if (this.isOnceIntentContext())
|
|
298
|
-
return this.parseOnceIntentStmt();
|
|
299
|
-
if (this.check("FAIL"))
|
|
300
|
-
return this.parseFailStmt(); // v0.3.2
|
|
301
|
-
if (this.check("STOP"))
|
|
302
|
-
return this.parseStopStmt(); // v0.3.2
|
|
303
|
-
this.error(`Unexpected token '${this.peek().lexeme}'. Expected 'patch', 'effect', 'when', 'once', 'onceIntent', 'fail', or 'stop'.`);
|
|
304
|
-
this.advance();
|
|
305
|
-
return null;
|
|
306
|
-
}
|
|
307
|
-
parsePatchStmt() {
|
|
308
|
-
const start = this.consume("PATCH", "Expected 'patch'").location;
|
|
309
|
-
const path = this.parsePath();
|
|
310
|
-
let op;
|
|
311
|
-
let value;
|
|
312
|
-
let end;
|
|
313
|
-
if (this.match("UNSET")) {
|
|
314
|
-
op = "unset";
|
|
315
|
-
end = this.previous().location;
|
|
316
|
-
}
|
|
317
|
-
else if (this.match("MERGE")) {
|
|
318
|
-
op = "merge";
|
|
319
|
-
value = this.parseExpression();
|
|
320
|
-
end = value.location;
|
|
321
|
-
}
|
|
322
|
-
else {
|
|
323
|
-
this.consume("EQ", "Expected '=', 'unset', or 'merge' after path");
|
|
324
|
-
op = "set";
|
|
325
|
-
value = this.parseExpression();
|
|
326
|
-
end = value.location;
|
|
327
|
-
}
|
|
328
|
-
return {
|
|
329
|
-
kind: "patch",
|
|
330
|
-
path,
|
|
331
|
-
op,
|
|
332
|
-
value,
|
|
333
|
-
location: mergeLocations(start, end),
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
parseEffectStmt() {
|
|
337
|
-
const start = this.consume("EFFECT", "Expected 'effect'").location;
|
|
338
|
-
// Effect type: identifier.identifier...
|
|
339
|
-
let effectType = this.consume("IDENTIFIER", "Expected effect type").lexeme;
|
|
340
|
-
while (this.match("DOT")) {
|
|
341
|
-
effectType += "." + this.consume("IDENTIFIER", "Expected identifier after '.'").lexeme;
|
|
342
|
-
}
|
|
343
|
-
this.consume("LPAREN", "Expected '(' after effect type");
|
|
344
|
-
this.consume("LBRACE", "Expected '{' for effect arguments");
|
|
345
|
-
const args = [];
|
|
346
|
-
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
347
|
-
args.push(this.parseEffectArg());
|
|
348
|
-
this.match("COMMA"); // Optional trailing comma
|
|
349
|
-
}
|
|
350
|
-
this.consume("RBRACE", "Expected '}' after effect arguments");
|
|
351
|
-
const end = this.consume("RPAREN", "Expected ')' to close effect").location;
|
|
352
|
-
return {
|
|
353
|
-
kind: "effect",
|
|
354
|
-
effectType,
|
|
355
|
-
args,
|
|
356
|
-
location: mergeLocations(start, end),
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
parseEffectArg() {
|
|
360
|
-
const nameToken = this.match("IDENTIFIER", "FAIL")
|
|
361
|
-
? this.previous()
|
|
362
|
-
: this.consume("IDENTIFIER", "Expected argument name");
|
|
363
|
-
this.consume("COLON", "Expected ':' after argument name");
|
|
364
|
-
// 'into', 'pass', 'fail' are path arguments
|
|
365
|
-
const isPath = ["into", "pass", "fail"].includes(nameToken.lexeme);
|
|
366
|
-
const value = isPath ? this.parsePath() : this.parseExpression();
|
|
367
|
-
return {
|
|
368
|
-
kind: "effectArg",
|
|
369
|
-
name: nameToken.lexeme,
|
|
370
|
-
value,
|
|
371
|
-
isPath,
|
|
372
|
-
location: mergeLocations(nameToken.location, value.location),
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* v0.3.2: Parse fail statement
|
|
377
|
-
* FailStmt ::= 'fail' StringLiteral ('with' Expr)?
|
|
378
|
-
*/
|
|
379
|
-
parseFailStmt() {
|
|
380
|
-
const start = this.consume("FAIL", "Expected 'fail'").location;
|
|
381
|
-
const codeToken = this.consume("STRING", "Expected error code string after 'fail'");
|
|
382
|
-
const code = codeToken.value;
|
|
383
|
-
let message;
|
|
384
|
-
let end = codeToken.location;
|
|
385
|
-
if (this.match("WITH")) {
|
|
386
|
-
message = this.parseExpression();
|
|
387
|
-
end = message.location;
|
|
388
|
-
}
|
|
389
|
-
return {
|
|
390
|
-
kind: "fail",
|
|
391
|
-
code,
|
|
392
|
-
message,
|
|
393
|
-
location: mergeLocations(start, end),
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* v0.3.2: Parse stop statement
|
|
398
|
-
* StopStmt ::= 'stop' StringLiteral
|
|
399
|
-
*/
|
|
400
|
-
parseStopStmt() {
|
|
401
|
-
const start = this.consume("STOP", "Expected 'stop'").location;
|
|
402
|
-
const reasonToken = this.consume("STRING", "Expected reason string after 'stop'");
|
|
403
|
-
const reason = reasonToken.value;
|
|
404
|
-
return {
|
|
405
|
-
kind: "stop",
|
|
406
|
-
reason,
|
|
407
|
-
location: mergeLocations(start, reasonToken.location),
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
// ============ Types ============
|
|
411
|
-
parseTypeExpr() {
|
|
412
|
-
let type = this.parseBaseType();
|
|
413
|
-
// Union type: T | U | V
|
|
414
|
-
if (this.check("PIPE")) {
|
|
415
|
-
const types = [type];
|
|
416
|
-
while (this.match("PIPE")) {
|
|
417
|
-
types.push(this.parseBaseType());
|
|
418
|
-
}
|
|
419
|
-
type = {
|
|
420
|
-
kind: "unionType",
|
|
421
|
-
types,
|
|
422
|
-
location: mergeLocations(types[0].location, types[types.length - 1].location),
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
return type;
|
|
426
|
-
}
|
|
427
|
-
parseBaseType() {
|
|
428
|
-
// v0.3.3: Object type: { field: Type, ... }
|
|
429
|
-
if (this.check("LBRACE")) {
|
|
430
|
-
return this.parseObjectType();
|
|
431
|
-
}
|
|
432
|
-
// Literal types: "string" | 42 | true
|
|
433
|
-
if (this.check("STRING")) {
|
|
434
|
-
const token = this.advance();
|
|
435
|
-
return {
|
|
436
|
-
kind: "literalType",
|
|
437
|
-
value: token.value,
|
|
438
|
-
location: token.location,
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
if (this.check("NUMBER")) {
|
|
442
|
-
const token = this.advance();
|
|
443
|
-
return {
|
|
444
|
-
kind: "literalType",
|
|
445
|
-
value: token.value,
|
|
446
|
-
location: token.location,
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
if (this.check("TRUE") || this.check("FALSE")) {
|
|
450
|
-
const token = this.advance();
|
|
451
|
-
return {
|
|
452
|
-
kind: "literalType",
|
|
453
|
-
value: token.kind === "TRUE",
|
|
454
|
-
location: token.location,
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
if (this.check("NULL")) {
|
|
458
|
-
const token = this.advance();
|
|
459
|
-
return {
|
|
460
|
-
kind: "literalType",
|
|
461
|
-
value: null,
|
|
462
|
-
location: token.location,
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
// Named type: Array<T>, Record<K, V>, or simple type
|
|
466
|
-
const nameToken = this.consume("IDENTIFIER", "Expected type name");
|
|
467
|
-
if (this.match("LT")) {
|
|
468
|
-
// Generic type
|
|
469
|
-
if (nameToken.lexeme === "Array") {
|
|
470
|
-
const elementType = this.parseTypeExpr();
|
|
471
|
-
const end = this.consume("GT", "Expected '>' after array element type").location;
|
|
472
|
-
return {
|
|
473
|
-
kind: "arrayType",
|
|
474
|
-
elementType,
|
|
475
|
-
location: mergeLocations(nameToken.location, end),
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
else if (nameToken.lexeme === "Record") {
|
|
479
|
-
const keyType = this.parseTypeExpr();
|
|
480
|
-
this.consume("COMMA", "Expected ',' between Record type parameters");
|
|
481
|
-
const valueType = this.parseTypeExpr();
|
|
482
|
-
const end = this.consume("GT", "Expected '>' after Record value type").location;
|
|
483
|
-
return {
|
|
484
|
-
kind: "recordType",
|
|
485
|
-
keyType,
|
|
486
|
-
valueType,
|
|
487
|
-
location: mergeLocations(nameToken.location, end),
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
else {
|
|
491
|
-
this.error(`Unknown generic type '${nameToken.lexeme}'`);
|
|
492
|
-
// Consume until >
|
|
493
|
-
while (!this.check("GT") && !this.isAtEnd())
|
|
494
|
-
this.advance();
|
|
495
|
-
this.match("GT");
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
return {
|
|
499
|
-
kind: "simpleType",
|
|
500
|
-
name: nameToken.lexeme,
|
|
501
|
-
location: nameToken.location,
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
/**
|
|
505
|
-
* v0.3.3: Parse object type
|
|
506
|
-
* Syntax: { field: Type, field?: Type, ... }
|
|
507
|
-
*/
|
|
508
|
-
parseObjectType() {
|
|
509
|
-
const start = this.consume("LBRACE", "Expected '{'").location;
|
|
510
|
-
const fields = [];
|
|
511
|
-
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
512
|
-
const nameToken = this.consume("IDENTIFIER", "Expected field name");
|
|
513
|
-
const optional = this.match("QUESTION");
|
|
514
|
-
this.consume("COLON", "Expected ':' after field name");
|
|
515
|
-
const typeExpr = this.parseTypeExpr();
|
|
516
|
-
fields.push({
|
|
517
|
-
kind: "typeField",
|
|
518
|
-
name: nameToken.lexeme,
|
|
519
|
-
typeExpr,
|
|
520
|
-
optional,
|
|
521
|
-
location: mergeLocations(nameToken.location, typeExpr.location),
|
|
522
|
-
});
|
|
523
|
-
// Optional comma between fields
|
|
524
|
-
this.match("COMMA");
|
|
525
|
-
}
|
|
526
|
-
const end = this.consume("RBRACE", "Expected '}' to close object type").location;
|
|
527
|
-
return {
|
|
528
|
-
kind: "objectType",
|
|
529
|
-
fields,
|
|
530
|
-
location: mergeLocations(start, end),
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
// ============ Expressions (Pratt Parser) ============
|
|
534
|
-
parseExpression(minPrecedence = 0 /* Precedence.NONE */) {
|
|
535
|
-
let left = this.parsePrimary();
|
|
536
|
-
while (true) {
|
|
537
|
-
const precedence = getBinaryPrecedence(this.peek().kind);
|
|
538
|
-
if (precedence <= minPrecedence)
|
|
539
|
-
break;
|
|
540
|
-
// Handle ternary specially
|
|
541
|
-
if (this.peek().kind === "QUESTION") {
|
|
542
|
-
left = this.parseTernary(left);
|
|
543
|
-
continue;
|
|
544
|
-
}
|
|
545
|
-
const op = tokenToBinaryOp(this.peek().kind);
|
|
546
|
-
if (!op)
|
|
547
|
-
break;
|
|
548
|
-
this.advance(); // Consume operator
|
|
549
|
-
const nextPrecedence = isRightAssociative(this.previous().kind)
|
|
550
|
-
? precedence - 1
|
|
551
|
-
: precedence;
|
|
552
|
-
const right = this.parseExpression(nextPrecedence);
|
|
553
|
-
left = {
|
|
554
|
-
kind: "binary",
|
|
555
|
-
operator: op,
|
|
556
|
-
left,
|
|
557
|
-
right,
|
|
558
|
-
location: mergeLocations(left.location, right.location),
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
return left;
|
|
562
|
-
}
|
|
563
|
-
parseTernary(condition) {
|
|
564
|
-
this.consume("QUESTION", "Expected '?'");
|
|
565
|
-
const consequent = this.parseExpression();
|
|
566
|
-
this.consume("COLON", "Expected ':' in ternary expression");
|
|
567
|
-
const alternate = this.parseExpression(1 /* Precedence.TERNARY */ - 1);
|
|
568
|
-
return {
|
|
569
|
-
kind: "ternary",
|
|
570
|
-
condition,
|
|
571
|
-
consequent,
|
|
572
|
-
alternate,
|
|
573
|
-
location: mergeLocations(condition.location, alternate.location),
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
parsePrimary() {
|
|
577
|
-
// Unary operators
|
|
578
|
-
if (this.check("BANG") || (this.check("MINUS") && this.isUnaryContext())) {
|
|
579
|
-
const op = this.advance();
|
|
580
|
-
const operand = this.parsePrimary();
|
|
581
|
-
return {
|
|
582
|
-
kind: "unary",
|
|
583
|
-
operator: op.kind === "BANG" ? "!" : "-",
|
|
584
|
-
operand,
|
|
585
|
-
location: mergeLocations(op.location, operand.location),
|
|
586
|
-
};
|
|
587
|
-
}
|
|
588
|
-
// Grouping
|
|
589
|
-
if (this.match("LPAREN")) {
|
|
590
|
-
const expr = this.parseExpression();
|
|
591
|
-
this.consume("RPAREN", "Expected ')' after expression");
|
|
592
|
-
return expr;
|
|
593
|
-
}
|
|
594
|
-
// Object literal
|
|
595
|
-
if (this.check("LBRACE")) {
|
|
596
|
-
return this.parseObjectLiteral();
|
|
597
|
-
}
|
|
598
|
-
// Array literal
|
|
599
|
-
if (this.check("LBRACKET")) {
|
|
600
|
-
return this.parseArrayLiteral();
|
|
601
|
-
}
|
|
602
|
-
// Literals
|
|
603
|
-
if (this.check("NUMBER")) {
|
|
604
|
-
const token = this.advance();
|
|
605
|
-
return {
|
|
606
|
-
kind: "literal",
|
|
607
|
-
value: token.value,
|
|
608
|
-
literalType: "number",
|
|
609
|
-
location: token.location,
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
if (this.check("STRING")) {
|
|
613
|
-
const token = this.advance();
|
|
614
|
-
return {
|
|
615
|
-
kind: "literal",
|
|
616
|
-
value: token.value,
|
|
617
|
-
literalType: "string",
|
|
618
|
-
location: token.location,
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
if (this.check("TRUE") || this.check("FALSE")) {
|
|
622
|
-
const token = this.advance();
|
|
623
|
-
return {
|
|
624
|
-
kind: "literal",
|
|
625
|
-
value: token.kind === "TRUE",
|
|
626
|
-
literalType: "boolean",
|
|
627
|
-
location: token.location,
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
if (this.check("NULL")) {
|
|
631
|
-
const token = this.advance();
|
|
632
|
-
return {
|
|
633
|
-
kind: "literal",
|
|
634
|
-
value: null,
|
|
635
|
-
literalType: "null",
|
|
636
|
-
location: token.location,
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
// System identifiers
|
|
640
|
-
if (this.check("SYSTEM_IDENT")) {
|
|
641
|
-
const token = this.advance();
|
|
642
|
-
// Parse $system.uuid → ["system", "uuid"]
|
|
643
|
-
const path = token.lexeme.slice(1).split(".");
|
|
644
|
-
return this.parsePostfix({
|
|
645
|
-
kind: "systemIdent",
|
|
646
|
-
path,
|
|
647
|
-
location: token.location,
|
|
648
|
-
});
|
|
649
|
-
}
|
|
650
|
-
// v0.3.2: $acc removed - reduce pattern deprecated
|
|
651
|
-
if (this.check("ITEM")) {
|
|
652
|
-
const token = this.advance();
|
|
653
|
-
return this.parsePostfix({
|
|
654
|
-
kind: "iterationVar",
|
|
655
|
-
name: "item",
|
|
656
|
-
location: token.location,
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
// Identifier or function call
|
|
660
|
-
if (this.check("IDENTIFIER")) {
|
|
661
|
-
const token = this.advance();
|
|
662
|
-
// Function call
|
|
663
|
-
if (this.check("LPAREN")) {
|
|
664
|
-
return this.parseFunctionCall(token);
|
|
665
|
-
}
|
|
666
|
-
// Plain identifier
|
|
667
|
-
return this.parsePostfix({
|
|
668
|
-
kind: "identifier",
|
|
669
|
-
name: token.lexeme,
|
|
670
|
-
location: token.location,
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
this.error(`Unexpected token '${this.peek().lexeme}'`);
|
|
674
|
-
return {
|
|
675
|
-
kind: "literal",
|
|
676
|
-
value: null,
|
|
677
|
-
literalType: "null",
|
|
678
|
-
location: this.peek().location,
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
parseFunctionCall(nameToken) {
|
|
682
|
-
this.consume("LPAREN", "Expected '(' for function call");
|
|
683
|
-
const args = [];
|
|
684
|
-
if (!this.check("RPAREN")) {
|
|
685
|
-
do {
|
|
686
|
-
args.push(this.parseExpression());
|
|
687
|
-
} while (this.match("COMMA"));
|
|
688
|
-
}
|
|
689
|
-
const end = this.consume("RPAREN", "Expected ')' after arguments").location;
|
|
690
|
-
return this.parsePostfix({
|
|
691
|
-
kind: "functionCall",
|
|
692
|
-
name: nameToken.lexeme,
|
|
693
|
-
args,
|
|
694
|
-
location: mergeLocations(nameToken.location, end),
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
parsePostfix(expr) {
|
|
698
|
-
while (true) {
|
|
699
|
-
if (this.match("DOT")) {
|
|
700
|
-
const prop = this.consume("IDENTIFIER", "Expected property name after '.'");
|
|
701
|
-
expr = {
|
|
702
|
-
kind: "propertyAccess",
|
|
703
|
-
object: expr,
|
|
704
|
-
property: prop.lexeme,
|
|
705
|
-
location: mergeLocations(expr.location, prop.location),
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
else if (this.match("LBRACKET")) {
|
|
709
|
-
const index = this.parseExpression();
|
|
710
|
-
const end = this.consume("RBRACKET", "Expected ']' after index").location;
|
|
711
|
-
expr = {
|
|
712
|
-
kind: "indexAccess",
|
|
713
|
-
object: expr,
|
|
714
|
-
index,
|
|
715
|
-
location: mergeLocations(expr.location, end),
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
|
-
else {
|
|
719
|
-
break;
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
return expr;
|
|
723
|
-
}
|
|
724
|
-
parseObjectLiteral() {
|
|
725
|
-
const start = this.consume("LBRACE", "Expected '{'").location;
|
|
726
|
-
const properties = [];
|
|
727
|
-
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
728
|
-
const keyToken = this.consume("IDENTIFIER", "Expected property name");
|
|
729
|
-
this.consume("COLON", "Expected ':' after property name");
|
|
730
|
-
const value = this.parseExpression();
|
|
731
|
-
properties.push({
|
|
732
|
-
kind: "objectProperty",
|
|
733
|
-
key: keyToken.lexeme,
|
|
734
|
-
value,
|
|
735
|
-
location: mergeLocations(keyToken.location, value.location),
|
|
736
|
-
});
|
|
737
|
-
if (!this.check("RBRACE")) {
|
|
738
|
-
this.consume("COMMA", "Expected ',' between properties");
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
const end = this.consume("RBRACE", "Expected '}' to close object").location;
|
|
742
|
-
return {
|
|
743
|
-
kind: "objectLiteral",
|
|
744
|
-
properties,
|
|
745
|
-
location: mergeLocations(start, end),
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
parseArrayLiteral() {
|
|
749
|
-
const start = this.consume("LBRACKET", "Expected '['").location;
|
|
750
|
-
const elements = [];
|
|
751
|
-
while (!this.check("RBRACKET") && !this.isAtEnd()) {
|
|
752
|
-
elements.push(this.parseExpression());
|
|
753
|
-
if (!this.check("RBRACKET")) {
|
|
754
|
-
this.consume("COMMA", "Expected ',' between elements");
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
const end = this.consume("RBRACKET", "Expected ']' to close array").location;
|
|
758
|
-
return {
|
|
759
|
-
kind: "arrayLiteral",
|
|
760
|
-
elements,
|
|
761
|
-
location: mergeLocations(start, end),
|
|
762
|
-
};
|
|
763
|
-
}
|
|
764
|
-
// ============ Path ============
|
|
765
|
-
parsePath() {
|
|
766
|
-
const segments = [];
|
|
767
|
-
const start = this.peek().location;
|
|
768
|
-
// First segment must be identifier
|
|
769
|
-
const first = this.consume("IDENTIFIER", "Expected identifier");
|
|
770
|
-
segments.push({
|
|
771
|
-
kind: "propertySegment",
|
|
772
|
-
name: first.lexeme,
|
|
773
|
-
location: first.location,
|
|
774
|
-
});
|
|
775
|
-
// Subsequent segments: .prop or [index]
|
|
776
|
-
while (true) {
|
|
777
|
-
if (this.match("DOT")) {
|
|
778
|
-
const prop = this.consume("IDENTIFIER", "Expected property name");
|
|
779
|
-
segments.push({
|
|
780
|
-
kind: "propertySegment",
|
|
781
|
-
name: prop.lexeme,
|
|
782
|
-
location: prop.location,
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
|
-
else if (this.match("LBRACKET")) {
|
|
786
|
-
const index = this.parseExpression();
|
|
787
|
-
const end = this.consume("RBRACKET", "Expected ']'").location;
|
|
788
|
-
segments.push({
|
|
789
|
-
kind: "indexSegment",
|
|
790
|
-
index,
|
|
791
|
-
location: mergeLocations(index.location, end),
|
|
792
|
-
});
|
|
793
|
-
}
|
|
794
|
-
else {
|
|
795
|
-
break;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
const last = segments[segments.length - 1];
|
|
799
|
-
return {
|
|
800
|
-
kind: "path",
|
|
801
|
-
segments,
|
|
802
|
-
location: mergeLocations(start, last.location),
|
|
803
|
-
};
|
|
804
|
-
}
|
|
805
|
-
// ============ Helpers ============
|
|
806
|
-
isUnaryContext() {
|
|
807
|
-
// Minus is unary at start of expression or after operators
|
|
808
|
-
if (this.current === 0)
|
|
809
|
-
return true;
|
|
810
|
-
const prev = this.previous();
|
|
811
|
-
const unaryPrecedingTokens = [
|
|
812
|
-
"LPAREN", "LBRACKET", "LBRACE", "COMMA", "COLON", "EQ",
|
|
813
|
-
"PLUS", "MINUS", "STAR", "SLASH", "PERCENT",
|
|
814
|
-
"EQ_EQ", "BANG_EQ", "LT", "LT_EQ", "GT", "GT_EQ",
|
|
815
|
-
"AMP_AMP", "PIPE_PIPE", "BANG", "QUESTION", "QUESTION_QUESTION",
|
|
816
|
-
];
|
|
817
|
-
return unaryPrecedingTokens.includes(prev.kind);
|
|
818
|
-
}
|
|
819
|
-
peek() {
|
|
820
|
-
return this.tokens[this.current];
|
|
821
|
-
}
|
|
822
|
-
peekNext() {
|
|
823
|
-
if (this.current + 1 >= this.tokens.length) {
|
|
824
|
-
return this.tokens[this.tokens.length - 1];
|
|
825
|
-
}
|
|
826
|
-
return this.tokens[this.current + 1];
|
|
827
|
-
}
|
|
828
|
-
previous() {
|
|
829
|
-
return this.tokens[this.current - 1];
|
|
830
|
-
}
|
|
831
|
-
isAtEnd() {
|
|
832
|
-
return this.peek().kind === "EOF";
|
|
833
|
-
}
|
|
834
|
-
advance() {
|
|
835
|
-
if (!this.isAtEnd())
|
|
836
|
-
this.current++;
|
|
837
|
-
return this.previous();
|
|
838
|
-
}
|
|
839
|
-
check(kind) {
|
|
840
|
-
if (this.isAtEnd())
|
|
841
|
-
return false;
|
|
842
|
-
return this.peek().kind === kind;
|
|
843
|
-
}
|
|
844
|
-
isOnceIntentContext() {
|
|
845
|
-
if (!this.check("IDENTIFIER"))
|
|
846
|
-
return false;
|
|
847
|
-
const token = this.peek();
|
|
848
|
-
if (token.lexeme !== "onceIntent")
|
|
849
|
-
return false;
|
|
850
|
-
const next = this.peekNext();
|
|
851
|
-
return next.kind === "LBRACE" || next.kind === "WHEN";
|
|
852
|
-
}
|
|
853
|
-
match(...kinds) {
|
|
854
|
-
for (const kind of kinds) {
|
|
855
|
-
if (this.check(kind)) {
|
|
856
|
-
this.advance();
|
|
857
|
-
return true;
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
return false;
|
|
861
|
-
}
|
|
862
|
-
consume(kind, message) {
|
|
863
|
-
if (this.check(kind))
|
|
864
|
-
return this.advance();
|
|
865
|
-
throw this.errorAtCurrent(message);
|
|
866
|
-
}
|
|
867
|
-
error(message) {
|
|
868
|
-
this.diagnostics.push({
|
|
869
|
-
severity: "error",
|
|
870
|
-
code: "MEL_PARSER",
|
|
871
|
-
message,
|
|
872
|
-
location: this.previous().location,
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
errorAtCurrent(message) {
|
|
876
|
-
this.diagnostics.push({
|
|
877
|
-
severity: "error",
|
|
878
|
-
code: "MEL_PARSER",
|
|
879
|
-
message,
|
|
880
|
-
location: this.peek().location,
|
|
881
|
-
});
|
|
882
|
-
return new Error(message);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
/**
|
|
886
|
-
* Parse tokens into an AST
|
|
887
|
-
*/
|
|
888
|
-
export function parse(tokens) {
|
|
889
|
-
const parser = new Parser(tokens);
|
|
890
|
-
return parser.parse();
|
|
891
|
-
}
|
|
892
|
-
//# sourceMappingURL=parser.js.map
|