@manifesto-ai/compiler 1.6.1 → 1.7.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 +64 -16
- package/dist/chunk-DJY6BFGK.js +74 -0
- package/dist/chunk-DJY6BFGK.js.map +1 -0
- package/dist/chunk-QP2LGMBA.js +4522 -0
- package/dist/chunk-QP2LGMBA.js.map +1 -0
- package/dist/chunk-QXLPDCLA.js +33 -0
- package/dist/chunk-QXLPDCLA.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 +2710 -11
- package/dist/index.js +2189 -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 +42 -24
- 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.d.ts +0 -126
- package/dist/api/compile-mel.d.ts.map +0 -1
- package/dist/api/compile-mel.js +0 -129
- 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 -59
- 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 -576
- 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 -153
- 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 -709
- 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 -368
- 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 -351
- 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 -203
- 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
|
@@ -0,0 +1,4522 @@
|
|
|
1
|
+
// src/lexer/tokens.ts
|
|
2
|
+
var KEYWORDS = {
|
|
3
|
+
domain: "DOMAIN",
|
|
4
|
+
state: "STATE",
|
|
5
|
+
computed: "COMPUTED",
|
|
6
|
+
action: "ACTION",
|
|
7
|
+
effect: "EFFECT",
|
|
8
|
+
when: "WHEN",
|
|
9
|
+
once: "ONCE",
|
|
10
|
+
patch: "PATCH",
|
|
11
|
+
unset: "UNSET",
|
|
12
|
+
merge: "MERGE",
|
|
13
|
+
true: "TRUE",
|
|
14
|
+
false: "FALSE",
|
|
15
|
+
null: "NULL",
|
|
16
|
+
as: "AS",
|
|
17
|
+
available: "AVAILABLE",
|
|
18
|
+
// v0.3.2
|
|
19
|
+
fail: "FAIL",
|
|
20
|
+
// v0.3.2
|
|
21
|
+
stop: "STOP",
|
|
22
|
+
// v0.3.2
|
|
23
|
+
with: "WITH",
|
|
24
|
+
// v0.3.2
|
|
25
|
+
type: "TYPE",
|
|
26
|
+
// v0.3.3
|
|
27
|
+
import: "IMPORT",
|
|
28
|
+
from: "FROM",
|
|
29
|
+
export: "EXPORT"
|
|
30
|
+
};
|
|
31
|
+
var RESERVED_KEYWORDS = /* @__PURE__ */ new Set([
|
|
32
|
+
// Control flow (forbidden)
|
|
33
|
+
"function",
|
|
34
|
+
"var",
|
|
35
|
+
"let",
|
|
36
|
+
"const",
|
|
37
|
+
"if",
|
|
38
|
+
"else",
|
|
39
|
+
"for",
|
|
40
|
+
"while",
|
|
41
|
+
"do",
|
|
42
|
+
"switch",
|
|
43
|
+
"case",
|
|
44
|
+
"break",
|
|
45
|
+
"continue",
|
|
46
|
+
"return",
|
|
47
|
+
"throw",
|
|
48
|
+
"try",
|
|
49
|
+
"catch",
|
|
50
|
+
"finally",
|
|
51
|
+
"new",
|
|
52
|
+
"delete",
|
|
53
|
+
"instanceof",
|
|
54
|
+
"void",
|
|
55
|
+
// NOTE: "typeof" removed - now a MEL builtin function
|
|
56
|
+
// NOTE: "with" removed - now a MEL keyword in v0.3.2
|
|
57
|
+
"debugger",
|
|
58
|
+
"this",
|
|
59
|
+
"super",
|
|
60
|
+
"arguments",
|
|
61
|
+
"eval",
|
|
62
|
+
// Reserved for future
|
|
63
|
+
"async",
|
|
64
|
+
"await",
|
|
65
|
+
"yield",
|
|
66
|
+
"class",
|
|
67
|
+
"extends",
|
|
68
|
+
"interface",
|
|
69
|
+
"enum",
|
|
70
|
+
"namespace",
|
|
71
|
+
"module"
|
|
72
|
+
// NOTE: "type" removed - now a MEL keyword in v0.3.3
|
|
73
|
+
]);
|
|
74
|
+
function isKeyword(lexeme) {
|
|
75
|
+
return lexeme in KEYWORDS;
|
|
76
|
+
}
|
|
77
|
+
function isReserved(lexeme) {
|
|
78
|
+
return RESERVED_KEYWORDS.has(lexeme);
|
|
79
|
+
}
|
|
80
|
+
function getKeywordKind(lexeme) {
|
|
81
|
+
return Object.hasOwn(KEYWORDS, lexeme) ? KEYWORDS[lexeme] : void 0;
|
|
82
|
+
}
|
|
83
|
+
function createToken(kind, lexeme, location, value) {
|
|
84
|
+
return { kind, lexeme, location, value };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/lexer/source-location.ts
|
|
88
|
+
function createPosition(line, column, offset) {
|
|
89
|
+
return { line, column, offset };
|
|
90
|
+
}
|
|
91
|
+
function createLocation(start, end, source) {
|
|
92
|
+
return { start, end, source };
|
|
93
|
+
}
|
|
94
|
+
function createPointLocation(pos, source) {
|
|
95
|
+
return { start: pos, end: pos, source };
|
|
96
|
+
}
|
|
97
|
+
function mergeLocations(a, b) {
|
|
98
|
+
return {
|
|
99
|
+
start: a.start.offset < b.start.offset ? a.start : b.start,
|
|
100
|
+
end: a.end.offset > b.end.offset ? a.end : b.end,
|
|
101
|
+
source: a.source ?? b.source
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/lexer/lexer.ts
|
|
106
|
+
var Lexer = class {
|
|
107
|
+
source;
|
|
108
|
+
sourcePath;
|
|
109
|
+
tokens = [];
|
|
110
|
+
diagnostics = [];
|
|
111
|
+
// Current position
|
|
112
|
+
start = 0;
|
|
113
|
+
// Start of current token
|
|
114
|
+
current = 0;
|
|
115
|
+
// Current character
|
|
116
|
+
line = 1;
|
|
117
|
+
column = 1;
|
|
118
|
+
lineStart = 0;
|
|
119
|
+
// Offset of current line start
|
|
120
|
+
constructor(source, sourcePath) {
|
|
121
|
+
this.source = source;
|
|
122
|
+
this.sourcePath = sourcePath;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Tokenize the source code
|
|
126
|
+
*/
|
|
127
|
+
tokenize() {
|
|
128
|
+
while (!this.isAtEnd()) {
|
|
129
|
+
this.start = this.current;
|
|
130
|
+
this.scanToken();
|
|
131
|
+
}
|
|
132
|
+
this.tokens.push(
|
|
133
|
+
createToken("EOF", "", this.currentLocation())
|
|
134
|
+
);
|
|
135
|
+
return {
|
|
136
|
+
tokens: this.tokens,
|
|
137
|
+
diagnostics: this.diagnostics
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
scanToken() {
|
|
141
|
+
const c = this.advance();
|
|
142
|
+
switch (c) {
|
|
143
|
+
// Single-character tokens
|
|
144
|
+
case "(":
|
|
145
|
+
this.addToken("LPAREN");
|
|
146
|
+
break;
|
|
147
|
+
case ")":
|
|
148
|
+
this.addToken("RPAREN");
|
|
149
|
+
break;
|
|
150
|
+
case "{":
|
|
151
|
+
this.addToken("LBRACE");
|
|
152
|
+
break;
|
|
153
|
+
case "}":
|
|
154
|
+
this.addToken("RBRACE");
|
|
155
|
+
break;
|
|
156
|
+
case "[":
|
|
157
|
+
this.addToken("LBRACKET");
|
|
158
|
+
break;
|
|
159
|
+
case "]":
|
|
160
|
+
this.addToken("RBRACKET");
|
|
161
|
+
break;
|
|
162
|
+
case ",":
|
|
163
|
+
this.addToken("COMMA");
|
|
164
|
+
break;
|
|
165
|
+
case ";":
|
|
166
|
+
this.addToken("SEMICOLON");
|
|
167
|
+
break;
|
|
168
|
+
case ".":
|
|
169
|
+
this.addToken("DOT");
|
|
170
|
+
break;
|
|
171
|
+
case "+":
|
|
172
|
+
this.addToken("PLUS");
|
|
173
|
+
break;
|
|
174
|
+
case "-":
|
|
175
|
+
this.addToken("MINUS");
|
|
176
|
+
break;
|
|
177
|
+
case "*":
|
|
178
|
+
this.addToken("STAR");
|
|
179
|
+
break;
|
|
180
|
+
case "%":
|
|
181
|
+
this.addToken("PERCENT");
|
|
182
|
+
break;
|
|
183
|
+
case ":":
|
|
184
|
+
this.addToken("COLON");
|
|
185
|
+
break;
|
|
186
|
+
// Two-character tokens
|
|
187
|
+
case "=":
|
|
188
|
+
this.addToken(this.match("=") ? "EQ_EQ" : "EQ");
|
|
189
|
+
break;
|
|
190
|
+
case "!":
|
|
191
|
+
this.addToken(this.match("=") ? "BANG_EQ" : "BANG");
|
|
192
|
+
break;
|
|
193
|
+
case "<":
|
|
194
|
+
this.addToken(this.match("=") ? "LT_EQ" : "LT");
|
|
195
|
+
break;
|
|
196
|
+
case ">":
|
|
197
|
+
this.addToken(this.match("=") ? "GT_EQ" : "GT");
|
|
198
|
+
break;
|
|
199
|
+
case "&":
|
|
200
|
+
if (this.match("&")) {
|
|
201
|
+
this.addToken("AMP_AMP");
|
|
202
|
+
} else {
|
|
203
|
+
this.error("Expected '&&' for logical AND");
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
case "|":
|
|
207
|
+
if (this.match("|")) {
|
|
208
|
+
this.addToken("PIPE_PIPE");
|
|
209
|
+
} else {
|
|
210
|
+
this.addToken("PIPE");
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
case "?":
|
|
214
|
+
this.addToken(this.match("?") ? "QUESTION_QUESTION" : "QUESTION");
|
|
215
|
+
break;
|
|
216
|
+
// Slash or comment
|
|
217
|
+
case "/":
|
|
218
|
+
if (this.match("/")) {
|
|
219
|
+
this.lineComment();
|
|
220
|
+
} else if (this.match("*")) {
|
|
221
|
+
this.blockComment();
|
|
222
|
+
} else {
|
|
223
|
+
this.addToken("SLASH");
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
226
|
+
// Whitespace
|
|
227
|
+
case " ":
|
|
228
|
+
case "\r":
|
|
229
|
+
case " ":
|
|
230
|
+
break;
|
|
231
|
+
case "\n":
|
|
232
|
+
this.newline();
|
|
233
|
+
break;
|
|
234
|
+
// String literals
|
|
235
|
+
case '"':
|
|
236
|
+
this.string('"');
|
|
237
|
+
break;
|
|
238
|
+
case "'":
|
|
239
|
+
this.string("'");
|
|
240
|
+
break;
|
|
241
|
+
// System identifiers ($...)
|
|
242
|
+
case "$":
|
|
243
|
+
this.systemIdentifier();
|
|
244
|
+
break;
|
|
245
|
+
default:
|
|
246
|
+
if (this.isDigit(c)) {
|
|
247
|
+
this.number();
|
|
248
|
+
} else if (this.isAlpha(c)) {
|
|
249
|
+
this.identifier();
|
|
250
|
+
} else {
|
|
251
|
+
this.error(`Unexpected character '${c}'`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// ============ Token Scanners ============
|
|
256
|
+
lineComment() {
|
|
257
|
+
while (this.peek() !== "\n" && !this.isAtEnd()) {
|
|
258
|
+
this.advance();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
blockComment() {
|
|
262
|
+
const startLine = this.line;
|
|
263
|
+
const startColumn = this.column - 2;
|
|
264
|
+
while (!this.isAtEnd()) {
|
|
265
|
+
if (this.peek() === "*" && this.peekNext() === "/") {
|
|
266
|
+
this.advance();
|
|
267
|
+
this.advance();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (this.peek() === "\n") {
|
|
271
|
+
this.newline();
|
|
272
|
+
}
|
|
273
|
+
this.advance();
|
|
274
|
+
}
|
|
275
|
+
this.error(`Unterminated block comment starting at line ${startLine}:${startColumn}`);
|
|
276
|
+
}
|
|
277
|
+
string(quote) {
|
|
278
|
+
const startLine = this.line;
|
|
279
|
+
const startColumn = this.column - 1;
|
|
280
|
+
let value = "";
|
|
281
|
+
while (this.peek() !== quote && !this.isAtEnd()) {
|
|
282
|
+
if (this.peek() === "\n") {
|
|
283
|
+
this.error("Unterminated string literal");
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (this.peek() === "\\") {
|
|
287
|
+
this.advance();
|
|
288
|
+
const escaped = this.advance();
|
|
289
|
+
switch (escaped) {
|
|
290
|
+
case "n":
|
|
291
|
+
value += "\n";
|
|
292
|
+
break;
|
|
293
|
+
case "r":
|
|
294
|
+
value += "\r";
|
|
295
|
+
break;
|
|
296
|
+
case "t":
|
|
297
|
+
value += " ";
|
|
298
|
+
break;
|
|
299
|
+
case "\\":
|
|
300
|
+
value += "\\";
|
|
301
|
+
break;
|
|
302
|
+
case "'":
|
|
303
|
+
value += "'";
|
|
304
|
+
break;
|
|
305
|
+
case '"':
|
|
306
|
+
value += '"';
|
|
307
|
+
break;
|
|
308
|
+
case "0":
|
|
309
|
+
value += "\0";
|
|
310
|
+
break;
|
|
311
|
+
default:
|
|
312
|
+
this.error(`Invalid escape sequence '\\${escaped}'`);
|
|
313
|
+
value += escaped;
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
value += this.advance();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (this.isAtEnd()) {
|
|
320
|
+
this.error(`Unterminated string starting at line ${startLine}:${startColumn}`);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
this.advance();
|
|
324
|
+
this.addToken("STRING", value);
|
|
325
|
+
}
|
|
326
|
+
number() {
|
|
327
|
+
if (this.source[this.start] === "0" && (this.peek() === "x" || this.peek() === "X")) {
|
|
328
|
+
this.advance();
|
|
329
|
+
while (this.isHexDigit(this.peek())) {
|
|
330
|
+
this.advance();
|
|
331
|
+
}
|
|
332
|
+
const hexStr = this.source.slice(this.start + 2, this.current);
|
|
333
|
+
const value2 = parseInt(hexStr, 16);
|
|
334
|
+
this.addToken("NUMBER", value2);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
while (this.isDigit(this.peek())) {
|
|
338
|
+
this.advance();
|
|
339
|
+
}
|
|
340
|
+
if (this.peek() === "." && this.isDigit(this.peekNext())) {
|
|
341
|
+
this.advance();
|
|
342
|
+
while (this.isDigit(this.peek())) {
|
|
343
|
+
this.advance();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (this.peek() === "e" || this.peek() === "E") {
|
|
347
|
+
this.advance();
|
|
348
|
+
if (this.peek() === "+" || this.peek() === "-") {
|
|
349
|
+
this.advance();
|
|
350
|
+
}
|
|
351
|
+
if (!this.isDigit(this.peek())) {
|
|
352
|
+
this.error("Invalid number: expected digits after exponent");
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
while (this.isDigit(this.peek())) {
|
|
356
|
+
this.advance();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const value = parseFloat(this.source.slice(this.start, this.current));
|
|
360
|
+
this.addToken("NUMBER", value);
|
|
361
|
+
}
|
|
362
|
+
identifier() {
|
|
363
|
+
while (this.isAlphaNumeric(this.peek())) {
|
|
364
|
+
if (this.peek() === "$") {
|
|
365
|
+
this.advance();
|
|
366
|
+
this.error("'$' is forbidden in identifiers (MEL A17)");
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
this.advance();
|
|
370
|
+
}
|
|
371
|
+
const lexeme = this.source.slice(this.start, this.current);
|
|
372
|
+
if (lexeme.startsWith("__sys__")) {
|
|
373
|
+
this.error("'__sys__' prefix is reserved for compiler-generated identifiers (MEL A26)");
|
|
374
|
+
this.addToken("ERROR");
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (isReserved(lexeme)) {
|
|
378
|
+
this.error(`'${lexeme}' is a reserved keyword and cannot be used`);
|
|
379
|
+
this.addToken("ERROR");
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const keywordKind = getKeywordKind(lexeme);
|
|
383
|
+
if (keywordKind) {
|
|
384
|
+
this.addToken(keywordKind);
|
|
385
|
+
} else {
|
|
386
|
+
this.addToken("IDENTIFIER");
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
systemIdentifier() {
|
|
390
|
+
if (!this.isAlpha(this.peek())) {
|
|
391
|
+
this.error("Expected identifier after '$'");
|
|
392
|
+
this.addToken("ERROR");
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
while (this.isAlphaNumeric(this.peek())) {
|
|
396
|
+
this.advance();
|
|
397
|
+
}
|
|
398
|
+
const initialLexeme = this.source.slice(this.start, this.current);
|
|
399
|
+
if (initialLexeme === "$item") {
|
|
400
|
+
this.addToken("ITEM");
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (initialLexeme === "$system" || initialLexeme === "$meta" || initialLexeme === "$input") {
|
|
404
|
+
while (this.peek() === "." && this.isAlpha(this.peekNext())) {
|
|
405
|
+
this.advance();
|
|
406
|
+
while (this.isAlphaNumeric(this.peek())) {
|
|
407
|
+
this.advance();
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
this.addToken("SYSTEM_IDENT");
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
this.error(`Invalid system identifier '${initialLexeme}'. Expected $system.*, $meta.*, $input.*, or $item`);
|
|
414
|
+
this.addToken("ERROR");
|
|
415
|
+
}
|
|
416
|
+
// ============ Helpers ============
|
|
417
|
+
isAtEnd() {
|
|
418
|
+
return this.current >= this.source.length;
|
|
419
|
+
}
|
|
420
|
+
advance() {
|
|
421
|
+
const c = this.source[this.current];
|
|
422
|
+
this.current++;
|
|
423
|
+
this.column++;
|
|
424
|
+
return c;
|
|
425
|
+
}
|
|
426
|
+
peek() {
|
|
427
|
+
if (this.isAtEnd()) return "\0";
|
|
428
|
+
return this.source[this.current];
|
|
429
|
+
}
|
|
430
|
+
peekNext() {
|
|
431
|
+
if (this.current + 1 >= this.source.length) return "\0";
|
|
432
|
+
return this.source[this.current + 1];
|
|
433
|
+
}
|
|
434
|
+
match(expected) {
|
|
435
|
+
if (this.isAtEnd()) return false;
|
|
436
|
+
if (this.source[this.current] !== expected) return false;
|
|
437
|
+
this.current++;
|
|
438
|
+
this.column++;
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
newline() {
|
|
442
|
+
this.line++;
|
|
443
|
+
this.column = 1;
|
|
444
|
+
this.lineStart = this.current;
|
|
445
|
+
}
|
|
446
|
+
isDigit(c) {
|
|
447
|
+
return c >= "0" && c <= "9";
|
|
448
|
+
}
|
|
449
|
+
isHexDigit(c) {
|
|
450
|
+
return this.isDigit(c) || c >= "a" && c <= "f" || c >= "A" && c <= "F";
|
|
451
|
+
}
|
|
452
|
+
isAlpha(c) {
|
|
453
|
+
return c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "_";
|
|
454
|
+
}
|
|
455
|
+
isAlphaNumeric(c) {
|
|
456
|
+
return this.isAlpha(c) || this.isDigit(c);
|
|
457
|
+
}
|
|
458
|
+
currentLocation() {
|
|
459
|
+
const startPos = this.positionAt(this.start);
|
|
460
|
+
const endPos = this.positionAt(this.current);
|
|
461
|
+
return createLocation(startPos, endPos, this.sourcePath);
|
|
462
|
+
}
|
|
463
|
+
positionAt(offset) {
|
|
464
|
+
let line = 1;
|
|
465
|
+
let lineStart = 0;
|
|
466
|
+
for (let i = 0; i < offset; i++) {
|
|
467
|
+
if (this.source[i] === "\n") {
|
|
468
|
+
line++;
|
|
469
|
+
lineStart = i + 1;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return createPosition(line, offset - lineStart + 1, offset);
|
|
473
|
+
}
|
|
474
|
+
addToken(kind, value) {
|
|
475
|
+
const lexeme = this.source.slice(this.start, this.current);
|
|
476
|
+
this.tokens.push(createToken(kind, lexeme, this.currentLocation(), value));
|
|
477
|
+
}
|
|
478
|
+
error(message) {
|
|
479
|
+
const location = this.currentLocation();
|
|
480
|
+
this.diagnostics.push({
|
|
481
|
+
severity: "error",
|
|
482
|
+
code: "MEL_LEXER",
|
|
483
|
+
message,
|
|
484
|
+
location,
|
|
485
|
+
source: this.getSourceLine(location.start.line)
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
getSourceLine(lineNumber) {
|
|
489
|
+
const lines = this.source.split("\n");
|
|
490
|
+
return lines[lineNumber - 1] ?? "";
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
function tokenize(source, sourcePath) {
|
|
494
|
+
const lexer = new Lexer(source, sourcePath);
|
|
495
|
+
return lexer.tokenize();
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// src/parser/ast.ts
|
|
499
|
+
function isExprNode(node) {
|
|
500
|
+
const exprKinds = [
|
|
501
|
+
"literal",
|
|
502
|
+
"identifier",
|
|
503
|
+
"systemIdent",
|
|
504
|
+
"iterationVar",
|
|
505
|
+
"propertyAccess",
|
|
506
|
+
"indexAccess",
|
|
507
|
+
"functionCall",
|
|
508
|
+
"unary",
|
|
509
|
+
"binary",
|
|
510
|
+
"ternary",
|
|
511
|
+
"objectLiteral",
|
|
512
|
+
"arrayLiteral"
|
|
513
|
+
];
|
|
514
|
+
return exprKinds.includes(node.kind);
|
|
515
|
+
}
|
|
516
|
+
function isStmtNode(node) {
|
|
517
|
+
const stmtKinds = ["when", "once", "onceIntent", "patch", "effect", "fail", "stop"];
|
|
518
|
+
return stmtKinds.includes(node.kind);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// src/parser/precedence.ts
|
|
522
|
+
var Precedence = /* @__PURE__ */ ((Precedence2) => {
|
|
523
|
+
Precedence2[Precedence2["NONE"] = 0] = "NONE";
|
|
524
|
+
Precedence2[Precedence2["TERNARY"] = 1] = "TERNARY";
|
|
525
|
+
Precedence2[Precedence2["NULLISH"] = 2] = "NULLISH";
|
|
526
|
+
Precedence2[Precedence2["OR"] = 3] = "OR";
|
|
527
|
+
Precedence2[Precedence2["AND"] = 4] = "AND";
|
|
528
|
+
Precedence2[Precedence2["EQUALITY"] = 5] = "EQUALITY";
|
|
529
|
+
Precedence2[Precedence2["COMPARISON"] = 6] = "COMPARISON";
|
|
530
|
+
Precedence2[Precedence2["ADDITIVE"] = 7] = "ADDITIVE";
|
|
531
|
+
Precedence2[Precedence2["MULTIPLICATIVE"] = 8] = "MULTIPLICATIVE";
|
|
532
|
+
Precedence2[Precedence2["UNARY"] = 9] = "UNARY";
|
|
533
|
+
Precedence2[Precedence2["CALL"] = 10] = "CALL";
|
|
534
|
+
Precedence2[Precedence2["ACCESS"] = 11] = "ACCESS";
|
|
535
|
+
return Precedence2;
|
|
536
|
+
})(Precedence || {});
|
|
537
|
+
function getBinaryPrecedence(kind) {
|
|
538
|
+
switch (kind) {
|
|
539
|
+
case "QUESTION":
|
|
540
|
+
return 1 /* TERNARY */;
|
|
541
|
+
case "QUESTION_QUESTION":
|
|
542
|
+
return 2 /* NULLISH */;
|
|
543
|
+
case "PIPE_PIPE":
|
|
544
|
+
return 3 /* OR */;
|
|
545
|
+
case "AMP_AMP":
|
|
546
|
+
return 4 /* AND */;
|
|
547
|
+
case "EQ_EQ":
|
|
548
|
+
case "BANG_EQ":
|
|
549
|
+
return 5 /* EQUALITY */;
|
|
550
|
+
case "LT":
|
|
551
|
+
case "LT_EQ":
|
|
552
|
+
case "GT":
|
|
553
|
+
case "GT_EQ":
|
|
554
|
+
return 6 /* COMPARISON */;
|
|
555
|
+
case "PLUS":
|
|
556
|
+
case "MINUS":
|
|
557
|
+
return 7 /* ADDITIVE */;
|
|
558
|
+
case "STAR":
|
|
559
|
+
case "SLASH":
|
|
560
|
+
case "PERCENT":
|
|
561
|
+
return 8 /* MULTIPLICATIVE */;
|
|
562
|
+
default:
|
|
563
|
+
return 0 /* NONE */;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
function tokenToBinaryOp(kind) {
|
|
567
|
+
switch (kind) {
|
|
568
|
+
case "PLUS":
|
|
569
|
+
return "+";
|
|
570
|
+
case "MINUS":
|
|
571
|
+
return "-";
|
|
572
|
+
case "STAR":
|
|
573
|
+
return "*";
|
|
574
|
+
case "SLASH":
|
|
575
|
+
return "/";
|
|
576
|
+
case "PERCENT":
|
|
577
|
+
return "%";
|
|
578
|
+
case "EQ_EQ":
|
|
579
|
+
return "==";
|
|
580
|
+
case "BANG_EQ":
|
|
581
|
+
return "!=";
|
|
582
|
+
case "LT":
|
|
583
|
+
return "<";
|
|
584
|
+
case "LT_EQ":
|
|
585
|
+
return "<=";
|
|
586
|
+
case "GT":
|
|
587
|
+
return ">";
|
|
588
|
+
case "GT_EQ":
|
|
589
|
+
return ">=";
|
|
590
|
+
case "AMP_AMP":
|
|
591
|
+
return "&&";
|
|
592
|
+
case "PIPE_PIPE":
|
|
593
|
+
return "||";
|
|
594
|
+
case "QUESTION_QUESTION":
|
|
595
|
+
return "??";
|
|
596
|
+
default:
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
function isBinaryOp(kind) {
|
|
601
|
+
return getBinaryPrecedence(kind) !== 0 /* NONE */;
|
|
602
|
+
}
|
|
603
|
+
function isUnaryOp(kind) {
|
|
604
|
+
return kind === "BANG" || kind === "MINUS";
|
|
605
|
+
}
|
|
606
|
+
function isRightAssociative(kind) {
|
|
607
|
+
return kind === "QUESTION" || kind === "QUESTION_QUESTION";
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// src/parser/parser.ts
|
|
611
|
+
var Parser = class {
|
|
612
|
+
tokens;
|
|
613
|
+
current = 0;
|
|
614
|
+
diagnostics = [];
|
|
615
|
+
constructor(tokens) {
|
|
616
|
+
this.tokens = tokens;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Parse tokens into an AST
|
|
620
|
+
*/
|
|
621
|
+
parse() {
|
|
622
|
+
try {
|
|
623
|
+
const program = this.parseProgram();
|
|
624
|
+
return { program, diagnostics: this.diagnostics };
|
|
625
|
+
} catch (e) {
|
|
626
|
+
return { program: null, diagnostics: this.diagnostics };
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
// ============ Program Structure ============
|
|
630
|
+
parseProgram() {
|
|
631
|
+
const start = this.peek().location;
|
|
632
|
+
const imports = [];
|
|
633
|
+
while (this.check("IMPORT")) {
|
|
634
|
+
imports.push(this.parseImport());
|
|
635
|
+
}
|
|
636
|
+
const domain = this.parseDomain();
|
|
637
|
+
return {
|
|
638
|
+
kind: "program",
|
|
639
|
+
imports,
|
|
640
|
+
domain,
|
|
641
|
+
location: mergeLocations(start, domain.location)
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
parseImport() {
|
|
645
|
+
const start = this.consume("IMPORT", "Expected 'import'").location;
|
|
646
|
+
this.consume("LBRACE", "Expected '{' after 'import'");
|
|
647
|
+
const names = [];
|
|
648
|
+
do {
|
|
649
|
+
names.push(this.consume("IDENTIFIER", "Expected identifier").lexeme);
|
|
650
|
+
} while (this.match("COMMA"));
|
|
651
|
+
this.consume("RBRACE", "Expected '}' after import names");
|
|
652
|
+
this.consume("FROM", "Expected 'from' after import names");
|
|
653
|
+
const fromToken = this.consume("STRING", "Expected string after 'from'");
|
|
654
|
+
return {
|
|
655
|
+
kind: "import",
|
|
656
|
+
names,
|
|
657
|
+
from: fromToken.value,
|
|
658
|
+
location: mergeLocations(start, fromToken.location)
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
parseDomain() {
|
|
662
|
+
const start = this.consume("DOMAIN", "Expected 'domain'").location;
|
|
663
|
+
const name = this.consume("IDENTIFIER", "Expected domain name").lexeme;
|
|
664
|
+
this.consume("LBRACE", "Expected '{' after domain name");
|
|
665
|
+
const types = [];
|
|
666
|
+
while (this.check("TYPE") && !this.isAtEnd()) {
|
|
667
|
+
types.push(this.parseTypeDecl());
|
|
668
|
+
}
|
|
669
|
+
const members = [];
|
|
670
|
+
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
671
|
+
if (this.check("TYPE")) {
|
|
672
|
+
types.push(this.parseTypeDecl());
|
|
673
|
+
} else {
|
|
674
|
+
const member = this.parseDomainMember();
|
|
675
|
+
if (member) members.push(member);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
const end = this.consume("RBRACE", "Expected '}' to close domain").location;
|
|
679
|
+
return {
|
|
680
|
+
kind: "domain",
|
|
681
|
+
name,
|
|
682
|
+
types,
|
|
683
|
+
members,
|
|
684
|
+
location: mergeLocations(start, end)
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* v0.3.3: Parse type declaration
|
|
689
|
+
* Syntax: type Name = TypeExpr
|
|
690
|
+
*/
|
|
691
|
+
parseTypeDecl() {
|
|
692
|
+
const start = this.consume("TYPE", "Expected 'type'").location;
|
|
693
|
+
const name = this.consume("IDENTIFIER", "Expected type name").lexeme;
|
|
694
|
+
this.consume("EQ", "Expected '=' after type name");
|
|
695
|
+
const typeExpr = this.parseTypeExpr();
|
|
696
|
+
return {
|
|
697
|
+
kind: "typeDecl",
|
|
698
|
+
name,
|
|
699
|
+
typeExpr,
|
|
700
|
+
location: mergeLocations(start, typeExpr.location)
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
parseDomainMember() {
|
|
704
|
+
if (this.check("STATE")) return this.parseState();
|
|
705
|
+
if (this.check("COMPUTED")) return this.parseComputed();
|
|
706
|
+
if (this.check("ACTION")) return this.parseAction();
|
|
707
|
+
this.error(`Unexpected token '${this.peek().lexeme}'. Expected 'state', 'computed', or 'action'.`);
|
|
708
|
+
this.advance();
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
// ============ State ============
|
|
712
|
+
parseState() {
|
|
713
|
+
const start = this.consume("STATE", "Expected 'state'").location;
|
|
714
|
+
this.consume("LBRACE", "Expected '{' after 'state'");
|
|
715
|
+
const fields = [];
|
|
716
|
+
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
717
|
+
fields.push(this.parseStateField());
|
|
718
|
+
}
|
|
719
|
+
const end = this.consume("RBRACE", "Expected '}' to close state block").location;
|
|
720
|
+
return {
|
|
721
|
+
kind: "state",
|
|
722
|
+
fields,
|
|
723
|
+
location: mergeLocations(start, end)
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
parseStateField() {
|
|
727
|
+
const nameToken = this.consume("IDENTIFIER", "Expected field name");
|
|
728
|
+
this.consume("COLON", "Expected ':' after field name");
|
|
729
|
+
const typeExpr = this.parseTypeExpr();
|
|
730
|
+
let initializer;
|
|
731
|
+
if (this.match("EQ")) {
|
|
732
|
+
initializer = this.parseExpression();
|
|
733
|
+
}
|
|
734
|
+
return {
|
|
735
|
+
kind: "stateField",
|
|
736
|
+
name: nameToken.lexeme,
|
|
737
|
+
typeExpr,
|
|
738
|
+
initializer,
|
|
739
|
+
location: mergeLocations(
|
|
740
|
+
nameToken.location,
|
|
741
|
+
initializer?.location ?? typeExpr.location
|
|
742
|
+
)
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
// ============ Computed ============
|
|
746
|
+
parseComputed() {
|
|
747
|
+
const start = this.consume("COMPUTED", "Expected 'computed'").location;
|
|
748
|
+
const name = this.consume("IDENTIFIER", "Expected computed name").lexeme;
|
|
749
|
+
this.consume("EQ", "Expected '=' after computed name");
|
|
750
|
+
const expression = this.parseExpression();
|
|
751
|
+
return {
|
|
752
|
+
kind: "computed",
|
|
753
|
+
name,
|
|
754
|
+
expression,
|
|
755
|
+
location: mergeLocations(start, expression.location)
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
// ============ Action ============
|
|
759
|
+
parseAction() {
|
|
760
|
+
const start = this.consume("ACTION", "Expected 'action'").location;
|
|
761
|
+
const name = this.consume("IDENTIFIER", "Expected action name").lexeme;
|
|
762
|
+
this.consume("LPAREN", "Expected '(' after action name");
|
|
763
|
+
const params = [];
|
|
764
|
+
if (!this.check("RPAREN")) {
|
|
765
|
+
do {
|
|
766
|
+
params.push(this.parseParam());
|
|
767
|
+
} while (this.match("COMMA"));
|
|
768
|
+
}
|
|
769
|
+
this.consume("RPAREN", "Expected ')' after parameters");
|
|
770
|
+
let available;
|
|
771
|
+
if (this.match("AVAILABLE")) {
|
|
772
|
+
this.consume("WHEN", "Expected 'when' after 'available'");
|
|
773
|
+
available = this.parseExpression();
|
|
774
|
+
}
|
|
775
|
+
this.consume("LBRACE", "Expected '{' to start action body");
|
|
776
|
+
const body = [];
|
|
777
|
+
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
778
|
+
const stmt = this.parseGuardedStmt();
|
|
779
|
+
if (stmt) body.push(stmt);
|
|
780
|
+
}
|
|
781
|
+
const end = this.consume("RBRACE", "Expected '}' to close action").location;
|
|
782
|
+
return {
|
|
783
|
+
kind: "action",
|
|
784
|
+
name,
|
|
785
|
+
params,
|
|
786
|
+
available,
|
|
787
|
+
body,
|
|
788
|
+
location: mergeLocations(start, end)
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
parseParam() {
|
|
792
|
+
const nameToken = this.consume("IDENTIFIER", "Expected parameter name");
|
|
793
|
+
this.consume("COLON", "Expected ':' after parameter name");
|
|
794
|
+
const typeExpr = this.parseTypeExpr();
|
|
795
|
+
return {
|
|
796
|
+
kind: "param",
|
|
797
|
+
name: nameToken.lexeme,
|
|
798
|
+
typeExpr,
|
|
799
|
+
location: mergeLocations(nameToken.location, typeExpr.location)
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
// ============ Statements ============
|
|
803
|
+
parseGuardedStmt() {
|
|
804
|
+
if (this.check("WHEN")) return this.parseWhenStmt();
|
|
805
|
+
if (this.check("ONCE")) return this.parseOnceStmt();
|
|
806
|
+
if (this.isOnceIntentContext()) return this.parseOnceIntentStmt();
|
|
807
|
+
const token = this.peek();
|
|
808
|
+
if (token.kind === "PATCH" || token.lexeme === "patch" || token.kind === "EFFECT" || token.lexeme === "effect" || token.kind === "FAIL" || token.lexeme === "fail" || token.kind === "STOP" || token.lexeme === "stop") {
|
|
809
|
+
this.error(
|
|
810
|
+
`'${token.lexeme}' must be inside a guard block (when, once, or onceIntent). Wrap it: when true { ${token.lexeme} ... }`
|
|
811
|
+
);
|
|
812
|
+
this.skipToRecoveryPoint();
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
815
|
+
this.error(`Unexpected token '${token.lexeme}'. Expected 'when', 'once', or 'onceIntent'.`);
|
|
816
|
+
this.advance();
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Skip tokens until we reach a recovery point (closing brace, guard keyword, or EOF).
|
|
821
|
+
*/
|
|
822
|
+
skipToRecoveryPoint() {
|
|
823
|
+
let braceDepth = 0;
|
|
824
|
+
while (!this.isAtEnd()) {
|
|
825
|
+
const t = this.peek();
|
|
826
|
+
if (t.kind === "LBRACE") {
|
|
827
|
+
braceDepth++;
|
|
828
|
+
this.advance();
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
if (t.kind === "RBRACE") {
|
|
832
|
+
if (braceDepth === 0) return;
|
|
833
|
+
braceDepth--;
|
|
834
|
+
this.advance();
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
if (braceDepth === 0 && (t.kind === "WHEN" || t.kind === "ONCE" || t.lexeme === "onceIntent")) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
this.advance();
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
parseWhenStmt() {
|
|
844
|
+
const start = this.consume("WHEN", "Expected 'when'").location;
|
|
845
|
+
const condition = this.parseExpression();
|
|
846
|
+
this.consume("LBRACE", "Expected '{' after when condition");
|
|
847
|
+
const body = [];
|
|
848
|
+
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
849
|
+
const stmt = this.parseInnerStmt();
|
|
850
|
+
if (stmt) body.push(stmt);
|
|
851
|
+
}
|
|
852
|
+
const end = this.consume("RBRACE", "Expected '}' to close when block").location;
|
|
853
|
+
return {
|
|
854
|
+
kind: "when",
|
|
855
|
+
condition,
|
|
856
|
+
body,
|
|
857
|
+
location: mergeLocations(start, end)
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
parseOnceStmt() {
|
|
861
|
+
const start = this.consume("ONCE", "Expected 'once'").location;
|
|
862
|
+
this.consume("LPAREN", "Expected '(' after 'once'");
|
|
863
|
+
const marker = this.parsePath();
|
|
864
|
+
this.consume("RPAREN", "Expected ')' after marker");
|
|
865
|
+
let condition;
|
|
866
|
+
if (this.match("WHEN")) {
|
|
867
|
+
condition = this.parseExpression();
|
|
868
|
+
}
|
|
869
|
+
this.consume("LBRACE", "Expected '{' to start once block");
|
|
870
|
+
const body = [];
|
|
871
|
+
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
872
|
+
const stmt = this.parseInnerStmt();
|
|
873
|
+
if (stmt) body.push(stmt);
|
|
874
|
+
}
|
|
875
|
+
const end = this.consume("RBRACE", "Expected '}' to close once block").location;
|
|
876
|
+
return {
|
|
877
|
+
kind: "once",
|
|
878
|
+
marker,
|
|
879
|
+
condition,
|
|
880
|
+
body,
|
|
881
|
+
location: mergeLocations(start, end)
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
parseOnceIntentStmt() {
|
|
885
|
+
const startToken = this.consume("IDENTIFIER", "Expected 'onceIntent'");
|
|
886
|
+
let condition;
|
|
887
|
+
if (this.match("WHEN")) {
|
|
888
|
+
condition = this.parseExpression();
|
|
889
|
+
}
|
|
890
|
+
this.consume("LBRACE", "Expected '{' to start onceIntent block");
|
|
891
|
+
const body = [];
|
|
892
|
+
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
893
|
+
const stmt = this.parseInnerStmt();
|
|
894
|
+
if (stmt) body.push(stmt);
|
|
895
|
+
}
|
|
896
|
+
const end = this.consume("RBRACE", "Expected '}' to close onceIntent block").location;
|
|
897
|
+
return {
|
|
898
|
+
kind: "onceIntent",
|
|
899
|
+
condition,
|
|
900
|
+
body,
|
|
901
|
+
location: mergeLocations(startToken.location, end)
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
parseInnerStmt() {
|
|
905
|
+
if (this.check("PATCH")) return this.parsePatchStmt();
|
|
906
|
+
if (this.check("EFFECT")) return this.parseEffectStmt();
|
|
907
|
+
if (this.check("WHEN")) return this.parseWhenStmt();
|
|
908
|
+
if (this.check("ONCE")) return this.parseOnceStmt();
|
|
909
|
+
if (this.isOnceIntentContext()) return this.parseOnceIntentStmt();
|
|
910
|
+
if (this.check("FAIL")) return this.parseFailStmt();
|
|
911
|
+
if (this.check("STOP")) return this.parseStopStmt();
|
|
912
|
+
this.error(`Unexpected token '${this.peek().lexeme}'. Expected 'patch', 'effect', 'when', 'once', 'onceIntent', 'fail', or 'stop'.`);
|
|
913
|
+
this.advance();
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
parsePatchStmt() {
|
|
917
|
+
const start = this.consume("PATCH", "Expected 'patch'").location;
|
|
918
|
+
const path = this.parsePath();
|
|
919
|
+
let op;
|
|
920
|
+
let value;
|
|
921
|
+
let end;
|
|
922
|
+
if (this.match("UNSET")) {
|
|
923
|
+
op = "unset";
|
|
924
|
+
end = this.previous().location;
|
|
925
|
+
} else if (this.match("MERGE")) {
|
|
926
|
+
op = "merge";
|
|
927
|
+
value = this.parseExpression();
|
|
928
|
+
end = value.location;
|
|
929
|
+
} else {
|
|
930
|
+
this.consume("EQ", "Expected '=', 'unset', or 'merge' after path");
|
|
931
|
+
op = "set";
|
|
932
|
+
value = this.parseExpression();
|
|
933
|
+
end = value.location;
|
|
934
|
+
}
|
|
935
|
+
return {
|
|
936
|
+
kind: "patch",
|
|
937
|
+
path,
|
|
938
|
+
op,
|
|
939
|
+
value,
|
|
940
|
+
location: mergeLocations(start, end)
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
parseEffectStmt() {
|
|
944
|
+
const start = this.consume("EFFECT", "Expected 'effect'").location;
|
|
945
|
+
let effectType = this.consume("IDENTIFIER", "Expected effect type").lexeme;
|
|
946
|
+
while (this.match("DOT")) {
|
|
947
|
+
effectType += "." + this.consume("IDENTIFIER", "Expected identifier after '.'").lexeme;
|
|
948
|
+
}
|
|
949
|
+
this.consume("LPAREN", "Expected '(' after effect type");
|
|
950
|
+
this.consume("LBRACE", "Expected '{' for effect arguments");
|
|
951
|
+
const args = [];
|
|
952
|
+
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
953
|
+
args.push(this.parseEffectArg());
|
|
954
|
+
this.match("COMMA");
|
|
955
|
+
}
|
|
956
|
+
this.consume("RBRACE", "Expected '}' after effect arguments");
|
|
957
|
+
const end = this.consume("RPAREN", "Expected ')' to close effect").location;
|
|
958
|
+
return {
|
|
959
|
+
kind: "effect",
|
|
960
|
+
effectType,
|
|
961
|
+
args,
|
|
962
|
+
location: mergeLocations(start, end)
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
parseEffectArg() {
|
|
966
|
+
const nameToken = this.match("IDENTIFIER", "FAIL") ? this.previous() : this.consume("IDENTIFIER", "Expected argument name");
|
|
967
|
+
this.consume("COLON", "Expected ':' after argument name");
|
|
968
|
+
const isPath = ["into", "pass", "fail"].includes(nameToken.lexeme);
|
|
969
|
+
const value = isPath ? this.parsePath() : this.parseExpression();
|
|
970
|
+
return {
|
|
971
|
+
kind: "effectArg",
|
|
972
|
+
name: nameToken.lexeme,
|
|
973
|
+
value,
|
|
974
|
+
isPath,
|
|
975
|
+
location: mergeLocations(nameToken.location, value.location)
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* v0.3.2: Parse fail statement
|
|
980
|
+
* FailStmt ::= 'fail' StringLiteral ('with' Expr)?
|
|
981
|
+
*/
|
|
982
|
+
parseFailStmt() {
|
|
983
|
+
const start = this.consume("FAIL", "Expected 'fail'").location;
|
|
984
|
+
const codeToken = this.consume("STRING", "Expected error code string after 'fail'");
|
|
985
|
+
const code = codeToken.value;
|
|
986
|
+
let message;
|
|
987
|
+
let end = codeToken.location;
|
|
988
|
+
if (this.match("WITH")) {
|
|
989
|
+
message = this.parseExpression();
|
|
990
|
+
end = message.location;
|
|
991
|
+
}
|
|
992
|
+
return {
|
|
993
|
+
kind: "fail",
|
|
994
|
+
code,
|
|
995
|
+
message,
|
|
996
|
+
location: mergeLocations(start, end)
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* v0.3.2: Parse stop statement
|
|
1001
|
+
* StopStmt ::= 'stop' StringLiteral
|
|
1002
|
+
*/
|
|
1003
|
+
parseStopStmt() {
|
|
1004
|
+
const start = this.consume("STOP", "Expected 'stop'").location;
|
|
1005
|
+
const reasonToken = this.consume("STRING", "Expected reason string after 'stop'");
|
|
1006
|
+
const reason = reasonToken.value;
|
|
1007
|
+
return {
|
|
1008
|
+
kind: "stop",
|
|
1009
|
+
reason,
|
|
1010
|
+
location: mergeLocations(start, reasonToken.location)
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
// ============ Types ============
|
|
1014
|
+
parseTypeExpr() {
|
|
1015
|
+
let type = this.parseBaseType();
|
|
1016
|
+
if (this.check("PIPE")) {
|
|
1017
|
+
const types = [type];
|
|
1018
|
+
while (this.match("PIPE")) {
|
|
1019
|
+
types.push(this.parseBaseType());
|
|
1020
|
+
}
|
|
1021
|
+
type = {
|
|
1022
|
+
kind: "unionType",
|
|
1023
|
+
types,
|
|
1024
|
+
location: mergeLocations(types[0].location, types[types.length - 1].location)
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
return type;
|
|
1028
|
+
}
|
|
1029
|
+
parseBaseType() {
|
|
1030
|
+
if (this.check("LBRACE")) {
|
|
1031
|
+
return this.parseObjectType();
|
|
1032
|
+
}
|
|
1033
|
+
if (this.check("STRING")) {
|
|
1034
|
+
const token = this.advance();
|
|
1035
|
+
return {
|
|
1036
|
+
kind: "literalType",
|
|
1037
|
+
value: token.value,
|
|
1038
|
+
location: token.location
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
if (this.check("NUMBER")) {
|
|
1042
|
+
const token = this.advance();
|
|
1043
|
+
return {
|
|
1044
|
+
kind: "literalType",
|
|
1045
|
+
value: token.value,
|
|
1046
|
+
location: token.location
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
if (this.check("TRUE") || this.check("FALSE")) {
|
|
1050
|
+
const token = this.advance();
|
|
1051
|
+
return {
|
|
1052
|
+
kind: "literalType",
|
|
1053
|
+
value: token.kind === "TRUE",
|
|
1054
|
+
location: token.location
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
if (this.check("NULL")) {
|
|
1058
|
+
const token = this.advance();
|
|
1059
|
+
return {
|
|
1060
|
+
kind: "literalType",
|
|
1061
|
+
value: null,
|
|
1062
|
+
location: token.location
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
const nameToken = this.consume("IDENTIFIER", "Expected type name");
|
|
1066
|
+
if (this.match("LT")) {
|
|
1067
|
+
if (nameToken.lexeme === "Array") {
|
|
1068
|
+
const elementType = this.parseTypeExpr();
|
|
1069
|
+
const end = this.consume("GT", "Expected '>' after array element type").location;
|
|
1070
|
+
return {
|
|
1071
|
+
kind: "arrayType",
|
|
1072
|
+
elementType,
|
|
1073
|
+
location: mergeLocations(nameToken.location, end)
|
|
1074
|
+
};
|
|
1075
|
+
} else if (nameToken.lexeme === "Record") {
|
|
1076
|
+
const keyType = this.parseTypeExpr();
|
|
1077
|
+
this.consume("COMMA", "Expected ',' between Record type parameters");
|
|
1078
|
+
const valueType = this.parseTypeExpr();
|
|
1079
|
+
const end = this.consume("GT", "Expected '>' after Record value type").location;
|
|
1080
|
+
return {
|
|
1081
|
+
kind: "recordType",
|
|
1082
|
+
keyType,
|
|
1083
|
+
valueType,
|
|
1084
|
+
location: mergeLocations(nameToken.location, end)
|
|
1085
|
+
};
|
|
1086
|
+
} else {
|
|
1087
|
+
this.error(`Unknown generic type '${nameToken.lexeme}'`);
|
|
1088
|
+
while (!this.check("GT") && !this.isAtEnd()) this.advance();
|
|
1089
|
+
this.match("GT");
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
return {
|
|
1093
|
+
kind: "simpleType",
|
|
1094
|
+
name: nameToken.lexeme,
|
|
1095
|
+
location: nameToken.location
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* v0.3.3: Parse object type
|
|
1100
|
+
* Syntax: { field: Type, field?: Type, ... }
|
|
1101
|
+
*/
|
|
1102
|
+
parseObjectType() {
|
|
1103
|
+
const start = this.consume("LBRACE", "Expected '{'").location;
|
|
1104
|
+
const fields = [];
|
|
1105
|
+
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
1106
|
+
const nameToken = this.consume("IDENTIFIER", "Expected field name");
|
|
1107
|
+
const optional = this.match("QUESTION");
|
|
1108
|
+
this.consume("COLON", "Expected ':' after field name");
|
|
1109
|
+
const typeExpr = this.parseTypeExpr();
|
|
1110
|
+
fields.push({
|
|
1111
|
+
kind: "typeField",
|
|
1112
|
+
name: nameToken.lexeme,
|
|
1113
|
+
typeExpr,
|
|
1114
|
+
optional,
|
|
1115
|
+
location: mergeLocations(nameToken.location, typeExpr.location)
|
|
1116
|
+
});
|
|
1117
|
+
this.match("COMMA");
|
|
1118
|
+
}
|
|
1119
|
+
const end = this.consume("RBRACE", "Expected '}' to close object type").location;
|
|
1120
|
+
return {
|
|
1121
|
+
kind: "objectType",
|
|
1122
|
+
fields,
|
|
1123
|
+
location: mergeLocations(start, end)
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
// ============ Expressions (Pratt Parser) ============
|
|
1127
|
+
parseExpression(minPrecedence = 0 /* NONE */) {
|
|
1128
|
+
let left = this.parsePrimary();
|
|
1129
|
+
while (true) {
|
|
1130
|
+
const precedence = getBinaryPrecedence(this.peek().kind);
|
|
1131
|
+
if (precedence <= minPrecedence) break;
|
|
1132
|
+
if (this.peek().kind === "QUESTION") {
|
|
1133
|
+
left = this.parseTernary(left);
|
|
1134
|
+
continue;
|
|
1135
|
+
}
|
|
1136
|
+
const op = tokenToBinaryOp(this.peek().kind);
|
|
1137
|
+
if (!op) break;
|
|
1138
|
+
this.advance();
|
|
1139
|
+
const nextPrecedence = isRightAssociative(this.previous().kind) ? precedence - 1 : precedence;
|
|
1140
|
+
const right = this.parseExpression(nextPrecedence);
|
|
1141
|
+
left = {
|
|
1142
|
+
kind: "binary",
|
|
1143
|
+
operator: op,
|
|
1144
|
+
left,
|
|
1145
|
+
right,
|
|
1146
|
+
location: mergeLocations(left.location, right.location)
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
return left;
|
|
1150
|
+
}
|
|
1151
|
+
parseTernary(condition) {
|
|
1152
|
+
this.consume("QUESTION", "Expected '?'");
|
|
1153
|
+
const consequent = this.parseExpression();
|
|
1154
|
+
this.consume("COLON", "Expected ':' in ternary expression");
|
|
1155
|
+
const alternate = this.parseExpression(1 /* TERNARY */ - 1);
|
|
1156
|
+
return {
|
|
1157
|
+
kind: "ternary",
|
|
1158
|
+
condition,
|
|
1159
|
+
consequent,
|
|
1160
|
+
alternate,
|
|
1161
|
+
location: mergeLocations(condition.location, alternate.location)
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
parsePrimary() {
|
|
1165
|
+
if (this.check("BANG") || this.check("MINUS") && this.isUnaryContext()) {
|
|
1166
|
+
const op = this.advance();
|
|
1167
|
+
const operand = this.parsePrimary();
|
|
1168
|
+
return {
|
|
1169
|
+
kind: "unary",
|
|
1170
|
+
operator: op.kind === "BANG" ? "!" : "-",
|
|
1171
|
+
operand,
|
|
1172
|
+
location: mergeLocations(op.location, operand.location)
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
if (this.match("LPAREN")) {
|
|
1176
|
+
const expr = this.parseExpression();
|
|
1177
|
+
this.consume("RPAREN", "Expected ')' after expression");
|
|
1178
|
+
return expr;
|
|
1179
|
+
}
|
|
1180
|
+
if (this.check("LBRACE")) {
|
|
1181
|
+
return this.parseObjectLiteral();
|
|
1182
|
+
}
|
|
1183
|
+
if (this.check("LBRACKET")) {
|
|
1184
|
+
return this.parseArrayLiteral();
|
|
1185
|
+
}
|
|
1186
|
+
if (this.check("NUMBER")) {
|
|
1187
|
+
const token = this.advance();
|
|
1188
|
+
return {
|
|
1189
|
+
kind: "literal",
|
|
1190
|
+
value: token.value,
|
|
1191
|
+
literalType: "number",
|
|
1192
|
+
location: token.location
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
if (this.check("STRING")) {
|
|
1196
|
+
const token = this.advance();
|
|
1197
|
+
return {
|
|
1198
|
+
kind: "literal",
|
|
1199
|
+
value: token.value,
|
|
1200
|
+
literalType: "string",
|
|
1201
|
+
location: token.location
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
if (this.check("TRUE") || this.check("FALSE")) {
|
|
1205
|
+
const token = this.advance();
|
|
1206
|
+
return {
|
|
1207
|
+
kind: "literal",
|
|
1208
|
+
value: token.kind === "TRUE",
|
|
1209
|
+
literalType: "boolean",
|
|
1210
|
+
location: token.location
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
if (this.check("NULL")) {
|
|
1214
|
+
const token = this.advance();
|
|
1215
|
+
return {
|
|
1216
|
+
kind: "literal",
|
|
1217
|
+
value: null,
|
|
1218
|
+
literalType: "null",
|
|
1219
|
+
location: token.location
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
if (this.check("SYSTEM_IDENT")) {
|
|
1223
|
+
const token = this.advance();
|
|
1224
|
+
const path = token.lexeme.slice(1).split(".");
|
|
1225
|
+
return this.parsePostfix({
|
|
1226
|
+
kind: "systemIdent",
|
|
1227
|
+
path,
|
|
1228
|
+
location: token.location
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
if (this.check("ITEM")) {
|
|
1232
|
+
const token = this.advance();
|
|
1233
|
+
return this.parsePostfix({
|
|
1234
|
+
kind: "iterationVar",
|
|
1235
|
+
name: "item",
|
|
1236
|
+
location: token.location
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
if (this.check("MERGE") && this.peekNext()?.kind === "LPAREN") {
|
|
1240
|
+
const token = this.advance();
|
|
1241
|
+
return this.parseFunctionCall(token);
|
|
1242
|
+
}
|
|
1243
|
+
if (this.check("IDENTIFIER")) {
|
|
1244
|
+
const token = this.advance();
|
|
1245
|
+
if (this.check("LPAREN")) {
|
|
1246
|
+
return this.parseFunctionCall(token);
|
|
1247
|
+
}
|
|
1248
|
+
return this.parsePostfix({
|
|
1249
|
+
kind: "identifier",
|
|
1250
|
+
name: token.lexeme,
|
|
1251
|
+
location: token.location
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
this.error(`Unexpected token '${this.peek().lexeme}'`);
|
|
1255
|
+
return {
|
|
1256
|
+
kind: "literal",
|
|
1257
|
+
value: null,
|
|
1258
|
+
literalType: "null",
|
|
1259
|
+
location: this.peek().location
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
parseFunctionCall(nameToken) {
|
|
1263
|
+
this.consume("LPAREN", "Expected '(' for function call");
|
|
1264
|
+
const args = [];
|
|
1265
|
+
if (!this.check("RPAREN")) {
|
|
1266
|
+
do {
|
|
1267
|
+
args.push(this.parseExpression());
|
|
1268
|
+
} while (this.match("COMMA"));
|
|
1269
|
+
}
|
|
1270
|
+
const end = this.consume("RPAREN", "Expected ')' after arguments").location;
|
|
1271
|
+
return this.parsePostfix({
|
|
1272
|
+
kind: "functionCall",
|
|
1273
|
+
name: nameToken.lexeme,
|
|
1274
|
+
args,
|
|
1275
|
+
location: mergeLocations(nameToken.location, end)
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
parsePostfix(expr) {
|
|
1279
|
+
while (true) {
|
|
1280
|
+
if (this.match("DOT")) {
|
|
1281
|
+
const prop = this.consume("IDENTIFIER", "Expected property name after '.'");
|
|
1282
|
+
expr = {
|
|
1283
|
+
kind: "propertyAccess",
|
|
1284
|
+
object: expr,
|
|
1285
|
+
property: prop.lexeme,
|
|
1286
|
+
location: mergeLocations(expr.location, prop.location)
|
|
1287
|
+
};
|
|
1288
|
+
} else if (this.match("LBRACKET")) {
|
|
1289
|
+
const index = this.parseExpression();
|
|
1290
|
+
const end = this.consume("RBRACKET", "Expected ']' after index").location;
|
|
1291
|
+
expr = {
|
|
1292
|
+
kind: "indexAccess",
|
|
1293
|
+
object: expr,
|
|
1294
|
+
index,
|
|
1295
|
+
location: mergeLocations(expr.location, end)
|
|
1296
|
+
};
|
|
1297
|
+
} else {
|
|
1298
|
+
break;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
return expr;
|
|
1302
|
+
}
|
|
1303
|
+
parseObjectLiteral() {
|
|
1304
|
+
const start = this.consume("LBRACE", "Expected '{'").location;
|
|
1305
|
+
const properties = [];
|
|
1306
|
+
while (!this.check("RBRACE") && !this.isAtEnd()) {
|
|
1307
|
+
const keyToken = this.consume("IDENTIFIER", "Expected property name");
|
|
1308
|
+
this.consume("COLON", "Expected ':' after property name");
|
|
1309
|
+
const value = this.parseExpression();
|
|
1310
|
+
properties.push({
|
|
1311
|
+
kind: "objectProperty",
|
|
1312
|
+
key: keyToken.lexeme,
|
|
1313
|
+
value,
|
|
1314
|
+
location: mergeLocations(keyToken.location, value.location)
|
|
1315
|
+
});
|
|
1316
|
+
if (!this.check("RBRACE")) {
|
|
1317
|
+
this.consume("COMMA", "Expected ',' between properties");
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
const end = this.consume("RBRACE", "Expected '}' to close object").location;
|
|
1321
|
+
return {
|
|
1322
|
+
kind: "objectLiteral",
|
|
1323
|
+
properties,
|
|
1324
|
+
location: mergeLocations(start, end)
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
parseArrayLiteral() {
|
|
1328
|
+
const start = this.consume("LBRACKET", "Expected '['").location;
|
|
1329
|
+
const elements = [];
|
|
1330
|
+
while (!this.check("RBRACKET") && !this.isAtEnd()) {
|
|
1331
|
+
elements.push(this.parseExpression());
|
|
1332
|
+
if (!this.check("RBRACKET")) {
|
|
1333
|
+
this.consume("COMMA", "Expected ',' between elements");
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
const end = this.consume("RBRACKET", "Expected ']' to close array").location;
|
|
1337
|
+
return {
|
|
1338
|
+
kind: "arrayLiteral",
|
|
1339
|
+
elements,
|
|
1340
|
+
location: mergeLocations(start, end)
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
// ============ Path ============
|
|
1344
|
+
parsePath() {
|
|
1345
|
+
const segments = [];
|
|
1346
|
+
const start = this.peek().location;
|
|
1347
|
+
const first = this.consume("IDENTIFIER", "Expected identifier");
|
|
1348
|
+
segments.push({
|
|
1349
|
+
kind: "propertySegment",
|
|
1350
|
+
name: first.lexeme,
|
|
1351
|
+
location: first.location
|
|
1352
|
+
});
|
|
1353
|
+
while (true) {
|
|
1354
|
+
if (this.match("DOT")) {
|
|
1355
|
+
const prop = this.consume("IDENTIFIER", "Expected property name");
|
|
1356
|
+
segments.push({
|
|
1357
|
+
kind: "propertySegment",
|
|
1358
|
+
name: prop.lexeme,
|
|
1359
|
+
location: prop.location
|
|
1360
|
+
});
|
|
1361
|
+
} else if (this.match("LBRACKET")) {
|
|
1362
|
+
const index = this.parseExpression();
|
|
1363
|
+
const end = this.consume("RBRACKET", "Expected ']'").location;
|
|
1364
|
+
segments.push({
|
|
1365
|
+
kind: "indexSegment",
|
|
1366
|
+
index,
|
|
1367
|
+
location: mergeLocations(index.location, end)
|
|
1368
|
+
});
|
|
1369
|
+
} else {
|
|
1370
|
+
break;
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
const last = segments[segments.length - 1];
|
|
1374
|
+
return {
|
|
1375
|
+
kind: "path",
|
|
1376
|
+
segments,
|
|
1377
|
+
location: mergeLocations(start, last.location)
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
// ============ Helpers ============
|
|
1381
|
+
isUnaryContext() {
|
|
1382
|
+
if (this.current === 0) return true;
|
|
1383
|
+
const prev = this.previous();
|
|
1384
|
+
const unaryPrecedingTokens = [
|
|
1385
|
+
"LPAREN",
|
|
1386
|
+
"LBRACKET",
|
|
1387
|
+
"LBRACE",
|
|
1388
|
+
"COMMA",
|
|
1389
|
+
"COLON",
|
|
1390
|
+
"EQ",
|
|
1391
|
+
"PLUS",
|
|
1392
|
+
"MINUS",
|
|
1393
|
+
"STAR",
|
|
1394
|
+
"SLASH",
|
|
1395
|
+
"PERCENT",
|
|
1396
|
+
"EQ_EQ",
|
|
1397
|
+
"BANG_EQ",
|
|
1398
|
+
"LT",
|
|
1399
|
+
"LT_EQ",
|
|
1400
|
+
"GT",
|
|
1401
|
+
"GT_EQ",
|
|
1402
|
+
"AMP_AMP",
|
|
1403
|
+
"PIPE_PIPE",
|
|
1404
|
+
"BANG",
|
|
1405
|
+
"QUESTION",
|
|
1406
|
+
"QUESTION_QUESTION"
|
|
1407
|
+
];
|
|
1408
|
+
return unaryPrecedingTokens.includes(prev.kind);
|
|
1409
|
+
}
|
|
1410
|
+
peek() {
|
|
1411
|
+
return this.tokens[this.current];
|
|
1412
|
+
}
|
|
1413
|
+
peekNext() {
|
|
1414
|
+
if (this.current + 1 >= this.tokens.length) {
|
|
1415
|
+
return this.tokens[this.tokens.length - 1];
|
|
1416
|
+
}
|
|
1417
|
+
return this.tokens[this.current + 1];
|
|
1418
|
+
}
|
|
1419
|
+
previous() {
|
|
1420
|
+
return this.tokens[this.current - 1];
|
|
1421
|
+
}
|
|
1422
|
+
isAtEnd() {
|
|
1423
|
+
return this.peek().kind === "EOF";
|
|
1424
|
+
}
|
|
1425
|
+
advance() {
|
|
1426
|
+
if (!this.isAtEnd()) this.current++;
|
|
1427
|
+
return this.previous();
|
|
1428
|
+
}
|
|
1429
|
+
check(kind) {
|
|
1430
|
+
if (this.isAtEnd()) return false;
|
|
1431
|
+
return this.peek().kind === kind;
|
|
1432
|
+
}
|
|
1433
|
+
isOnceIntentContext() {
|
|
1434
|
+
if (!this.check("IDENTIFIER")) return false;
|
|
1435
|
+
const token = this.peek();
|
|
1436
|
+
if (token.lexeme !== "onceIntent") return false;
|
|
1437
|
+
const next = this.peekNext();
|
|
1438
|
+
return next.kind === "LBRACE" || next.kind === "WHEN";
|
|
1439
|
+
}
|
|
1440
|
+
match(...kinds) {
|
|
1441
|
+
for (const kind of kinds) {
|
|
1442
|
+
if (this.check(kind)) {
|
|
1443
|
+
this.advance();
|
|
1444
|
+
return true;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
return false;
|
|
1448
|
+
}
|
|
1449
|
+
consume(kind, message) {
|
|
1450
|
+
if (this.check(kind)) return this.advance();
|
|
1451
|
+
throw this.errorAtCurrent(message);
|
|
1452
|
+
}
|
|
1453
|
+
error(message) {
|
|
1454
|
+
this.diagnostics.push({
|
|
1455
|
+
severity: "error",
|
|
1456
|
+
code: "MEL_PARSER",
|
|
1457
|
+
message,
|
|
1458
|
+
location: this.previous().location
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
errorAtCurrent(message) {
|
|
1462
|
+
this.diagnostics.push({
|
|
1463
|
+
severity: "error",
|
|
1464
|
+
code: "MEL_PARSER",
|
|
1465
|
+
message,
|
|
1466
|
+
location: this.peek().location
|
|
1467
|
+
});
|
|
1468
|
+
return new Error(message);
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1471
|
+
function parse(tokens) {
|
|
1472
|
+
const parser = new Parser(tokens);
|
|
1473
|
+
return parser.parse();
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
// src/analyzer/scope.ts
|
|
1477
|
+
var Scope = class {
|
|
1478
|
+
parent;
|
|
1479
|
+
symbols = /* @__PURE__ */ new Map();
|
|
1480
|
+
kind;
|
|
1481
|
+
constructor(kind, parent = null) {
|
|
1482
|
+
this.kind = kind;
|
|
1483
|
+
this.parent = parent;
|
|
1484
|
+
}
|
|
1485
|
+
/**
|
|
1486
|
+
* Define a new symbol in this scope
|
|
1487
|
+
*/
|
|
1488
|
+
define(symbol) {
|
|
1489
|
+
this.symbols.set(symbol.name, symbol);
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* Look up a symbol, searching parent scopes if not found
|
|
1493
|
+
*/
|
|
1494
|
+
lookup(name) {
|
|
1495
|
+
const symbol = this.symbols.get(name);
|
|
1496
|
+
if (symbol) return symbol;
|
|
1497
|
+
return this.parent?.lookup(name);
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Check if a symbol is defined in this scope (not parent scopes)
|
|
1501
|
+
*/
|
|
1502
|
+
isDefined(name) {
|
|
1503
|
+
return this.symbols.has(name);
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
var ScopeAnalyzer = class {
|
|
1507
|
+
diagnostics = [];
|
|
1508
|
+
scopes = /* @__PURE__ */ new Map();
|
|
1509
|
+
currentScope = null;
|
|
1510
|
+
domainScope = null;
|
|
1511
|
+
/**
|
|
1512
|
+
* Analyze a MEL program
|
|
1513
|
+
*/
|
|
1514
|
+
analyze(program) {
|
|
1515
|
+
this.diagnostics = [];
|
|
1516
|
+
this.scopes = /* @__PURE__ */ new Map();
|
|
1517
|
+
this.currentScope = null;
|
|
1518
|
+
this.domainScope = null;
|
|
1519
|
+
this.analyzeDomain(program.domain);
|
|
1520
|
+
return {
|
|
1521
|
+
scopes: this.scopes,
|
|
1522
|
+
diagnostics: this.diagnostics
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
analyzeDomain(domain) {
|
|
1526
|
+
const scope = new Scope("domain");
|
|
1527
|
+
this.domainScope = scope;
|
|
1528
|
+
this.currentScope = scope;
|
|
1529
|
+
this.scopes.set("domain", scope);
|
|
1530
|
+
for (const member of domain.members) {
|
|
1531
|
+
if (member.kind === "state") {
|
|
1532
|
+
for (const field of member.fields) {
|
|
1533
|
+
this.defineSymbol({
|
|
1534
|
+
name: field.name,
|
|
1535
|
+
kind: "state",
|
|
1536
|
+
location: field.location
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
} else if (member.kind === "computed") {
|
|
1540
|
+
this.defineSymbol({
|
|
1541
|
+
name: member.name,
|
|
1542
|
+
kind: "computed",
|
|
1543
|
+
location: member.location
|
|
1544
|
+
});
|
|
1545
|
+
} else if (member.kind === "action") {
|
|
1546
|
+
this.defineSymbol({
|
|
1547
|
+
name: member.name,
|
|
1548
|
+
kind: "action",
|
|
1549
|
+
location: member.location
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
for (const member of domain.members) {
|
|
1554
|
+
if (member.kind === "computed") {
|
|
1555
|
+
this.analyzeComputedExpr(member);
|
|
1556
|
+
} else if (member.kind === "action") {
|
|
1557
|
+
this.analyzeAction(member);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
analyzeComputedExpr(computed) {
|
|
1562
|
+
this.analyzeExpr(computed.expression, "computed");
|
|
1563
|
+
}
|
|
1564
|
+
analyzeAction(action) {
|
|
1565
|
+
const scope = new Scope("action", this.domainScope);
|
|
1566
|
+
this.currentScope = scope;
|
|
1567
|
+
this.scopes.set(`action.${action.name}`, scope);
|
|
1568
|
+
for (const param of action.params) {
|
|
1569
|
+
scope.define({
|
|
1570
|
+
name: param.name,
|
|
1571
|
+
kind: "param",
|
|
1572
|
+
location: param.location
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
for (const stmt of action.body) {
|
|
1576
|
+
this.analyzeStmt(stmt);
|
|
1577
|
+
}
|
|
1578
|
+
this.currentScope = this.domainScope;
|
|
1579
|
+
}
|
|
1580
|
+
analyzeStmt(stmt) {
|
|
1581
|
+
switch (stmt.kind) {
|
|
1582
|
+
case "when":
|
|
1583
|
+
this.analyzeExpr(stmt.condition, "action");
|
|
1584
|
+
for (const inner of stmt.body) {
|
|
1585
|
+
this.analyzeStmt(inner);
|
|
1586
|
+
}
|
|
1587
|
+
break;
|
|
1588
|
+
case "onceIntent":
|
|
1589
|
+
if (stmt.condition) {
|
|
1590
|
+
this.analyzeExpr(stmt.condition, "action");
|
|
1591
|
+
}
|
|
1592
|
+
for (const inner of stmt.body) {
|
|
1593
|
+
this.analyzeStmt(inner);
|
|
1594
|
+
}
|
|
1595
|
+
break;
|
|
1596
|
+
case "once":
|
|
1597
|
+
this.validatePath(stmt.marker);
|
|
1598
|
+
if (stmt.condition) {
|
|
1599
|
+
this.analyzeExpr(stmt.condition, "action");
|
|
1600
|
+
}
|
|
1601
|
+
for (const inner of stmt.body) {
|
|
1602
|
+
this.analyzeStmt(inner);
|
|
1603
|
+
}
|
|
1604
|
+
break;
|
|
1605
|
+
case "patch":
|
|
1606
|
+
this.validatePath(stmt.path);
|
|
1607
|
+
if (stmt.value) {
|
|
1608
|
+
this.analyzeExpr(stmt.value, "action");
|
|
1609
|
+
}
|
|
1610
|
+
break;
|
|
1611
|
+
case "effect":
|
|
1612
|
+
for (const arg of stmt.args) {
|
|
1613
|
+
if (arg.isPath) {
|
|
1614
|
+
this.validatePath(arg.value);
|
|
1615
|
+
} else {
|
|
1616
|
+
this.analyzeExpr(arg.value, "action");
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
break;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
analyzeExpr(expr, context) {
|
|
1623
|
+
switch (expr.kind) {
|
|
1624
|
+
case "identifier":
|
|
1625
|
+
this.checkIdentifier(expr.name, expr.location, context);
|
|
1626
|
+
break;
|
|
1627
|
+
case "systemIdent":
|
|
1628
|
+
this.checkSystemIdent(expr.path, expr.location, context);
|
|
1629
|
+
break;
|
|
1630
|
+
case "propertyAccess":
|
|
1631
|
+
this.analyzeExpr(expr.object, context);
|
|
1632
|
+
break;
|
|
1633
|
+
case "indexAccess":
|
|
1634
|
+
this.analyzeExpr(expr.object, context);
|
|
1635
|
+
this.analyzeExpr(expr.index, context);
|
|
1636
|
+
break;
|
|
1637
|
+
case "functionCall":
|
|
1638
|
+
for (const arg of expr.args) {
|
|
1639
|
+
this.analyzeExpr(arg, context);
|
|
1640
|
+
}
|
|
1641
|
+
break;
|
|
1642
|
+
case "binary":
|
|
1643
|
+
this.analyzeExpr(expr.left, context);
|
|
1644
|
+
this.analyzeExpr(expr.right, context);
|
|
1645
|
+
break;
|
|
1646
|
+
case "unary":
|
|
1647
|
+
this.analyzeExpr(expr.operand, context);
|
|
1648
|
+
break;
|
|
1649
|
+
case "ternary":
|
|
1650
|
+
this.analyzeExpr(expr.condition, context);
|
|
1651
|
+
this.analyzeExpr(expr.consequent, context);
|
|
1652
|
+
this.analyzeExpr(expr.alternate, context);
|
|
1653
|
+
break;
|
|
1654
|
+
case "objectLiteral":
|
|
1655
|
+
for (const prop of expr.properties) {
|
|
1656
|
+
this.analyzeExpr(prop.value, context);
|
|
1657
|
+
}
|
|
1658
|
+
break;
|
|
1659
|
+
case "arrayLiteral":
|
|
1660
|
+
for (const elem of expr.elements) {
|
|
1661
|
+
this.analyzeExpr(elem, context);
|
|
1662
|
+
}
|
|
1663
|
+
break;
|
|
1664
|
+
case "iterationVar":
|
|
1665
|
+
break;
|
|
1666
|
+
case "literal":
|
|
1667
|
+
break;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
checkIdentifier(name, location, context) {
|
|
1671
|
+
const symbol = this.currentScope?.lookup(name);
|
|
1672
|
+
if (!symbol) {
|
|
1673
|
+
this.error(`Undefined identifier '${name}'`, location, "E_UNDEFINED");
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
if (context === "computed") {
|
|
1677
|
+
if (symbol.kind === "param") {
|
|
1678
|
+
this.error(
|
|
1679
|
+
`Cannot access parameter '${name}' in computed expression`,
|
|
1680
|
+
location,
|
|
1681
|
+
"E_INVALID_ACCESS"
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
checkSystemIdent(path, location, context) {
|
|
1687
|
+
const [namespace, ...rest] = path;
|
|
1688
|
+
if (namespace === "system" && context === "computed") {
|
|
1689
|
+
this.error(
|
|
1690
|
+
`Cannot use $system.* in computed expressions (non-deterministic)`,
|
|
1691
|
+
location,
|
|
1692
|
+
"E001"
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1695
|
+
if (namespace === "system") {
|
|
1696
|
+
const validKeys = ["uuid", "timestamp", "time.now", "random"];
|
|
1697
|
+
const key = rest.join(".");
|
|
1698
|
+
if (key && !validKeys.includes(key)) {
|
|
1699
|
+
this.error(
|
|
1700
|
+
`Invalid system value '$system.${key}'. Valid values: ${validKeys.join(", ")}`,
|
|
1701
|
+
location,
|
|
1702
|
+
"E003"
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
if (namespace === "meta") {
|
|
1707
|
+
const validKeys = ["intentId", "actionName", "timestamp"];
|
|
1708
|
+
const key = rest.join(".");
|
|
1709
|
+
if (key && !validKeys.includes(key)) {
|
|
1710
|
+
this.error(
|
|
1711
|
+
`Invalid meta value '$meta.${key}'. Valid values: ${validKeys.join(", ")}`,
|
|
1712
|
+
location,
|
|
1713
|
+
"E003"
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
validatePath(path) {
|
|
1719
|
+
if (!path || !path.segments) return;
|
|
1720
|
+
const first = path.segments[0];
|
|
1721
|
+
if (first?.kind === "propertySegment") {
|
|
1722
|
+
const symbol = this.currentScope?.lookup(first.name);
|
|
1723
|
+
if (!symbol) {
|
|
1724
|
+
this.error(
|
|
1725
|
+
`Undefined identifier '${first.name}' in path`,
|
|
1726
|
+
path.location,
|
|
1727
|
+
"E_UNDEFINED"
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
defineSymbol(symbol) {
|
|
1733
|
+
if (!this.currentScope) return;
|
|
1734
|
+
if (this.currentScope.isDefined(symbol.name)) {
|
|
1735
|
+
this.error(
|
|
1736
|
+
`Duplicate identifier '${symbol.name}'`,
|
|
1737
|
+
symbol.location,
|
|
1738
|
+
"E_DUPLICATE"
|
|
1739
|
+
);
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
this.currentScope.define(symbol);
|
|
1743
|
+
}
|
|
1744
|
+
error(message, location, code) {
|
|
1745
|
+
this.diagnostics.push({
|
|
1746
|
+
severity: "error",
|
|
1747
|
+
code,
|
|
1748
|
+
message,
|
|
1749
|
+
location
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1753
|
+
function analyzeScope(program) {
|
|
1754
|
+
const analyzer = new ScopeAnalyzer();
|
|
1755
|
+
return analyzer.analyze(program);
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
// src/diagnostics/types.ts
|
|
1759
|
+
function createError(code, message, location, options) {
|
|
1760
|
+
return {
|
|
1761
|
+
severity: "error",
|
|
1762
|
+
code,
|
|
1763
|
+
message,
|
|
1764
|
+
location,
|
|
1765
|
+
...options
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
function createWarning(code, message, location, options) {
|
|
1769
|
+
return {
|
|
1770
|
+
severity: "warning",
|
|
1771
|
+
code,
|
|
1772
|
+
message,
|
|
1773
|
+
location,
|
|
1774
|
+
...options
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
function createInfo(code, message, location) {
|
|
1778
|
+
return {
|
|
1779
|
+
severity: "info",
|
|
1780
|
+
code,
|
|
1781
|
+
message,
|
|
1782
|
+
location
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
function isError(diagnostic) {
|
|
1786
|
+
return diagnostic.severity === "error";
|
|
1787
|
+
}
|
|
1788
|
+
function hasErrors(diagnostics) {
|
|
1789
|
+
return diagnostics.some(isError);
|
|
1790
|
+
}
|
|
1791
|
+
function filterBySeverity(diagnostics, severity) {
|
|
1792
|
+
return diagnostics.filter((d) => d.severity === severity);
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// src/analyzer/validator.ts
|
|
1796
|
+
function createContext() {
|
|
1797
|
+
return {
|
|
1798
|
+
inAction: false,
|
|
1799
|
+
inGuard: false,
|
|
1800
|
+
guardDepth: 0,
|
|
1801
|
+
hasMarkerPatch: false,
|
|
1802
|
+
diagnostics: []
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
var SemanticValidator = class {
|
|
1806
|
+
ctx = createContext();
|
|
1807
|
+
/**
|
|
1808
|
+
* Validate a MEL program
|
|
1809
|
+
*/
|
|
1810
|
+
validate(program) {
|
|
1811
|
+
this.ctx = createContext();
|
|
1812
|
+
this.validateDomain(program.domain);
|
|
1813
|
+
return {
|
|
1814
|
+
valid: !this.ctx.diagnostics.some((d) => d.severity === "error"),
|
|
1815
|
+
diagnostics: this.ctx.diagnostics
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
validateDomain(domain) {
|
|
1819
|
+
if (domain.name.startsWith("__")) {
|
|
1820
|
+
this.error(
|
|
1821
|
+
"Domain name cannot start with '__' (reserved prefix)",
|
|
1822
|
+
domain.location,
|
|
1823
|
+
"E_RESERVED_NAME"
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
for (const member of domain.members) {
|
|
1827
|
+
switch (member.kind) {
|
|
1828
|
+
case "state":
|
|
1829
|
+
this.validateState(member);
|
|
1830
|
+
break;
|
|
1831
|
+
case "computed":
|
|
1832
|
+
this.validateExpr(member.expression, "computed");
|
|
1833
|
+
break;
|
|
1834
|
+
case "action":
|
|
1835
|
+
this.validateAction(member);
|
|
1836
|
+
break;
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* v0.3.3: Validate state fields for W012 (anonymous object types)
|
|
1842
|
+
*/
|
|
1843
|
+
validateState(state) {
|
|
1844
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1845
|
+
for (const field of state.fields) {
|
|
1846
|
+
const prev = seen.get(field.name);
|
|
1847
|
+
if (prev) {
|
|
1848
|
+
this.error(
|
|
1849
|
+
`Duplicate state field '${field.name}'. First declared at line ${prev.start.line}`,
|
|
1850
|
+
field.location,
|
|
1851
|
+
"E_DUPLICATE_FIELD"
|
|
1852
|
+
);
|
|
1853
|
+
} else {
|
|
1854
|
+
seen.set(field.name, field.location);
|
|
1855
|
+
}
|
|
1856
|
+
this.validateStateField(field);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* v0.3.3: Validate state field - check for anonymous object types (W012)
|
|
1861
|
+
*/
|
|
1862
|
+
validateStateField(field) {
|
|
1863
|
+
this.checkAnonymousObjectType(field.typeExpr, field.location);
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* v0.3.3: Check if a type expression contains anonymous object types
|
|
1867
|
+
* Issues W012 warning for inline object types in state fields
|
|
1868
|
+
*/
|
|
1869
|
+
checkAnonymousObjectType(typeExpr, fieldLocation) {
|
|
1870
|
+
switch (typeExpr.kind) {
|
|
1871
|
+
case "objectType":
|
|
1872
|
+
this.ctx.diagnostics.push(
|
|
1873
|
+
createWarning(
|
|
1874
|
+
"W012",
|
|
1875
|
+
"Anonymous object type in state field. Use a named type declaration instead: type MyType = { ... }",
|
|
1876
|
+
typeExpr.location,
|
|
1877
|
+
{
|
|
1878
|
+
suggestion: "Define this type using 'type TypeName = { ... }' and reference it by name"
|
|
1879
|
+
}
|
|
1880
|
+
)
|
|
1881
|
+
);
|
|
1882
|
+
for (const f of typeExpr.fields) {
|
|
1883
|
+
this.checkAnonymousObjectType(f.typeExpr, fieldLocation);
|
|
1884
|
+
}
|
|
1885
|
+
break;
|
|
1886
|
+
case "arrayType":
|
|
1887
|
+
this.checkAnonymousObjectType(typeExpr.elementType, fieldLocation);
|
|
1888
|
+
break;
|
|
1889
|
+
case "recordType":
|
|
1890
|
+
this.checkAnonymousObjectType(typeExpr.keyType, fieldLocation);
|
|
1891
|
+
this.checkAnonymousObjectType(typeExpr.valueType, fieldLocation);
|
|
1892
|
+
break;
|
|
1893
|
+
case "unionType":
|
|
1894
|
+
for (const t of typeExpr.types) {
|
|
1895
|
+
this.checkAnonymousObjectType(t, fieldLocation);
|
|
1896
|
+
}
|
|
1897
|
+
break;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
validateAction(action) {
|
|
1901
|
+
this.ctx.inAction = true;
|
|
1902
|
+
if (action.available) {
|
|
1903
|
+
this.validateAvailableExpr(action.available);
|
|
1904
|
+
}
|
|
1905
|
+
for (const stmt of action.body) {
|
|
1906
|
+
this.validateGuardedStmt(stmt);
|
|
1907
|
+
}
|
|
1908
|
+
this.ctx.inAction = false;
|
|
1909
|
+
}
|
|
1910
|
+
/**
|
|
1911
|
+
* v0.3.3: Validate available expression is pure (E005)
|
|
1912
|
+
* No $system.*, no effects, no $input.*
|
|
1913
|
+
*/
|
|
1914
|
+
validateAvailableExpr(expr) {
|
|
1915
|
+
switch (expr.kind) {
|
|
1916
|
+
case "systemIdent":
|
|
1917
|
+
if (expr.path[0] === "system") {
|
|
1918
|
+
this.error(
|
|
1919
|
+
"$system.* cannot be used in available condition (must be pure expression)",
|
|
1920
|
+
expr.location,
|
|
1921
|
+
"E005"
|
|
1922
|
+
);
|
|
1923
|
+
}
|
|
1924
|
+
if (expr.path[0] === "input") {
|
|
1925
|
+
this.error(
|
|
1926
|
+
"$input.* cannot be used in available condition (parameters not available at availability check)",
|
|
1927
|
+
expr.location,
|
|
1928
|
+
"E005"
|
|
1929
|
+
);
|
|
1930
|
+
}
|
|
1931
|
+
break;
|
|
1932
|
+
case "functionCall":
|
|
1933
|
+
for (const arg of expr.args) {
|
|
1934
|
+
this.validateAvailableExpr(arg);
|
|
1935
|
+
}
|
|
1936
|
+
break;
|
|
1937
|
+
case "binary":
|
|
1938
|
+
this.validateAvailableExpr(expr.left);
|
|
1939
|
+
this.validateAvailableExpr(expr.right);
|
|
1940
|
+
break;
|
|
1941
|
+
case "unary":
|
|
1942
|
+
this.validateAvailableExpr(expr.operand);
|
|
1943
|
+
break;
|
|
1944
|
+
case "ternary":
|
|
1945
|
+
this.validateAvailableExpr(expr.condition);
|
|
1946
|
+
this.validateAvailableExpr(expr.consequent);
|
|
1947
|
+
this.validateAvailableExpr(expr.alternate);
|
|
1948
|
+
break;
|
|
1949
|
+
case "propertyAccess":
|
|
1950
|
+
this.validateAvailableExpr(expr.object);
|
|
1951
|
+
break;
|
|
1952
|
+
case "indexAccess":
|
|
1953
|
+
this.validateAvailableExpr(expr.object);
|
|
1954
|
+
this.validateAvailableExpr(expr.index);
|
|
1955
|
+
break;
|
|
1956
|
+
case "objectLiteral":
|
|
1957
|
+
for (const prop of expr.properties) {
|
|
1958
|
+
this.validateAvailableExpr(prop.value);
|
|
1959
|
+
}
|
|
1960
|
+
break;
|
|
1961
|
+
case "arrayLiteral":
|
|
1962
|
+
for (const elem of expr.elements) {
|
|
1963
|
+
this.validateAvailableExpr(elem);
|
|
1964
|
+
}
|
|
1965
|
+
break;
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
validateGuardedStmt(stmt) {
|
|
1969
|
+
switch (stmt.kind) {
|
|
1970
|
+
case "when":
|
|
1971
|
+
this.validateWhen(stmt);
|
|
1972
|
+
break;
|
|
1973
|
+
case "once":
|
|
1974
|
+
this.validateOnce(stmt);
|
|
1975
|
+
break;
|
|
1976
|
+
case "onceIntent":
|
|
1977
|
+
this.validateOnceIntent(stmt);
|
|
1978
|
+
break;
|
|
1979
|
+
case "patch":
|
|
1980
|
+
this.validatePatch(stmt);
|
|
1981
|
+
break;
|
|
1982
|
+
case "effect":
|
|
1983
|
+
this.validateEffect(stmt);
|
|
1984
|
+
break;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
validateWhen(stmt) {
|
|
1988
|
+
this.ctx.inGuard = true;
|
|
1989
|
+
this.ctx.guardDepth++;
|
|
1990
|
+
this.validateCondition(stmt.condition, "when");
|
|
1991
|
+
for (const inner of stmt.body) {
|
|
1992
|
+
this.validateGuardedStmt(inner);
|
|
1993
|
+
}
|
|
1994
|
+
this.ctx.guardDepth--;
|
|
1995
|
+
if (this.ctx.guardDepth === 0) {
|
|
1996
|
+
this.ctx.inGuard = false;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
validateOnce(stmt) {
|
|
2000
|
+
this.ctx.inGuard = true;
|
|
2001
|
+
this.ctx.guardDepth++;
|
|
2002
|
+
this.ctx.hasMarkerPatch = false;
|
|
2003
|
+
if (stmt.condition) {
|
|
2004
|
+
this.validateCondition(stmt.condition, "once");
|
|
2005
|
+
}
|
|
2006
|
+
for (const inner of stmt.body) {
|
|
2007
|
+
this.validateGuardedStmt(inner);
|
|
2008
|
+
}
|
|
2009
|
+
this.ctx.guardDepth--;
|
|
2010
|
+
if (this.ctx.guardDepth === 0) {
|
|
2011
|
+
this.ctx.inGuard = false;
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
validateOnceIntent(stmt) {
|
|
2015
|
+
this.ctx.inGuard = true;
|
|
2016
|
+
this.ctx.guardDepth++;
|
|
2017
|
+
if (stmt.condition) {
|
|
2018
|
+
this.validateCondition(stmt.condition, "onceIntent");
|
|
2019
|
+
}
|
|
2020
|
+
for (const inner of stmt.body) {
|
|
2021
|
+
this.validateGuardedStmt(inner);
|
|
2022
|
+
}
|
|
2023
|
+
this.ctx.guardDepth--;
|
|
2024
|
+
if (this.ctx.guardDepth === 0) {
|
|
2025
|
+
this.ctx.inGuard = false;
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
validatePatch(stmt) {
|
|
2029
|
+
if (!this.ctx.inGuard) {
|
|
2030
|
+
this.error(
|
|
2031
|
+
"Patch must be inside a guard (when, once, or onceIntent)",
|
|
2032
|
+
stmt.location,
|
|
2033
|
+
"E_UNGUARDED_PATCH"
|
|
2034
|
+
);
|
|
2035
|
+
}
|
|
2036
|
+
if (stmt.value) {
|
|
2037
|
+
this.validateExpr(stmt.value, "action");
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
validateEffect(stmt) {
|
|
2041
|
+
if (!this.ctx.inGuard) {
|
|
2042
|
+
this.error(
|
|
2043
|
+
"Effect must be inside a guard (when, once, or onceIntent)",
|
|
2044
|
+
stmt.location,
|
|
2045
|
+
"E_UNGUARDED_EFFECT"
|
|
2046
|
+
);
|
|
2047
|
+
}
|
|
2048
|
+
for (const arg of stmt.args) {
|
|
2049
|
+
if (!arg.isPath) {
|
|
2050
|
+
this.validateExpr(arg.value, "action");
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
validateCondition(expr, guardType) {
|
|
2055
|
+
this.validateExpr(expr, "action");
|
|
2056
|
+
if (expr.kind === "literal" && typeof expr.value !== "boolean") {
|
|
2057
|
+
this.warn(
|
|
2058
|
+
`Condition in ${guardType} is a non-boolean literal. Consider using a boolean expression`,
|
|
2059
|
+
expr.location,
|
|
2060
|
+
"W_NON_BOOL_COND"
|
|
2061
|
+
);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
validateExpr(expr, context) {
|
|
2065
|
+
switch (expr.kind) {
|
|
2066
|
+
case "functionCall":
|
|
2067
|
+
this.validateFunctionCall(expr, context);
|
|
2068
|
+
break;
|
|
2069
|
+
case "binary":
|
|
2070
|
+
this.validateExpr(expr.left, context);
|
|
2071
|
+
this.validateExpr(expr.right, context);
|
|
2072
|
+
break;
|
|
2073
|
+
case "unary":
|
|
2074
|
+
this.validateExpr(expr.operand, context);
|
|
2075
|
+
break;
|
|
2076
|
+
case "ternary":
|
|
2077
|
+
this.validateExpr(expr.condition, context);
|
|
2078
|
+
this.validateExpr(expr.consequent, context);
|
|
2079
|
+
this.validateExpr(expr.alternate, context);
|
|
2080
|
+
break;
|
|
2081
|
+
case "propertyAccess":
|
|
2082
|
+
this.validateExpr(expr.object, context);
|
|
2083
|
+
break;
|
|
2084
|
+
case "indexAccess":
|
|
2085
|
+
this.validateExpr(expr.object, context);
|
|
2086
|
+
this.validateExpr(expr.index, context);
|
|
2087
|
+
break;
|
|
2088
|
+
case "objectLiteral":
|
|
2089
|
+
for (const prop of expr.properties) {
|
|
2090
|
+
this.validateExpr(prop.value, context);
|
|
2091
|
+
}
|
|
2092
|
+
break;
|
|
2093
|
+
case "arrayLiteral":
|
|
2094
|
+
for (const elem of expr.elements) {
|
|
2095
|
+
this.validateExpr(elem, context);
|
|
2096
|
+
}
|
|
2097
|
+
break;
|
|
2098
|
+
case "systemIdent":
|
|
2099
|
+
break;
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
validateFunctionCall(expr, context) {
|
|
2103
|
+
const { name, args, location } = expr;
|
|
2104
|
+
if (["reduce", "fold", "foldl", "foldr", "scan"].includes(name)) {
|
|
2105
|
+
this.error(
|
|
2106
|
+
`Function '${name}' is forbidden. Use sum(), min(), max() for aggregation instead`,
|
|
2107
|
+
location,
|
|
2108
|
+
"E011"
|
|
2109
|
+
);
|
|
2110
|
+
}
|
|
2111
|
+
if (["sum", "min", "max"].includes(name) && args.length === 1) {
|
|
2112
|
+
if (context === "action") {
|
|
2113
|
+
this.error(
|
|
2114
|
+
`Primitive aggregation '${name}()' can only be used in computed expressions, not in actions`,
|
|
2115
|
+
location,
|
|
2116
|
+
"E009"
|
|
2117
|
+
);
|
|
2118
|
+
}
|
|
2119
|
+
const arg = args[0];
|
|
2120
|
+
if (arg.kind === "functionCall") {
|
|
2121
|
+
this.error(
|
|
2122
|
+
`Primitive aggregation '${name}()' does not allow composition. Use a direct reference, not '${arg.name}()'`,
|
|
2123
|
+
location,
|
|
2124
|
+
"E010"
|
|
2125
|
+
);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
switch (name) {
|
|
2129
|
+
// FDR-MEL-042: eq/neq on primitives only
|
|
2130
|
+
case "eq":
|
|
2131
|
+
case "neq":
|
|
2132
|
+
break;
|
|
2133
|
+
// FDR-MEL-026: len() on Array only
|
|
2134
|
+
case "len":
|
|
2135
|
+
// v0.3.3: sum() for array aggregation
|
|
2136
|
+
case "sum":
|
|
2137
|
+
if (args.length !== 1) {
|
|
2138
|
+
this.error(
|
|
2139
|
+
`Function '${name}' expects 1 argument, got ${args.length}`,
|
|
2140
|
+
location,
|
|
2141
|
+
"E_ARG_COUNT"
|
|
2142
|
+
);
|
|
2143
|
+
}
|
|
2144
|
+
break;
|
|
2145
|
+
// Binary functions need exactly 2 args
|
|
2146
|
+
case "add":
|
|
2147
|
+
case "sub":
|
|
2148
|
+
case "mul":
|
|
2149
|
+
case "div":
|
|
2150
|
+
case "mod":
|
|
2151
|
+
case "gt":
|
|
2152
|
+
case "gte":
|
|
2153
|
+
case "lt":
|
|
2154
|
+
case "lte":
|
|
2155
|
+
if (args.length !== 2) {
|
|
2156
|
+
this.error(
|
|
2157
|
+
`Function '${name}' expects 2 arguments, got ${args.length}`,
|
|
2158
|
+
location,
|
|
2159
|
+
"E_ARG_COUNT"
|
|
2160
|
+
);
|
|
2161
|
+
}
|
|
2162
|
+
break;
|
|
2163
|
+
// Unary functions need exactly 1 arg
|
|
2164
|
+
case "not":
|
|
2165
|
+
case "neg":
|
|
2166
|
+
case "abs":
|
|
2167
|
+
case "floor":
|
|
2168
|
+
case "ceil":
|
|
2169
|
+
case "round":
|
|
2170
|
+
case "sqrt":
|
|
2171
|
+
case "isNull":
|
|
2172
|
+
case "isNotNull":
|
|
2173
|
+
case "trim":
|
|
2174
|
+
case "lower":
|
|
2175
|
+
case "upper":
|
|
2176
|
+
case "strlen":
|
|
2177
|
+
case "keys":
|
|
2178
|
+
case "values":
|
|
2179
|
+
case "entries":
|
|
2180
|
+
case "first":
|
|
2181
|
+
case "last":
|
|
2182
|
+
case "typeof":
|
|
2183
|
+
case "toString":
|
|
2184
|
+
case "toNumber":
|
|
2185
|
+
case "toBoolean":
|
|
2186
|
+
case "reverse":
|
|
2187
|
+
case "unique":
|
|
2188
|
+
case "flat":
|
|
2189
|
+
case "fromEntries":
|
|
2190
|
+
if (args.length !== 1) {
|
|
2191
|
+
this.error(
|
|
2192
|
+
`Function '${name}' expects 1 argument, got ${args.length}`,
|
|
2193
|
+
location,
|
|
2194
|
+
"E_ARG_COUNT"
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2197
|
+
break;
|
|
2198
|
+
// Binary functions need exactly 2 args
|
|
2199
|
+
case "pow":
|
|
2200
|
+
case "filter":
|
|
2201
|
+
case "map":
|
|
2202
|
+
case "find":
|
|
2203
|
+
case "every":
|
|
2204
|
+
case "some":
|
|
2205
|
+
case "at":
|
|
2206
|
+
case "includes":
|
|
2207
|
+
case "field":
|
|
2208
|
+
case "hasKey":
|
|
2209
|
+
case "pick":
|
|
2210
|
+
case "omit":
|
|
2211
|
+
case "startsWith":
|
|
2212
|
+
case "endsWith":
|
|
2213
|
+
case "strIncludes":
|
|
2214
|
+
case "indexOf":
|
|
2215
|
+
if (args.length !== 2) {
|
|
2216
|
+
this.error(
|
|
2217
|
+
`Function '${name}' expects 2 arguments, got ${args.length}`,
|
|
2218
|
+
location,
|
|
2219
|
+
"E_ARG_COUNT"
|
|
2220
|
+
);
|
|
2221
|
+
}
|
|
2222
|
+
break;
|
|
2223
|
+
// 2-3 arg functions
|
|
2224
|
+
case "slice":
|
|
2225
|
+
case "substring":
|
|
2226
|
+
case "substr":
|
|
2227
|
+
case "replace":
|
|
2228
|
+
if (args.length < 2 || args.length > 3) {
|
|
2229
|
+
this.error(
|
|
2230
|
+
`Function '${name}' expects 2-3 arguments, got ${args.length}`,
|
|
2231
|
+
location,
|
|
2232
|
+
"E_ARG_COUNT"
|
|
2233
|
+
);
|
|
2234
|
+
}
|
|
2235
|
+
break;
|
|
2236
|
+
// split(str, delimiter)
|
|
2237
|
+
case "split":
|
|
2238
|
+
if (args.length !== 2) {
|
|
2239
|
+
this.error(
|
|
2240
|
+
`Function '${name}' expects 2 arguments, got ${args.length}`,
|
|
2241
|
+
location,
|
|
2242
|
+
"E_ARG_COUNT"
|
|
2243
|
+
);
|
|
2244
|
+
}
|
|
2245
|
+
break;
|
|
2246
|
+
// Variadic functions (at least 1 arg)
|
|
2247
|
+
case "and":
|
|
2248
|
+
case "or":
|
|
2249
|
+
case "concat":
|
|
2250
|
+
case "min":
|
|
2251
|
+
case "max":
|
|
2252
|
+
case "merge":
|
|
2253
|
+
case "coalesce":
|
|
2254
|
+
case "append":
|
|
2255
|
+
if (args.length < 1) {
|
|
2256
|
+
this.error(
|
|
2257
|
+
`Function '${name}' expects at least 1 argument`,
|
|
2258
|
+
location,
|
|
2259
|
+
"E_ARG_COUNT"
|
|
2260
|
+
);
|
|
2261
|
+
}
|
|
2262
|
+
break;
|
|
2263
|
+
// Conditional needs exactly 3 args
|
|
2264
|
+
case "if":
|
|
2265
|
+
case "cond":
|
|
2266
|
+
if (args.length !== 3) {
|
|
2267
|
+
this.error(
|
|
2268
|
+
`Function '${name}' expects 3 arguments, got ${args.length}`,
|
|
2269
|
+
location,
|
|
2270
|
+
"E_ARG_COUNT"
|
|
2271
|
+
);
|
|
2272
|
+
}
|
|
2273
|
+
break;
|
|
2274
|
+
default:
|
|
2275
|
+
this.error(
|
|
2276
|
+
`Unknown function '${name}'. Check spelling or see MEL builtin function reference`,
|
|
2277
|
+
location,
|
|
2278
|
+
"E_UNKNOWN_FN"
|
|
2279
|
+
);
|
|
2280
|
+
break;
|
|
2281
|
+
}
|
|
2282
|
+
for (const arg of args) {
|
|
2283
|
+
this.validateExpr(arg, context);
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
error(message, location, code) {
|
|
2287
|
+
this.ctx.diagnostics.push({
|
|
2288
|
+
severity: "error",
|
|
2289
|
+
code,
|
|
2290
|
+
message,
|
|
2291
|
+
location
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
warn(message, location, code) {
|
|
2295
|
+
this.ctx.diagnostics.push({
|
|
2296
|
+
severity: "warning",
|
|
2297
|
+
code,
|
|
2298
|
+
message,
|
|
2299
|
+
location
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2303
|
+
function validateSemantics(program) {
|
|
2304
|
+
const validator = new SemanticValidator();
|
|
2305
|
+
return validator.validate(program);
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
// src/generator/normalizer.ts
|
|
2309
|
+
function normalizeExpr(op, left, right) {
|
|
2310
|
+
switch (op) {
|
|
2311
|
+
// Arithmetic
|
|
2312
|
+
case "+":
|
|
2313
|
+
return { kind: "add", left, right };
|
|
2314
|
+
case "-":
|
|
2315
|
+
return { kind: "sub", left, right };
|
|
2316
|
+
case "*":
|
|
2317
|
+
return { kind: "mul", left, right };
|
|
2318
|
+
case "/":
|
|
2319
|
+
return { kind: "div", left, right };
|
|
2320
|
+
case "%":
|
|
2321
|
+
return { kind: "mod", left, right };
|
|
2322
|
+
// Comparison
|
|
2323
|
+
case "==":
|
|
2324
|
+
return { kind: "eq", left, right };
|
|
2325
|
+
case "!=":
|
|
2326
|
+
return { kind: "neq", left, right };
|
|
2327
|
+
case "<":
|
|
2328
|
+
return { kind: "lt", left, right };
|
|
2329
|
+
case "<=":
|
|
2330
|
+
return { kind: "lte", left, right };
|
|
2331
|
+
case ">":
|
|
2332
|
+
return { kind: "gt", left, right };
|
|
2333
|
+
case ">=":
|
|
2334
|
+
return { kind: "gte", left, right };
|
|
2335
|
+
// Logical
|
|
2336
|
+
case "&&":
|
|
2337
|
+
return { kind: "and", args: [left, right] };
|
|
2338
|
+
case "||":
|
|
2339
|
+
return { kind: "or", args: [left, right] };
|
|
2340
|
+
// Nullish coalescing
|
|
2341
|
+
case "??":
|
|
2342
|
+
return { kind: "coalesce", args: [left, right] };
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
function normalizeFunctionCall(name, args) {
|
|
2346
|
+
switch (name) {
|
|
2347
|
+
// ============ Arithmetic ============
|
|
2348
|
+
case "add":
|
|
2349
|
+
return { kind: "add", left: args[0], right: args[1] };
|
|
2350
|
+
case "sub":
|
|
2351
|
+
return { kind: "sub", left: args[0], right: args[1] };
|
|
2352
|
+
case "mul":
|
|
2353
|
+
return { kind: "mul", left: args[0], right: args[1] };
|
|
2354
|
+
case "div":
|
|
2355
|
+
return { kind: "div", left: args[0], right: args[1] };
|
|
2356
|
+
case "mod":
|
|
2357
|
+
return { kind: "mod", left: args[0], right: args[1] };
|
|
2358
|
+
case "neg":
|
|
2359
|
+
return { kind: "neg", arg: args[0] };
|
|
2360
|
+
case "abs":
|
|
2361
|
+
return { kind: "abs", arg: args[0] };
|
|
2362
|
+
case "min":
|
|
2363
|
+
if (args.length === 1) {
|
|
2364
|
+
return { kind: "minArray", array: args[0] };
|
|
2365
|
+
}
|
|
2366
|
+
return { kind: "min", args };
|
|
2367
|
+
case "max":
|
|
2368
|
+
if (args.length === 1) {
|
|
2369
|
+
return { kind: "maxArray", array: args[0] };
|
|
2370
|
+
}
|
|
2371
|
+
return { kind: "max", args };
|
|
2372
|
+
// v0.3.2: sum array aggregation
|
|
2373
|
+
case "sum":
|
|
2374
|
+
return { kind: "sumArray", array: args[0] };
|
|
2375
|
+
case "floor":
|
|
2376
|
+
return { kind: "floor", arg: args[0] };
|
|
2377
|
+
case "ceil":
|
|
2378
|
+
return { kind: "ceil", arg: args[0] };
|
|
2379
|
+
case "round":
|
|
2380
|
+
return { kind: "round", arg: args[0] };
|
|
2381
|
+
case "sqrt":
|
|
2382
|
+
return { kind: "sqrt", arg: args[0] };
|
|
2383
|
+
case "pow":
|
|
2384
|
+
return { kind: "pow", base: args[0], exponent: args[1] };
|
|
2385
|
+
// ============ Comparison ============
|
|
2386
|
+
case "eq":
|
|
2387
|
+
return { kind: "eq", left: args[0], right: args[1] };
|
|
2388
|
+
case "neq":
|
|
2389
|
+
return { kind: "neq", left: args[0], right: args[1] };
|
|
2390
|
+
case "gt":
|
|
2391
|
+
return { kind: "gt", left: args[0], right: args[1] };
|
|
2392
|
+
case "gte":
|
|
2393
|
+
return { kind: "gte", left: args[0], right: args[1] };
|
|
2394
|
+
case "lt":
|
|
2395
|
+
return { kind: "lt", left: args[0], right: args[1] };
|
|
2396
|
+
case "lte":
|
|
2397
|
+
return { kind: "lte", left: args[0], right: args[1] };
|
|
2398
|
+
// ============ Logical ============
|
|
2399
|
+
case "and":
|
|
2400
|
+
return { kind: "and", args };
|
|
2401
|
+
case "or":
|
|
2402
|
+
return { kind: "or", args };
|
|
2403
|
+
case "not":
|
|
2404
|
+
return { kind: "not", arg: args[0] };
|
|
2405
|
+
// ============ Type Checking ============
|
|
2406
|
+
case "isNull":
|
|
2407
|
+
return { kind: "isNull", arg: args[0] };
|
|
2408
|
+
case "isNotNull":
|
|
2409
|
+
return { kind: "not", arg: { kind: "isNull", arg: args[0] } };
|
|
2410
|
+
case "typeof":
|
|
2411
|
+
return { kind: "typeof", arg: args[0] };
|
|
2412
|
+
case "coalesce":
|
|
2413
|
+
return { kind: "coalesce", args };
|
|
2414
|
+
// ============ String ============
|
|
2415
|
+
case "concat":
|
|
2416
|
+
return { kind: "concat", args };
|
|
2417
|
+
case "trim":
|
|
2418
|
+
return { kind: "trim", str: args[0] };
|
|
2419
|
+
case "lower":
|
|
2420
|
+
case "toLowerCase":
|
|
2421
|
+
return { kind: "toLowerCase", str: args[0] };
|
|
2422
|
+
case "upper":
|
|
2423
|
+
case "toUpperCase":
|
|
2424
|
+
return { kind: "toUpperCase", str: args[0] };
|
|
2425
|
+
case "strlen":
|
|
2426
|
+
case "strLen":
|
|
2427
|
+
return { kind: "strLen", str: args[0] };
|
|
2428
|
+
case "substr":
|
|
2429
|
+
case "substring":
|
|
2430
|
+
return args[2] ? { kind: "substring", str: args[0], start: args[1], end: args[2] } : { kind: "substring", str: args[0], start: args[1] };
|
|
2431
|
+
case "toString":
|
|
2432
|
+
return { kind: "toString", arg: args[0] };
|
|
2433
|
+
// ============ Collection ============
|
|
2434
|
+
case "len":
|
|
2435
|
+
case "length":
|
|
2436
|
+
return { kind: "len", arg: args[0] };
|
|
2437
|
+
case "at":
|
|
2438
|
+
return { kind: "at", array: args[0], index: args[1] };
|
|
2439
|
+
case "first":
|
|
2440
|
+
return { kind: "first", array: args[0] };
|
|
2441
|
+
case "last":
|
|
2442
|
+
return { kind: "last", array: args[0] };
|
|
2443
|
+
case "slice":
|
|
2444
|
+
return args[2] ? { kind: "slice", array: args[0], start: args[1], end: args[2] } : { kind: "slice", array: args[0], start: args[1] };
|
|
2445
|
+
case "includes":
|
|
2446
|
+
return { kind: "includes", array: args[0], item: args[1] };
|
|
2447
|
+
case "filter":
|
|
2448
|
+
return { kind: "filter", array: args[0], predicate: args[1] };
|
|
2449
|
+
case "map":
|
|
2450
|
+
return { kind: "map", array: args[0], mapper: args[1] };
|
|
2451
|
+
case "find":
|
|
2452
|
+
return { kind: "find", array: args[0], predicate: args[1] };
|
|
2453
|
+
case "every":
|
|
2454
|
+
return { kind: "every", array: args[0], predicate: args[1] };
|
|
2455
|
+
case "some":
|
|
2456
|
+
return { kind: "some", array: args[0], predicate: args[1] };
|
|
2457
|
+
case "append":
|
|
2458
|
+
return { kind: "append", array: args[0], items: args.slice(1) };
|
|
2459
|
+
// ============ Object ============
|
|
2460
|
+
case "keys":
|
|
2461
|
+
return { kind: "keys", obj: args[0] };
|
|
2462
|
+
case "values":
|
|
2463
|
+
return { kind: "values", obj: args[0] };
|
|
2464
|
+
case "entries":
|
|
2465
|
+
return { kind: "entries", obj: args[0] };
|
|
2466
|
+
case "merge":
|
|
2467
|
+
return { kind: "merge", objects: args };
|
|
2468
|
+
// ============ Conditional ============
|
|
2469
|
+
case "if":
|
|
2470
|
+
case "cond":
|
|
2471
|
+
return { kind: "if", cond: args[0], then: args[1], else: args[2] };
|
|
2472
|
+
// ============ Unknown Function ============
|
|
2473
|
+
default:
|
|
2474
|
+
return {
|
|
2475
|
+
kind: "object",
|
|
2476
|
+
fields: {
|
|
2477
|
+
__call: { kind: "lit", value: name },
|
|
2478
|
+
__args: { kind: "lit", value: args }
|
|
2479
|
+
}
|
|
2480
|
+
};
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
// src/generator/ir.ts
|
|
2485
|
+
import { hashSchemaSync, semanticPathToPatchPath, sha256Sync } from "@manifesto-ai/core";
|
|
2486
|
+
function createContext2(domainName) {
|
|
2487
|
+
return {
|
|
2488
|
+
domainName,
|
|
2489
|
+
stateFields: /* @__PURE__ */ new Set(),
|
|
2490
|
+
computedFields: /* @__PURE__ */ new Set(),
|
|
2491
|
+
actionParams: /* @__PURE__ */ new Map(),
|
|
2492
|
+
onceIntentCounters: /* @__PURE__ */ new Map(),
|
|
2493
|
+
currentAction: null,
|
|
2494
|
+
diagnostics: [],
|
|
2495
|
+
typeDefs: /* @__PURE__ */ new Map()
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
function generate(program) {
|
|
2499
|
+
const ctx = createContext2(program.domain.name);
|
|
2500
|
+
collectFieldNames(program.domain, ctx);
|
|
2501
|
+
const types = generateTypes(program.domain, ctx);
|
|
2502
|
+
const state = generateState(program.domain, ctx);
|
|
2503
|
+
const computed = generateComputed(program.domain, ctx);
|
|
2504
|
+
const actions = generateActions(program.domain, ctx);
|
|
2505
|
+
if (ctx.diagnostics.some((d) => d.severity === "error")) {
|
|
2506
|
+
return {
|
|
2507
|
+
schema: null,
|
|
2508
|
+
diagnostics: ctx.diagnostics
|
|
2509
|
+
};
|
|
2510
|
+
}
|
|
2511
|
+
const schemaWithoutHash = {
|
|
2512
|
+
id: `mel:${program.domain.name.toLowerCase()}`,
|
|
2513
|
+
version: "1.0.0",
|
|
2514
|
+
types,
|
|
2515
|
+
state,
|
|
2516
|
+
computed,
|
|
2517
|
+
actions,
|
|
2518
|
+
meta: {
|
|
2519
|
+
name: program.domain.name
|
|
2520
|
+
}
|
|
2521
|
+
};
|
|
2522
|
+
const hash = computeHash(schemaWithoutHash);
|
|
2523
|
+
const schema = {
|
|
2524
|
+
...schemaWithoutHash,
|
|
2525
|
+
hash
|
|
2526
|
+
};
|
|
2527
|
+
return {
|
|
2528
|
+
schema,
|
|
2529
|
+
diagnostics: ctx.diagnostics
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
function collectFieldNames(domain, ctx) {
|
|
2533
|
+
for (const typeDecl of domain.types) {
|
|
2534
|
+
ctx.typeDefs.set(typeDecl.name, typeDecl);
|
|
2535
|
+
}
|
|
2536
|
+
for (const member of domain.members) {
|
|
2537
|
+
if (member.kind === "state") {
|
|
2538
|
+
for (const field of member.fields) {
|
|
2539
|
+
ctx.stateFields.add(field.name);
|
|
2540
|
+
}
|
|
2541
|
+
} else if (member.kind === "computed") {
|
|
2542
|
+
ctx.computedFields.add(member.name);
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
function generateTypes(domain, _ctx) {
|
|
2547
|
+
const types = {};
|
|
2548
|
+
for (const typeDecl of domain.types) {
|
|
2549
|
+
types[typeDecl.name] = {
|
|
2550
|
+
name: typeDecl.name,
|
|
2551
|
+
definition: typeExprToDefinition(typeDecl.typeExpr)
|
|
2552
|
+
};
|
|
2553
|
+
}
|
|
2554
|
+
return types;
|
|
2555
|
+
}
|
|
2556
|
+
function typeExprToDefinition(typeExpr) {
|
|
2557
|
+
switch (typeExpr.kind) {
|
|
2558
|
+
case "simpleType":
|
|
2559
|
+
if (["string", "number", "boolean", "null"].includes(typeExpr.name)) {
|
|
2560
|
+
return { kind: "primitive", type: typeExpr.name };
|
|
2561
|
+
}
|
|
2562
|
+
return { kind: "ref", name: typeExpr.name };
|
|
2563
|
+
case "arrayType":
|
|
2564
|
+
return {
|
|
2565
|
+
kind: "array",
|
|
2566
|
+
element: typeExprToDefinition(typeExpr.elementType)
|
|
2567
|
+
};
|
|
2568
|
+
case "recordType":
|
|
2569
|
+
return {
|
|
2570
|
+
kind: "record",
|
|
2571
|
+
key: typeExprToDefinition(typeExpr.keyType),
|
|
2572
|
+
value: typeExprToDefinition(typeExpr.valueType)
|
|
2573
|
+
};
|
|
2574
|
+
case "objectType":
|
|
2575
|
+
const fields = {};
|
|
2576
|
+
for (const field of typeExpr.fields) {
|
|
2577
|
+
fields[field.name] = {
|
|
2578
|
+
type: typeExprToDefinition(field.typeExpr),
|
|
2579
|
+
optional: field.optional
|
|
2580
|
+
};
|
|
2581
|
+
}
|
|
2582
|
+
return { kind: "object", fields };
|
|
2583
|
+
case "unionType":
|
|
2584
|
+
return {
|
|
2585
|
+
kind: "union",
|
|
2586
|
+
types: typeExpr.types.map(typeExprToDefinition)
|
|
2587
|
+
};
|
|
2588
|
+
case "literalType":
|
|
2589
|
+
return { kind: "literal", value: typeExpr.value };
|
|
2590
|
+
default:
|
|
2591
|
+
const _exhaustive = typeExpr;
|
|
2592
|
+
throw new Error(`Unknown type expression kind: ${typeExpr.kind}`);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
function generateState(domain, ctx) {
|
|
2596
|
+
const fields = {};
|
|
2597
|
+
for (const member of domain.members) {
|
|
2598
|
+
if (member.kind === "state") {
|
|
2599
|
+
for (const field of member.fields) {
|
|
2600
|
+
fields[field.name] = generateFieldSpec(field, ctx);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
return { fields };
|
|
2605
|
+
}
|
|
2606
|
+
function generateFieldSpec(field, ctx) {
|
|
2607
|
+
const spec = typeExprToFieldSpec(field.typeExpr, ctx);
|
|
2608
|
+
const defaultValue = field.initializer ? evaluateInitializer(field.initializer, ctx) : void 0;
|
|
2609
|
+
return {
|
|
2610
|
+
...spec,
|
|
2611
|
+
required: true,
|
|
2612
|
+
default: defaultValue
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2615
|
+
function typeExprToFieldSpec(typeExpr, ctx) {
|
|
2616
|
+
switch (typeExpr.kind) {
|
|
2617
|
+
case "simpleType":
|
|
2618
|
+
switch (typeExpr.name) {
|
|
2619
|
+
case "string":
|
|
2620
|
+
return { type: "string", required: true };
|
|
2621
|
+
case "number":
|
|
2622
|
+
return { type: "number", required: true };
|
|
2623
|
+
case "boolean":
|
|
2624
|
+
return { type: "boolean", required: true };
|
|
2625
|
+
case "null":
|
|
2626
|
+
return { type: "null", required: true };
|
|
2627
|
+
default: {
|
|
2628
|
+
const typeDef = ctx.typeDefs.get(typeExpr.name);
|
|
2629
|
+
if (typeDef) {
|
|
2630
|
+
return typeExprToFieldSpec(typeDef.typeExpr, ctx);
|
|
2631
|
+
}
|
|
2632
|
+
return { type: "object", required: true };
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
case "unionType": {
|
|
2636
|
+
const literals = [];
|
|
2637
|
+
let isLiteralUnion = true;
|
|
2638
|
+
let hasNull = false;
|
|
2639
|
+
for (const t of typeExpr.types) {
|
|
2640
|
+
if (t.kind === "literalType") {
|
|
2641
|
+
if (t.value === null) {
|
|
2642
|
+
hasNull = true;
|
|
2643
|
+
}
|
|
2644
|
+
literals.push(t.value);
|
|
2645
|
+
continue;
|
|
2646
|
+
}
|
|
2647
|
+
if (t.kind === "simpleType" && t.name === "null") {
|
|
2648
|
+
hasNull = true;
|
|
2649
|
+
literals.push(null);
|
|
2650
|
+
continue;
|
|
2651
|
+
}
|
|
2652
|
+
isLiteralUnion = false;
|
|
2653
|
+
}
|
|
2654
|
+
if (isLiteralUnion && literals.length > 0) {
|
|
2655
|
+
return { type: { enum: literals }, required: !hasNull };
|
|
2656
|
+
}
|
|
2657
|
+
if (hasNull) {
|
|
2658
|
+
for (const t of typeExpr.types) {
|
|
2659
|
+
if (t.kind !== "simpleType" || t.name !== "null") {
|
|
2660
|
+
const innerSpec = typeExprToFieldSpec(t, ctx);
|
|
2661
|
+
return { ...innerSpec, required: false };
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
for (const t of typeExpr.types) {
|
|
2666
|
+
if (t.kind !== "simpleType" || t.name !== "null") {
|
|
2667
|
+
return typeExprToFieldSpec(t, ctx);
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
return { type: "null", required: true };
|
|
2671
|
+
}
|
|
2672
|
+
case "arrayType": {
|
|
2673
|
+
const itemSpec = typeExprToFieldSpec(typeExpr.elementType, ctx);
|
|
2674
|
+
return {
|
|
2675
|
+
type: "array",
|
|
2676
|
+
required: true,
|
|
2677
|
+
items: itemSpec
|
|
2678
|
+
};
|
|
2679
|
+
}
|
|
2680
|
+
case "recordType":
|
|
2681
|
+
return { type: "object", required: true };
|
|
2682
|
+
case "literalType":
|
|
2683
|
+
if (typeof typeExpr.value === "string") return { type: "string", required: true };
|
|
2684
|
+
if (typeof typeExpr.value === "number") return { type: "number", required: true };
|
|
2685
|
+
if (typeof typeExpr.value === "boolean") return { type: "boolean", required: true };
|
|
2686
|
+
return { type: "null", required: true };
|
|
2687
|
+
case "objectType": {
|
|
2688
|
+
const objectFields = {};
|
|
2689
|
+
for (const field of typeExpr.fields) {
|
|
2690
|
+
const fieldSpec = typeExprToFieldSpec(field.typeExpr, ctx);
|
|
2691
|
+
objectFields[field.name] = {
|
|
2692
|
+
...fieldSpec,
|
|
2693
|
+
required: !field.optional
|
|
2694
|
+
};
|
|
2695
|
+
}
|
|
2696
|
+
return {
|
|
2697
|
+
type: "object",
|
|
2698
|
+
required: true,
|
|
2699
|
+
fields: objectFields
|
|
2700
|
+
};
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
function evaluateInitializer(expr, ctx) {
|
|
2705
|
+
switch (expr.kind) {
|
|
2706
|
+
case "literal":
|
|
2707
|
+
return expr.value;
|
|
2708
|
+
case "arrayLiteral":
|
|
2709
|
+
return expr.elements.map((e) => evaluateInitializer(e, ctx));
|
|
2710
|
+
case "objectLiteral": {
|
|
2711
|
+
const obj = {};
|
|
2712
|
+
for (const prop of expr.properties) {
|
|
2713
|
+
obj[prop.key] = evaluateInitializer(prop.value, ctx);
|
|
2714
|
+
}
|
|
2715
|
+
return obj;
|
|
2716
|
+
}
|
|
2717
|
+
default:
|
|
2718
|
+
return void 0;
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
function generateComputed(domain, ctx) {
|
|
2722
|
+
const fields = {};
|
|
2723
|
+
for (const member of domain.members) {
|
|
2724
|
+
if (member.kind === "computed") {
|
|
2725
|
+
const expr = generateExpr(member.expression, ctx);
|
|
2726
|
+
const deps = extractDeps(expr);
|
|
2727
|
+
fields[member.name] = {
|
|
2728
|
+
deps,
|
|
2729
|
+
expr
|
|
2730
|
+
};
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
return { fields };
|
|
2734
|
+
}
|
|
2735
|
+
function extractDeps(expr) {
|
|
2736
|
+
const deps = /* @__PURE__ */ new Set();
|
|
2737
|
+
function visit(node) {
|
|
2738
|
+
if (node.kind === "get") {
|
|
2739
|
+
deps.add(node.path);
|
|
2740
|
+
} else {
|
|
2741
|
+
for (const value of Object.values(node)) {
|
|
2742
|
+
if (typeof value === "object" && value !== null) {
|
|
2743
|
+
if (Array.isArray(value)) {
|
|
2744
|
+
for (const item of value) {
|
|
2745
|
+
if (typeof item === "object" && item !== null && "kind" in item) {
|
|
2746
|
+
visit(item);
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
} else if ("kind" in value) {
|
|
2750
|
+
visit(value);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
visit(expr);
|
|
2757
|
+
return Array.from(deps);
|
|
2758
|
+
}
|
|
2759
|
+
function generateActions(domain, ctx) {
|
|
2760
|
+
const actions = {};
|
|
2761
|
+
for (const member of domain.members) {
|
|
2762
|
+
if (member.kind === "action") {
|
|
2763
|
+
ctx.currentAction = member.name;
|
|
2764
|
+
ctx.onceIntentCounters.set(member.name, 0);
|
|
2765
|
+
const params = /* @__PURE__ */ new Set();
|
|
2766
|
+
for (const param of member.params) {
|
|
2767
|
+
params.add(param.name);
|
|
2768
|
+
}
|
|
2769
|
+
ctx.actionParams.set(member.name, params);
|
|
2770
|
+
const flow = generateFlow(member.body, ctx);
|
|
2771
|
+
let input;
|
|
2772
|
+
if (member.params.length > 0) {
|
|
2773
|
+
const inputFields = {};
|
|
2774
|
+
for (const param of member.params) {
|
|
2775
|
+
const fieldSpec = typeExprToFieldSpec(param.typeExpr, ctx);
|
|
2776
|
+
const inputField = {
|
|
2777
|
+
type: fieldSpec.type,
|
|
2778
|
+
required: fieldSpec.required ?? true
|
|
2779
|
+
};
|
|
2780
|
+
if (fieldSpec.type === "object" && fieldSpec.fields) {
|
|
2781
|
+
inputField.fields = fieldSpec.fields;
|
|
2782
|
+
}
|
|
2783
|
+
if (fieldSpec.type === "array" && fieldSpec.items) {
|
|
2784
|
+
inputField.items = fieldSpec.items;
|
|
2785
|
+
}
|
|
2786
|
+
inputFields[param.name] = inputField;
|
|
2787
|
+
}
|
|
2788
|
+
input = {
|
|
2789
|
+
type: "object",
|
|
2790
|
+
required: true,
|
|
2791
|
+
fields: inputFields
|
|
2792
|
+
};
|
|
2793
|
+
}
|
|
2794
|
+
let available;
|
|
2795
|
+
if (member.available) {
|
|
2796
|
+
available = generateExpr(member.available, ctx);
|
|
2797
|
+
}
|
|
2798
|
+
actions[member.name] = {
|
|
2799
|
+
flow,
|
|
2800
|
+
input,
|
|
2801
|
+
available
|
|
2802
|
+
};
|
|
2803
|
+
ctx.currentAction = null;
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
return actions;
|
|
2807
|
+
}
|
|
2808
|
+
function generateFlow(stmts, ctx) {
|
|
2809
|
+
if (stmts.length === 0) {
|
|
2810
|
+
return { kind: "seq", steps: [] };
|
|
2811
|
+
}
|
|
2812
|
+
if (stmts.length === 1) {
|
|
2813
|
+
return generateStmt(stmts[0], ctx);
|
|
2814
|
+
}
|
|
2815
|
+
return {
|
|
2816
|
+
kind: "seq",
|
|
2817
|
+
steps: stmts.map((s) => generateStmt(s, ctx))
|
|
2818
|
+
};
|
|
2819
|
+
}
|
|
2820
|
+
function generateStmt(stmt, ctx) {
|
|
2821
|
+
switch (stmt.kind) {
|
|
2822
|
+
case "when":
|
|
2823
|
+
return generateWhen(stmt, ctx);
|
|
2824
|
+
case "once":
|
|
2825
|
+
return generateOnce(stmt, ctx);
|
|
2826
|
+
case "onceIntent":
|
|
2827
|
+
return generateOnceIntent(stmt, ctx);
|
|
2828
|
+
case "patch":
|
|
2829
|
+
return generatePatch(stmt, ctx);
|
|
2830
|
+
case "effect":
|
|
2831
|
+
return generateEffect(stmt, ctx);
|
|
2832
|
+
case "fail":
|
|
2833
|
+
return generateFail(stmt, ctx);
|
|
2834
|
+
case "stop":
|
|
2835
|
+
return generateStop(stmt, ctx);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
function generateWhen(stmt, ctx) {
|
|
2839
|
+
const cond = generateExpr(stmt.condition, ctx);
|
|
2840
|
+
const thenFlow = generateFlow(stmt.body, ctx);
|
|
2841
|
+
return {
|
|
2842
|
+
kind: "if",
|
|
2843
|
+
cond,
|
|
2844
|
+
then: thenFlow
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
function generateOnce(stmt, ctx) {
|
|
2848
|
+
const markerPath = generatePath(stmt.marker, ctx);
|
|
2849
|
+
const intentIdExpr = { kind: "get", path: "meta.intentId" };
|
|
2850
|
+
let cond = {
|
|
2851
|
+
kind: "neq",
|
|
2852
|
+
left: { kind: "get", path: markerPath },
|
|
2853
|
+
right: intentIdExpr
|
|
2854
|
+
};
|
|
2855
|
+
if (stmt.condition) {
|
|
2856
|
+
const extraCond = generateExpr(stmt.condition, ctx);
|
|
2857
|
+
cond = {
|
|
2858
|
+
kind: "and",
|
|
2859
|
+
args: [cond, extraCond]
|
|
2860
|
+
};
|
|
2861
|
+
}
|
|
2862
|
+
const markerPatch = {
|
|
2863
|
+
kind: "patch",
|
|
2864
|
+
op: "set",
|
|
2865
|
+
path: toPatchPath(markerPath),
|
|
2866
|
+
value: intentIdExpr
|
|
2867
|
+
};
|
|
2868
|
+
const bodySteps = stmt.body.map((s) => generateStmt(s, ctx));
|
|
2869
|
+
return {
|
|
2870
|
+
kind: "if",
|
|
2871
|
+
cond,
|
|
2872
|
+
then: {
|
|
2873
|
+
kind: "seq",
|
|
2874
|
+
steps: [markerPatch, ...bodySteps]
|
|
2875
|
+
}
|
|
2876
|
+
};
|
|
2877
|
+
}
|
|
2878
|
+
function generateOnceIntent(stmt, ctx) {
|
|
2879
|
+
const actionName = ctx.currentAction ?? "unknown";
|
|
2880
|
+
const nextIndex = ctx.onceIntentCounters.get(actionName) ?? 0;
|
|
2881
|
+
ctx.onceIntentCounters.set(actionName, nextIndex + 1);
|
|
2882
|
+
const guardId = sha256Sync(`${actionName}:${nextIndex}:intent`);
|
|
2883
|
+
const guardPath = `$mel.guards.intent.${guardId}`;
|
|
2884
|
+
const intentIdExpr = { kind: "get", path: "meta.intentId" };
|
|
2885
|
+
let cond = {
|
|
2886
|
+
kind: "neq",
|
|
2887
|
+
left: { kind: "get", path: guardPath },
|
|
2888
|
+
right: intentIdExpr
|
|
2889
|
+
};
|
|
2890
|
+
if (stmt.condition) {
|
|
2891
|
+
const extraCond = generateExpr(stmt.condition, ctx);
|
|
2892
|
+
cond = {
|
|
2893
|
+
kind: "and",
|
|
2894
|
+
args: [cond, extraCond]
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
const markerPatch = {
|
|
2898
|
+
kind: "patch",
|
|
2899
|
+
op: "merge",
|
|
2900
|
+
path: toPatchPath("$mel.guards.intent"),
|
|
2901
|
+
value: {
|
|
2902
|
+
kind: "object",
|
|
2903
|
+
fields: { [guardId]: intentIdExpr }
|
|
2904
|
+
}
|
|
2905
|
+
};
|
|
2906
|
+
const bodySteps = stmt.body.map((s) => generateStmt(s, ctx));
|
|
2907
|
+
return {
|
|
2908
|
+
kind: "if",
|
|
2909
|
+
cond,
|
|
2910
|
+
then: {
|
|
2911
|
+
kind: "seq",
|
|
2912
|
+
steps: [markerPatch, ...bodySteps]
|
|
2913
|
+
}
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2916
|
+
function generatePatch(stmt, ctx) {
|
|
2917
|
+
const path = generatePath(stmt.path, ctx);
|
|
2918
|
+
const result = {
|
|
2919
|
+
kind: "patch",
|
|
2920
|
+
op: stmt.op,
|
|
2921
|
+
path: toPatchPath(path)
|
|
2922
|
+
};
|
|
2923
|
+
if (stmt.value) {
|
|
2924
|
+
result.value = generateExpr(stmt.value, ctx);
|
|
2925
|
+
}
|
|
2926
|
+
return result;
|
|
2927
|
+
}
|
|
2928
|
+
function generateEffect(stmt, ctx) {
|
|
2929
|
+
const params = {};
|
|
2930
|
+
for (const arg of stmt.args) {
|
|
2931
|
+
if (arg.isPath) {
|
|
2932
|
+
params[arg.name] = { kind: "lit", value: generatePath(arg.value, ctx) };
|
|
2933
|
+
} else {
|
|
2934
|
+
params[arg.name] = generateExpr(arg.value, ctx);
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
return {
|
|
2938
|
+
kind: "effect",
|
|
2939
|
+
type: stmt.effectType,
|
|
2940
|
+
params
|
|
2941
|
+
};
|
|
2942
|
+
}
|
|
2943
|
+
function generateFail(stmt, ctx) {
|
|
2944
|
+
const result = {
|
|
2945
|
+
kind: "fail",
|
|
2946
|
+
code: stmt.code
|
|
2947
|
+
};
|
|
2948
|
+
if (stmt.message) {
|
|
2949
|
+
result.message = generateExpr(stmt.message, ctx);
|
|
2950
|
+
}
|
|
2951
|
+
return result;
|
|
2952
|
+
}
|
|
2953
|
+
function generateStop(stmt, ctx) {
|
|
2954
|
+
return {
|
|
2955
|
+
kind: "halt",
|
|
2956
|
+
reason: stmt.reason
|
|
2957
|
+
};
|
|
2958
|
+
}
|
|
2959
|
+
function escapePathSegment(segment) {
|
|
2960
|
+
return segment.replaceAll("\\", "\\\\").replaceAll(".", "\\.");
|
|
2961
|
+
}
|
|
2962
|
+
function joinPathPreserveEmptySegments(...segments) {
|
|
2963
|
+
return segments.map(escapePathSegment).join(".");
|
|
2964
|
+
}
|
|
2965
|
+
function generatePath(path, ctx) {
|
|
2966
|
+
const segments = [];
|
|
2967
|
+
for (const segment of path.segments) {
|
|
2968
|
+
if (segment.kind === "propertySegment") {
|
|
2969
|
+
segments.push(segment.name);
|
|
2970
|
+
} else {
|
|
2971
|
+
const indexExpr = generateExpr(segment.index, ctx);
|
|
2972
|
+
if (indexExpr.kind === "lit") {
|
|
2973
|
+
segments.push(String(indexExpr.value));
|
|
2974
|
+
} else {
|
|
2975
|
+
segments.push("*");
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
const first = segments[0];
|
|
2980
|
+
if (ctx.stateFields.has(first)) {
|
|
2981
|
+
return joinPathPreserveEmptySegments(...segments);
|
|
2982
|
+
}
|
|
2983
|
+
if (ctx.computedFields.has(first)) {
|
|
2984
|
+
return joinPathPreserveEmptySegments(...segments);
|
|
2985
|
+
}
|
|
2986
|
+
if (ctx.currentAction && ctx.actionParams.get(ctx.currentAction)?.has(first)) {
|
|
2987
|
+
return `input.${joinPathPreserveEmptySegments(...segments)}`;
|
|
2988
|
+
}
|
|
2989
|
+
return joinPathPreserveEmptySegments(...segments);
|
|
2990
|
+
}
|
|
2991
|
+
function toPatchPath(path) {
|
|
2992
|
+
return semanticPathToPatchPath(path);
|
|
2993
|
+
}
|
|
2994
|
+
function generateExpr(expr, ctx) {
|
|
2995
|
+
switch (expr.kind) {
|
|
2996
|
+
case "literal":
|
|
2997
|
+
return { kind: "lit", value: expr.value };
|
|
2998
|
+
case "identifier":
|
|
2999
|
+
return generateIdentifier(expr.name, ctx);
|
|
3000
|
+
case "systemIdent":
|
|
3001
|
+
return generateSystemIdent(expr.path, ctx);
|
|
3002
|
+
case "iterationVar":
|
|
3003
|
+
return { kind: "get", path: `$${expr.name}` };
|
|
3004
|
+
case "propertyAccess":
|
|
3005
|
+
return generatePropertyAccess(expr, ctx);
|
|
3006
|
+
case "indexAccess":
|
|
3007
|
+
return {
|
|
3008
|
+
kind: "at",
|
|
3009
|
+
array: generateExpr(expr.object, ctx),
|
|
3010
|
+
index: generateExpr(expr.index, ctx)
|
|
3011
|
+
};
|
|
3012
|
+
case "functionCall":
|
|
3013
|
+
return normalizeFunctionCall(
|
|
3014
|
+
expr.name,
|
|
3015
|
+
expr.args.map((a) => generateExpr(a, ctx))
|
|
3016
|
+
);
|
|
3017
|
+
case "binary":
|
|
3018
|
+
return normalizeExpr(
|
|
3019
|
+
expr.operator,
|
|
3020
|
+
generateExpr(expr.left, ctx),
|
|
3021
|
+
generateExpr(expr.right, ctx)
|
|
3022
|
+
);
|
|
3023
|
+
case "unary":
|
|
3024
|
+
if (expr.operator === "!") {
|
|
3025
|
+
return { kind: "not", arg: generateExpr(expr.operand, ctx) };
|
|
3026
|
+
} else {
|
|
3027
|
+
return { kind: "neg", arg: generateExpr(expr.operand, ctx) };
|
|
3028
|
+
}
|
|
3029
|
+
case "ternary":
|
|
3030
|
+
return {
|
|
3031
|
+
kind: "if",
|
|
3032
|
+
cond: generateExpr(expr.condition, ctx),
|
|
3033
|
+
then: generateExpr(expr.consequent, ctx),
|
|
3034
|
+
else: generateExpr(expr.alternate, ctx)
|
|
3035
|
+
};
|
|
3036
|
+
case "objectLiteral": {
|
|
3037
|
+
const fields = {};
|
|
3038
|
+
for (const prop of expr.properties) {
|
|
3039
|
+
fields[prop.key] = generateExpr(prop.value, ctx);
|
|
3040
|
+
}
|
|
3041
|
+
return { kind: "object", fields };
|
|
3042
|
+
}
|
|
3043
|
+
case "arrayLiteral":
|
|
3044
|
+
if (expr.elements.length === 0) {
|
|
3045
|
+
return { kind: "lit", value: [] };
|
|
3046
|
+
}
|
|
3047
|
+
const allLiterals = expr.elements.every((e) => e.kind === "literal");
|
|
3048
|
+
if (allLiterals) {
|
|
3049
|
+
return { kind: "lit", value: expr.elements.map((e) => e.value) };
|
|
3050
|
+
}
|
|
3051
|
+
return {
|
|
3052
|
+
kind: "append",
|
|
3053
|
+
array: { kind: "lit", value: [] },
|
|
3054
|
+
items: expr.elements.map((e) => generateExpr(e, ctx))
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
function generateIdentifier(name, ctx) {
|
|
3059
|
+
if (ctx.stateFields.has(name)) {
|
|
3060
|
+
return { kind: "get", path: name };
|
|
3061
|
+
}
|
|
3062
|
+
if (ctx.computedFields.has(name)) {
|
|
3063
|
+
return { kind: "get", path: name };
|
|
3064
|
+
}
|
|
3065
|
+
if (ctx.currentAction && ctx.actionParams.get(ctx.currentAction)?.has(name)) {
|
|
3066
|
+
return { kind: "get", path: `input.${name}` };
|
|
3067
|
+
}
|
|
3068
|
+
ctx.diagnostics.push({
|
|
3069
|
+
severity: "error",
|
|
3070
|
+
code: "E_UNKNOWN_IDENT",
|
|
3071
|
+
message: `Unknown identifier '${name}'`,
|
|
3072
|
+
location: { start: { line: 0, column: 0, offset: 0 }, end: { line: 0, column: 0, offset: 0 } }
|
|
3073
|
+
});
|
|
3074
|
+
return { kind: "get", path: name };
|
|
3075
|
+
}
|
|
3076
|
+
function generateSystemIdent(path, ctx) {
|
|
3077
|
+
const [namespace, ...rest] = path;
|
|
3078
|
+
switch (namespace) {
|
|
3079
|
+
case "system":
|
|
3080
|
+
return { kind: "get", path: `$system.${rest.join(".")}` };
|
|
3081
|
+
case "meta":
|
|
3082
|
+
return { kind: "get", path: `meta.${rest.join(".")}` };
|
|
3083
|
+
case "input":
|
|
3084
|
+
return { kind: "get", path: `input.${rest.join(".")}` };
|
|
3085
|
+
default:
|
|
3086
|
+
ctx.diagnostics.push({
|
|
3087
|
+
severity: "error",
|
|
3088
|
+
code: "E_INVALID_SYSTEM",
|
|
3089
|
+
message: `Invalid system identifier namespace '$${namespace}'`,
|
|
3090
|
+
location: { start: { line: 0, column: 0, offset: 0 }, end: { line: 0, column: 0, offset: 0 } }
|
|
3091
|
+
});
|
|
3092
|
+
return { kind: "lit", value: null };
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
function generatePropertyAccess(expr, ctx) {
|
|
3096
|
+
const objectExpr = generateExpr(expr.object, ctx);
|
|
3097
|
+
if (objectExpr.kind === "get") {
|
|
3098
|
+
return { kind: "get", path: `${objectExpr.path}.${expr.property}` };
|
|
3099
|
+
}
|
|
3100
|
+
return {
|
|
3101
|
+
kind: "field",
|
|
3102
|
+
object: objectExpr,
|
|
3103
|
+
property: expr.property
|
|
3104
|
+
};
|
|
3105
|
+
}
|
|
3106
|
+
function computeHash(schema) {
|
|
3107
|
+
return hashSchemaSync(schema);
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
// src/lowering/errors.ts
|
|
3111
|
+
var LoweringError = class extends Error {
|
|
3112
|
+
code;
|
|
3113
|
+
path;
|
|
3114
|
+
details;
|
|
3115
|
+
constructor(code, message, options) {
|
|
3116
|
+
super(message);
|
|
3117
|
+
this.name = "LoweringError";
|
|
3118
|
+
this.code = code;
|
|
3119
|
+
this.path = options?.path;
|
|
3120
|
+
this.details = options?.details;
|
|
3121
|
+
}
|
|
3122
|
+
};
|
|
3123
|
+
function invalidKindForContext(kind, context, path) {
|
|
3124
|
+
return new LoweringError(
|
|
3125
|
+
"INVALID_KIND_FOR_CONTEXT",
|
|
3126
|
+
`Node kind '${kind}' is not allowed in ${context} context`,
|
|
3127
|
+
{ path, details: { kind, context } }
|
|
3128
|
+
);
|
|
3129
|
+
}
|
|
3130
|
+
function unknownCallFn(fn, path) {
|
|
3131
|
+
return new LoweringError(
|
|
3132
|
+
"UNKNOWN_CALL_FN",
|
|
3133
|
+
`Unknown function '${fn}' in call expression`,
|
|
3134
|
+
{ path, details: { fn } }
|
|
3135
|
+
);
|
|
3136
|
+
}
|
|
3137
|
+
function invalidSysPath(sysPath, path) {
|
|
3138
|
+
return new LoweringError(
|
|
3139
|
+
"INVALID_SYS_PATH",
|
|
3140
|
+
`System path '${sysPath.join(".")}' is not allowed in Translator path`,
|
|
3141
|
+
{ path, details: { sysPath } }
|
|
3142
|
+
);
|
|
3143
|
+
}
|
|
3144
|
+
function unsupportedBase(baseKind, path) {
|
|
3145
|
+
return new LoweringError(
|
|
3146
|
+
"UNSUPPORTED_BASE",
|
|
3147
|
+
`Unsupported base expression kind '${baseKind}'. Only var(item) is supported.`,
|
|
3148
|
+
{ path, details: { baseKind } }
|
|
3149
|
+
);
|
|
3150
|
+
}
|
|
3151
|
+
function invalidShape(description, path) {
|
|
3152
|
+
return new LoweringError(
|
|
3153
|
+
"INVALID_SHAPE",
|
|
3154
|
+
`Invalid node shape: ${description}`,
|
|
3155
|
+
{ path, details: { description } }
|
|
3156
|
+
);
|
|
3157
|
+
}
|
|
3158
|
+
function unknownNodeKind(kind, path) {
|
|
3159
|
+
return new LoweringError(
|
|
3160
|
+
"UNKNOWN_NODE_KIND",
|
|
3161
|
+
`Unknown expression node kind '${kind}'`,
|
|
3162
|
+
{ path, details: { kind } }
|
|
3163
|
+
);
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
// src/lowering/lower-expr.ts
|
|
3167
|
+
function lowerExprNode(input, ctx) {
|
|
3168
|
+
switch (input.kind) {
|
|
3169
|
+
case "lit":
|
|
3170
|
+
return lowerLit(input);
|
|
3171
|
+
case "var":
|
|
3172
|
+
return lowerVar(input, ctx);
|
|
3173
|
+
case "sys":
|
|
3174
|
+
return lowerSys(input, ctx);
|
|
3175
|
+
case "get":
|
|
3176
|
+
return lowerGet(input, ctx);
|
|
3177
|
+
case "call":
|
|
3178
|
+
return lowerCall(input, ctx);
|
|
3179
|
+
case "obj":
|
|
3180
|
+
return lowerObj(input, ctx);
|
|
3181
|
+
case "arr":
|
|
3182
|
+
return lowerArr(input, ctx);
|
|
3183
|
+
default:
|
|
3184
|
+
throw unknownNodeKind(input.kind);
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
function lowerLit(input) {
|
|
3188
|
+
return { kind: "lit", value: input.value };
|
|
3189
|
+
}
|
|
3190
|
+
function lowerVar(input, ctx) {
|
|
3191
|
+
if (!ctx.allowItem) {
|
|
3192
|
+
throw invalidKindForContext("var", ctx.mode);
|
|
3193
|
+
}
|
|
3194
|
+
return { kind: "get", path: "$item" };
|
|
3195
|
+
}
|
|
3196
|
+
function lowerSys(input, ctx) {
|
|
3197
|
+
if (input.path.length === 0) {
|
|
3198
|
+
throw invalidSysPath(input.path);
|
|
3199
|
+
}
|
|
3200
|
+
const prefix = input.path[0];
|
|
3201
|
+
const allowedPrefixes = ctx.allowSysPaths?.prefixes ?? ["meta", "input"];
|
|
3202
|
+
if (!allowedPrefixes.includes(prefix)) {
|
|
3203
|
+
throw invalidSysPath(input.path);
|
|
3204
|
+
}
|
|
3205
|
+
const path = input.path.join(".");
|
|
3206
|
+
return { kind: "get", path };
|
|
3207
|
+
}
|
|
3208
|
+
function lowerGet(input, ctx) {
|
|
3209
|
+
const pathStr = input.path.map((seg) => seg.name).join(".");
|
|
3210
|
+
if (input.base === void 0) {
|
|
3211
|
+
return { kind: "get", path: pathStr };
|
|
3212
|
+
}
|
|
3213
|
+
if (input.base.kind === "var" && input.base.name === "item") {
|
|
3214
|
+
if (!ctx.allowItem) {
|
|
3215
|
+
throw invalidKindForContext("var", ctx.mode);
|
|
3216
|
+
}
|
|
3217
|
+
return { kind: "get", path: `$item.${pathStr}` };
|
|
3218
|
+
}
|
|
3219
|
+
throw unsupportedBase(input.base.kind);
|
|
3220
|
+
}
|
|
3221
|
+
function lowerCall(input, ctx) {
|
|
3222
|
+
const { fn, args } = input;
|
|
3223
|
+
if (isBinaryOp2(fn)) {
|
|
3224
|
+
if (args.length !== 2) {
|
|
3225
|
+
throw unknownCallFn(fn);
|
|
3226
|
+
}
|
|
3227
|
+
const [left, right] = args;
|
|
3228
|
+
return {
|
|
3229
|
+
kind: fn,
|
|
3230
|
+
left: lowerExprNode(left, ctx),
|
|
3231
|
+
right: lowerExprNode(right, ctx)
|
|
3232
|
+
};
|
|
3233
|
+
}
|
|
3234
|
+
if (isUnaryArgOp(fn)) {
|
|
3235
|
+
if (args.length !== 1) {
|
|
3236
|
+
throw unknownCallFn(fn);
|
|
3237
|
+
}
|
|
3238
|
+
return {
|
|
3239
|
+
kind: fn,
|
|
3240
|
+
arg: lowerExprNode(args[0], ctx)
|
|
3241
|
+
};
|
|
3242
|
+
}
|
|
3243
|
+
if (fn === "trim") {
|
|
3244
|
+
if (args.length !== 1) {
|
|
3245
|
+
throw unknownCallFn(fn);
|
|
3246
|
+
}
|
|
3247
|
+
return {
|
|
3248
|
+
kind: "trim",
|
|
3249
|
+
str: lowerExprNode(args[0], ctx)
|
|
3250
|
+
};
|
|
3251
|
+
}
|
|
3252
|
+
if (isArgsOp(fn)) {
|
|
3253
|
+
return {
|
|
3254
|
+
kind: fn,
|
|
3255
|
+
args: args.map((a) => lowerExprNode(a, ctx))
|
|
3256
|
+
};
|
|
3257
|
+
}
|
|
3258
|
+
if (fn === "if") {
|
|
3259
|
+
if (args.length !== 3) {
|
|
3260
|
+
throw unknownCallFn(fn);
|
|
3261
|
+
}
|
|
3262
|
+
return {
|
|
3263
|
+
kind: "if",
|
|
3264
|
+
cond: lowerExprNode(args[0], ctx),
|
|
3265
|
+
then: lowerExprNode(args[1], ctx),
|
|
3266
|
+
else: lowerExprNode(args[2], ctx)
|
|
3267
|
+
};
|
|
3268
|
+
}
|
|
3269
|
+
if (fn === "field") {
|
|
3270
|
+
if (args.length !== 2) {
|
|
3271
|
+
throw unknownCallFn(fn);
|
|
3272
|
+
}
|
|
3273
|
+
const object = lowerExprNode(args[0], ctx);
|
|
3274
|
+
const property = args[1];
|
|
3275
|
+
if (property.kind !== "lit" || typeof property.value !== "string") {
|
|
3276
|
+
throw unknownCallFn(fn);
|
|
3277
|
+
}
|
|
3278
|
+
return {
|
|
3279
|
+
kind: "field",
|
|
3280
|
+
object,
|
|
3281
|
+
property: property.value
|
|
3282
|
+
};
|
|
3283
|
+
}
|
|
3284
|
+
if (isArrayArgOp(fn)) {
|
|
3285
|
+
if (args.length !== 1) {
|
|
3286
|
+
throw unknownCallFn(fn);
|
|
3287
|
+
}
|
|
3288
|
+
return {
|
|
3289
|
+
kind: fn,
|
|
3290
|
+
array: lowerExprNode(args[0], ctx)
|
|
3291
|
+
};
|
|
3292
|
+
}
|
|
3293
|
+
if (isObjArgOp(fn)) {
|
|
3294
|
+
if (args.length !== 1) {
|
|
3295
|
+
throw unknownCallFn(fn);
|
|
3296
|
+
}
|
|
3297
|
+
return {
|
|
3298
|
+
kind: fn,
|
|
3299
|
+
obj: lowerExprNode(args[0], ctx)
|
|
3300
|
+
};
|
|
3301
|
+
}
|
|
3302
|
+
if (fn === "at") {
|
|
3303
|
+
if (args.length !== 2) {
|
|
3304
|
+
throw unknownCallFn(fn);
|
|
3305
|
+
}
|
|
3306
|
+
return {
|
|
3307
|
+
kind: "at",
|
|
3308
|
+
array: lowerExprNode(args[0], ctx),
|
|
3309
|
+
index: lowerExprNode(args[1], ctx)
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
if (fn === "includes") {
|
|
3313
|
+
if (args.length !== 2) {
|
|
3314
|
+
throw unknownCallFn(fn);
|
|
3315
|
+
}
|
|
3316
|
+
return {
|
|
3317
|
+
kind: "includes",
|
|
3318
|
+
array: lowerExprNode(args[0], ctx),
|
|
3319
|
+
item: lowerExprNode(args[1], ctx)
|
|
3320
|
+
};
|
|
3321
|
+
}
|
|
3322
|
+
if (isPredicateOp(fn)) {
|
|
3323
|
+
if (args.length !== 2) {
|
|
3324
|
+
throw unknownCallFn(fn);
|
|
3325
|
+
}
|
|
3326
|
+
const predicateCtx = { ...ctx, allowItem: true };
|
|
3327
|
+
if (fn === "map") {
|
|
3328
|
+
return {
|
|
3329
|
+
kind: "map",
|
|
3330
|
+
array: lowerExprNode(args[0], ctx),
|
|
3331
|
+
mapper: lowerExprNode(args[1], predicateCtx)
|
|
3332
|
+
};
|
|
3333
|
+
}
|
|
3334
|
+
return {
|
|
3335
|
+
kind: fn,
|
|
3336
|
+
array: lowerExprNode(args[0], ctx),
|
|
3337
|
+
predicate: lowerExprNode(args[1], predicateCtx)
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3340
|
+
if (fn === "slice") {
|
|
3341
|
+
if (args.length < 2 || args.length > 3) {
|
|
3342
|
+
throw unknownCallFn(fn);
|
|
3343
|
+
}
|
|
3344
|
+
const result = {
|
|
3345
|
+
kind: "slice",
|
|
3346
|
+
array: lowerExprNode(args[0], ctx),
|
|
3347
|
+
start: lowerExprNode(args[1], ctx)
|
|
3348
|
+
};
|
|
3349
|
+
if (args.length === 3) {
|
|
3350
|
+
result.end = lowerExprNode(args[2], ctx);
|
|
3351
|
+
}
|
|
3352
|
+
return result;
|
|
3353
|
+
}
|
|
3354
|
+
if (fn === "substring") {
|
|
3355
|
+
if (args.length < 2 || args.length > 3) {
|
|
3356
|
+
throw unknownCallFn(fn);
|
|
3357
|
+
}
|
|
3358
|
+
const result = {
|
|
3359
|
+
kind: "substring",
|
|
3360
|
+
str: lowerExprNode(args[0], ctx),
|
|
3361
|
+
start: lowerExprNode(args[1], ctx)
|
|
3362
|
+
};
|
|
3363
|
+
if (args.length === 3) {
|
|
3364
|
+
result.end = lowerExprNode(args[2], ctx);
|
|
3365
|
+
}
|
|
3366
|
+
return result;
|
|
3367
|
+
}
|
|
3368
|
+
if (fn === "append") {
|
|
3369
|
+
if (args.length < 1) {
|
|
3370
|
+
throw unknownCallFn(fn);
|
|
3371
|
+
}
|
|
3372
|
+
return {
|
|
3373
|
+
kind: "append",
|
|
3374
|
+
array: lowerExprNode(args[0], ctx),
|
|
3375
|
+
items: args.slice(1).map((a) => lowerExprNode(a, ctx))
|
|
3376
|
+
};
|
|
3377
|
+
}
|
|
3378
|
+
if (fn === "merge") {
|
|
3379
|
+
return {
|
|
3380
|
+
kind: "merge",
|
|
3381
|
+
objects: args.map((a) => lowerExprNode(a, ctx))
|
|
3382
|
+
};
|
|
3383
|
+
}
|
|
3384
|
+
throw unknownCallFn(fn);
|
|
3385
|
+
}
|
|
3386
|
+
function lowerObj(input, ctx) {
|
|
3387
|
+
const fields = {};
|
|
3388
|
+
for (const field of input.fields) {
|
|
3389
|
+
fields[field.key] = lowerExprNode(field.value, ctx);
|
|
3390
|
+
}
|
|
3391
|
+
return { kind: "object", fields };
|
|
3392
|
+
}
|
|
3393
|
+
function lowerArr(input, ctx) {
|
|
3394
|
+
const allLiterals = input.elements.every((e) => e.kind === "lit");
|
|
3395
|
+
if (allLiterals) {
|
|
3396
|
+
const values = input.elements.map(
|
|
3397
|
+
(e) => e.value
|
|
3398
|
+
);
|
|
3399
|
+
return { kind: "lit", value: values };
|
|
3400
|
+
}
|
|
3401
|
+
const loweredElements = input.elements.map((e) => lowerExprNode(e, ctx));
|
|
3402
|
+
if (loweredElements.length === 0) {
|
|
3403
|
+
return { kind: "lit", value: [] };
|
|
3404
|
+
}
|
|
3405
|
+
return {
|
|
3406
|
+
kind: "append",
|
|
3407
|
+
array: { kind: "lit", value: [] },
|
|
3408
|
+
items: loweredElements
|
|
3409
|
+
};
|
|
3410
|
+
}
|
|
3411
|
+
function isBinaryOp2(fn) {
|
|
3412
|
+
return [
|
|
3413
|
+
"eq",
|
|
3414
|
+
"neq",
|
|
3415
|
+
"gt",
|
|
3416
|
+
"gte",
|
|
3417
|
+
"lt",
|
|
3418
|
+
"lte",
|
|
3419
|
+
"add",
|
|
3420
|
+
"sub",
|
|
3421
|
+
"mul",
|
|
3422
|
+
"div",
|
|
3423
|
+
"mod"
|
|
3424
|
+
].includes(fn);
|
|
3425
|
+
}
|
|
3426
|
+
function isUnaryArgOp(fn) {
|
|
3427
|
+
return ["not", "len", "typeof", "isNull"].includes(fn);
|
|
3428
|
+
}
|
|
3429
|
+
function isArgsOp(fn) {
|
|
3430
|
+
return ["and", "or", "concat", "coalesce"].includes(fn);
|
|
3431
|
+
}
|
|
3432
|
+
function isArrayArgOp(fn) {
|
|
3433
|
+
return ["first", "last"].includes(fn);
|
|
3434
|
+
}
|
|
3435
|
+
function isObjArgOp(fn) {
|
|
3436
|
+
return ["keys", "values", "entries"].includes(fn);
|
|
3437
|
+
}
|
|
3438
|
+
function isPredicateOp(fn) {
|
|
3439
|
+
return ["filter", "find", "every", "some", "map"].includes(fn);
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
// src/lowering/lower-runtime-patch.ts
|
|
3443
|
+
function lowerRuntimePatches(patches, ctx) {
|
|
3444
|
+
return patches.map((patch) => lowerRuntimePatch(patch, ctx));
|
|
3445
|
+
}
|
|
3446
|
+
function lowerRuntimePatch(patch, ctx) {
|
|
3447
|
+
const condition = patch.condition ? lowerExprNode(patch.condition, ctx) : void 0;
|
|
3448
|
+
const value = patch.value ? lowerExprNode(patch.value, ctx) : void 0;
|
|
3449
|
+
return {
|
|
3450
|
+
condition,
|
|
3451
|
+
op: patch.op,
|
|
3452
|
+
path: lowerRuntimePath(patch.path, ctx),
|
|
3453
|
+
value
|
|
3454
|
+
};
|
|
3455
|
+
}
|
|
3456
|
+
function lowerRuntimePath(path, ctx) {
|
|
3457
|
+
return path.map((segment) => {
|
|
3458
|
+
if (segment.kind === "prop") {
|
|
3459
|
+
return segment;
|
|
3460
|
+
}
|
|
3461
|
+
return { kind: "expr", expr: lowerExprNode(segment.expr, ctx) };
|
|
3462
|
+
});
|
|
3463
|
+
}
|
|
3464
|
+
|
|
3465
|
+
// src/api/compile-mel-patch-collector.ts
|
|
3466
|
+
import { sha256Sync as sha256Sync2 } from "@manifesto-ai/core";
|
|
3467
|
+
|
|
3468
|
+
// src/api/compile-mel-patch-expr.ts
|
|
3469
|
+
function isSyntheticPatchCondition(condition) {
|
|
3470
|
+
return condition.kind === "literal" && condition.literalType === "boolean" && condition.value === true;
|
|
3471
|
+
}
|
|
3472
|
+
function toMelExpr(input) {
|
|
3473
|
+
switch (input.kind) {
|
|
3474
|
+
case "literal":
|
|
3475
|
+
return { kind: "lit", value: toMelPrimitive(input.value, input.literalType) };
|
|
3476
|
+
case "identifier":
|
|
3477
|
+
return {
|
|
3478
|
+
kind: "get",
|
|
3479
|
+
path: [{ kind: "prop", name: input.name }]
|
|
3480
|
+
};
|
|
3481
|
+
case "systemIdent":
|
|
3482
|
+
return { kind: "sys", path: input.path };
|
|
3483
|
+
case "iterationVar":
|
|
3484
|
+
return { kind: "var", name: input.name };
|
|
3485
|
+
case "propertyAccess": {
|
|
3486
|
+
const path = collectStaticMelExpr(input);
|
|
3487
|
+
if (path) {
|
|
3488
|
+
return path;
|
|
3489
|
+
}
|
|
3490
|
+
return {
|
|
3491
|
+
kind: "call",
|
|
3492
|
+
fn: "field",
|
|
3493
|
+
args: [toMelExpr(input.object), { kind: "lit", value: input.property }]
|
|
3494
|
+
};
|
|
3495
|
+
}
|
|
3496
|
+
case "indexAccess": {
|
|
3497
|
+
return {
|
|
3498
|
+
kind: "call",
|
|
3499
|
+
fn: "at",
|
|
3500
|
+
args: [toMelExpr(input.object), toMelExpr(input.index)]
|
|
3501
|
+
};
|
|
3502
|
+
}
|
|
3503
|
+
case "functionCall":
|
|
3504
|
+
return {
|
|
3505
|
+
kind: "call",
|
|
3506
|
+
fn: input.name,
|
|
3507
|
+
args: input.args.map(toMelExpr)
|
|
3508
|
+
};
|
|
3509
|
+
case "unary":
|
|
3510
|
+
if (input.operator === "!") {
|
|
3511
|
+
return {
|
|
3512
|
+
kind: "call",
|
|
3513
|
+
fn: "not",
|
|
3514
|
+
args: [toMelExpr(input.operand)]
|
|
3515
|
+
};
|
|
3516
|
+
}
|
|
3517
|
+
return {
|
|
3518
|
+
kind: "call",
|
|
3519
|
+
fn: "sub",
|
|
3520
|
+
args: [{ kind: "lit", value: 0 }, toMelExpr(input.operand)]
|
|
3521
|
+
};
|
|
3522
|
+
case "binary":
|
|
3523
|
+
return {
|
|
3524
|
+
kind: "call",
|
|
3525
|
+
fn: toMelBinaryOp(input.operator),
|
|
3526
|
+
args: [toMelExpr(input.left), toMelExpr(input.right)]
|
|
3527
|
+
};
|
|
3528
|
+
case "ternary":
|
|
3529
|
+
return {
|
|
3530
|
+
kind: "call",
|
|
3531
|
+
fn: "if",
|
|
3532
|
+
args: [
|
|
3533
|
+
toMelExpr(input.condition),
|
|
3534
|
+
toMelExpr(input.consequent),
|
|
3535
|
+
toMelExpr(input.alternate)
|
|
3536
|
+
]
|
|
3537
|
+
};
|
|
3538
|
+
case "objectLiteral":
|
|
3539
|
+
return {
|
|
3540
|
+
kind: "obj",
|
|
3541
|
+
fields: input.properties.map((property) => ({
|
|
3542
|
+
key: property.key,
|
|
3543
|
+
value: toMelExpr(property.value)
|
|
3544
|
+
}))
|
|
3545
|
+
};
|
|
3546
|
+
case "arrayLiteral":
|
|
3547
|
+
return {
|
|
3548
|
+
kind: "arr",
|
|
3549
|
+
elements: input.elements.map(toMelExpr)
|
|
3550
|
+
};
|
|
3551
|
+
default:
|
|
3552
|
+
throw new Error(`Unsupported expression kind '${input.kind}'`);
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
function toMelPrimitive(value, literalType) {
|
|
3556
|
+
if (literalType === "null") {
|
|
3557
|
+
return null;
|
|
3558
|
+
}
|
|
3559
|
+
if (literalType === "number") {
|
|
3560
|
+
if (typeof value === "number") {
|
|
3561
|
+
return value;
|
|
3562
|
+
}
|
|
3563
|
+
if (typeof value === "bigint") {
|
|
3564
|
+
return Number(value);
|
|
3565
|
+
}
|
|
3566
|
+
if (typeof value === "string" && value.length > 0) {
|
|
3567
|
+
const parsed = Number(value);
|
|
3568
|
+
if (!Number.isNaN(parsed)) {
|
|
3569
|
+
return parsed;
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
throw new Error("Invalid number literal");
|
|
3573
|
+
}
|
|
3574
|
+
if (literalType === "string") {
|
|
3575
|
+
if (typeof value === "string") {
|
|
3576
|
+
return value;
|
|
3577
|
+
}
|
|
3578
|
+
throw new Error("Invalid string literal");
|
|
3579
|
+
}
|
|
3580
|
+
if (literalType === "boolean") {
|
|
3581
|
+
if (typeof value === "boolean") {
|
|
3582
|
+
return value;
|
|
3583
|
+
}
|
|
3584
|
+
throw new Error("Invalid boolean literal");
|
|
3585
|
+
}
|
|
3586
|
+
throw new Error("Unsupported literal type");
|
|
3587
|
+
}
|
|
3588
|
+
function collectStaticMelExpr(expr) {
|
|
3589
|
+
if (expr.kind === "identifier") {
|
|
3590
|
+
return { kind: "get", path: [{ kind: "prop", name: expr.name }] };
|
|
3591
|
+
}
|
|
3592
|
+
if (expr.kind === "iterationVar") {
|
|
3593
|
+
if (expr.name !== "item") {
|
|
3594
|
+
return null;
|
|
3595
|
+
}
|
|
3596
|
+
return { kind: "var", name: "item" };
|
|
3597
|
+
}
|
|
3598
|
+
if (expr.kind === "propertyAccess") {
|
|
3599
|
+
const basePath = collectStaticMelExpr(expr.object);
|
|
3600
|
+
if (!basePath) {
|
|
3601
|
+
return null;
|
|
3602
|
+
}
|
|
3603
|
+
return {
|
|
3604
|
+
kind: "call",
|
|
3605
|
+
fn: "field",
|
|
3606
|
+
args: [basePath, { kind: "lit", value: expr.property }]
|
|
3607
|
+
};
|
|
3608
|
+
}
|
|
3609
|
+
return null;
|
|
3610
|
+
}
|
|
3611
|
+
function toMelBinaryOp(op) {
|
|
3612
|
+
switch (op) {
|
|
3613
|
+
case "+":
|
|
3614
|
+
return "add";
|
|
3615
|
+
case "-":
|
|
3616
|
+
return "sub";
|
|
3617
|
+
case "*":
|
|
3618
|
+
return "mul";
|
|
3619
|
+
case "/":
|
|
3620
|
+
return "div";
|
|
3621
|
+
case "%":
|
|
3622
|
+
return "mod";
|
|
3623
|
+
case "==":
|
|
3624
|
+
return "eq";
|
|
3625
|
+
case "!=":
|
|
3626
|
+
return "neq";
|
|
3627
|
+
case "<":
|
|
3628
|
+
return "lt";
|
|
3629
|
+
case "<=":
|
|
3630
|
+
return "lte";
|
|
3631
|
+
case ">":
|
|
3632
|
+
return "gt";
|
|
3633
|
+
case ">=":
|
|
3634
|
+
return "gte";
|
|
3635
|
+
case "&&":
|
|
3636
|
+
return "and";
|
|
3637
|
+
case "||":
|
|
3638
|
+
return "or";
|
|
3639
|
+
case "??":
|
|
3640
|
+
return "coalesce";
|
|
3641
|
+
default:
|
|
3642
|
+
throw new Error(`Unsupported binary operator '${op}'`);
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
|
|
3646
|
+
// src/api/compile-mel-patch-collector.ts
|
|
3647
|
+
var PatchStatementCollector = class {
|
|
3648
|
+
constructor(deps) {
|
|
3649
|
+
this.deps = deps;
|
|
3650
|
+
}
|
|
3651
|
+
conditionComposer = new ConditionComposer();
|
|
3652
|
+
collect(stmts, errors, context, parentCondition) {
|
|
3653
|
+
return this.collectPatchStatements(stmts, errors, context, parentCondition);
|
|
3654
|
+
}
|
|
3655
|
+
collectPatchStatements(stmts, errors, context, parentCondition) {
|
|
3656
|
+
const patchStatements = [];
|
|
3657
|
+
for (const stmt of stmts) {
|
|
3658
|
+
if (stmt.kind === "patch") {
|
|
3659
|
+
patchStatements.push({
|
|
3660
|
+
patch: stmt,
|
|
3661
|
+
condition: parentCondition
|
|
3662
|
+
});
|
|
3663
|
+
continue;
|
|
3664
|
+
}
|
|
3665
|
+
if (stmt.kind === "when") {
|
|
3666
|
+
let condition = parentCondition;
|
|
3667
|
+
try {
|
|
3668
|
+
condition = this.conditionComposer.and(
|
|
3669
|
+
parentCondition,
|
|
3670
|
+
this.deps.toMelExpr(stmt.condition)
|
|
3671
|
+
);
|
|
3672
|
+
} catch (error) {
|
|
3673
|
+
errors.push({
|
|
3674
|
+
severity: "error",
|
|
3675
|
+
code: "E_LOWER",
|
|
3676
|
+
message: error.message,
|
|
3677
|
+
location: this.deps.mapLocation(stmt.condition.location)
|
|
3678
|
+
});
|
|
3679
|
+
}
|
|
3680
|
+
const whenMarkerId = sha256Sync2(
|
|
3681
|
+
`${context.actionName}:${context.whenCounter}:when`
|
|
3682
|
+
);
|
|
3683
|
+
context.whenCounter += 1;
|
|
3684
|
+
const whenMarkerPath = {
|
|
3685
|
+
kind: "path",
|
|
3686
|
+
segments: [
|
|
3687
|
+
{
|
|
3688
|
+
kind: "propertySegment",
|
|
3689
|
+
name: "$mel",
|
|
3690
|
+
location: stmt.location
|
|
3691
|
+
},
|
|
3692
|
+
{
|
|
3693
|
+
kind: "propertySegment",
|
|
3694
|
+
name: "__whenGuards",
|
|
3695
|
+
location: stmt.location
|
|
3696
|
+
},
|
|
3697
|
+
{
|
|
3698
|
+
kind: "propertySegment",
|
|
3699
|
+
name: whenMarkerId,
|
|
3700
|
+
location: stmt.location
|
|
3701
|
+
}
|
|
3702
|
+
],
|
|
3703
|
+
location: stmt.location
|
|
3704
|
+
};
|
|
3705
|
+
const whenMarkerExpr = pathToMelExpr(whenMarkerPath);
|
|
3706
|
+
const guardedBody = this.collectPatchStatements(
|
|
3707
|
+
stmt.body,
|
|
3708
|
+
errors,
|
|
3709
|
+
context,
|
|
3710
|
+
void 0
|
|
3711
|
+
).map((statement) => ({
|
|
3712
|
+
patch: statement.patch,
|
|
3713
|
+
condition: this.conditionComposer.and(whenMarkerExpr, statement.condition)
|
|
3714
|
+
}));
|
|
3715
|
+
patchStatements.push(
|
|
3716
|
+
{
|
|
3717
|
+
patch: {
|
|
3718
|
+
kind: "patch",
|
|
3719
|
+
op: "set",
|
|
3720
|
+
path: whenMarkerPath,
|
|
3721
|
+
value: {
|
|
3722
|
+
kind: "literal",
|
|
3723
|
+
literalType: "boolean",
|
|
3724
|
+
value: true,
|
|
3725
|
+
location: stmt.location
|
|
3726
|
+
},
|
|
3727
|
+
location: stmt.location
|
|
3728
|
+
},
|
|
3729
|
+
condition
|
|
3730
|
+
},
|
|
3731
|
+
...guardedBody,
|
|
3732
|
+
{
|
|
3733
|
+
patch: {
|
|
3734
|
+
kind: "patch",
|
|
3735
|
+
op: "unset",
|
|
3736
|
+
path: whenMarkerPath,
|
|
3737
|
+
location: stmt.location
|
|
3738
|
+
}
|
|
3739
|
+
}
|
|
3740
|
+
);
|
|
3741
|
+
continue;
|
|
3742
|
+
}
|
|
3743
|
+
if (stmt.kind === "once") {
|
|
3744
|
+
let condition = parentCondition;
|
|
3745
|
+
const markerExpr = pathToMelExpr(stmt.marker);
|
|
3746
|
+
const markerPath = stmt.marker;
|
|
3747
|
+
const markerLocation = stmt.location;
|
|
3748
|
+
let onceCondition = {
|
|
3749
|
+
kind: "call",
|
|
3750
|
+
fn: "neq",
|
|
3751
|
+
args: [markerExpr, { kind: "sys", path: ["meta", "intentId"] }]
|
|
3752
|
+
};
|
|
3753
|
+
if (stmt.condition) {
|
|
3754
|
+
try {
|
|
3755
|
+
onceCondition = this.conditionComposer.and(
|
|
3756
|
+
onceCondition,
|
|
3757
|
+
this.deps.toMelExpr(stmt.condition)
|
|
3758
|
+
) ?? onceCondition;
|
|
3759
|
+
} catch (error) {
|
|
3760
|
+
errors.push({
|
|
3761
|
+
severity: "error",
|
|
3762
|
+
code: "E_LOWER",
|
|
3763
|
+
message: error.message,
|
|
3764
|
+
location: this.deps.mapLocation(stmt.condition.location)
|
|
3765
|
+
});
|
|
3766
|
+
}
|
|
3767
|
+
}
|
|
3768
|
+
condition = this.conditionComposer.and(parentCondition, onceCondition);
|
|
3769
|
+
const onceScopeMarkerId = sha256Sync2(
|
|
3770
|
+
`${context.actionName}:${context.onceCounter}:once`
|
|
3771
|
+
);
|
|
3772
|
+
context.onceCounter += 1;
|
|
3773
|
+
const onceScopeMarkerPath = {
|
|
3774
|
+
kind: "path",
|
|
3775
|
+
segments: [
|
|
3776
|
+
{
|
|
3777
|
+
kind: "propertySegment",
|
|
3778
|
+
name: "$mel",
|
|
3779
|
+
location: markerLocation
|
|
3780
|
+
},
|
|
3781
|
+
{
|
|
3782
|
+
kind: "propertySegment",
|
|
3783
|
+
name: "__onceScopeGuards",
|
|
3784
|
+
location: markerLocation
|
|
3785
|
+
},
|
|
3786
|
+
{
|
|
3787
|
+
kind: "propertySegment",
|
|
3788
|
+
name: onceScopeMarkerId,
|
|
3789
|
+
location: markerLocation
|
|
3790
|
+
}
|
|
3791
|
+
],
|
|
3792
|
+
location: markerLocation
|
|
3793
|
+
};
|
|
3794
|
+
const onceScopeMarkerExpr = pathToMelExpr(onceScopeMarkerPath);
|
|
3795
|
+
const scopedBodyPatchStatements = this.collectPatchStatements(
|
|
3796
|
+
stmt.body,
|
|
3797
|
+
errors,
|
|
3798
|
+
context,
|
|
3799
|
+
onceScopeMarkerExpr
|
|
3800
|
+
);
|
|
3801
|
+
patchStatements.push(
|
|
3802
|
+
{
|
|
3803
|
+
patch: {
|
|
3804
|
+
kind: "patch",
|
|
3805
|
+
op: "set",
|
|
3806
|
+
path: onceScopeMarkerPath,
|
|
3807
|
+
value: {
|
|
3808
|
+
kind: "literal",
|
|
3809
|
+
literalType: "boolean",
|
|
3810
|
+
value: true,
|
|
3811
|
+
location: markerLocation
|
|
3812
|
+
},
|
|
3813
|
+
location: markerLocation
|
|
3814
|
+
},
|
|
3815
|
+
condition
|
|
3816
|
+
},
|
|
3817
|
+
{
|
|
3818
|
+
patch: {
|
|
3819
|
+
kind: "patch",
|
|
3820
|
+
op: "set",
|
|
3821
|
+
path: markerPath,
|
|
3822
|
+
value: {
|
|
3823
|
+
kind: "systemIdent",
|
|
3824
|
+
path: ["meta", "intentId"],
|
|
3825
|
+
location: markerLocation
|
|
3826
|
+
},
|
|
3827
|
+
location: markerLocation
|
|
3828
|
+
},
|
|
3829
|
+
condition: onceScopeMarkerExpr
|
|
3830
|
+
},
|
|
3831
|
+
...scopedBodyPatchStatements,
|
|
3832
|
+
{
|
|
3833
|
+
patch: {
|
|
3834
|
+
kind: "patch",
|
|
3835
|
+
op: "unset",
|
|
3836
|
+
path: onceScopeMarkerPath,
|
|
3837
|
+
location: markerLocation
|
|
3838
|
+
},
|
|
3839
|
+
condition
|
|
3840
|
+
}
|
|
3841
|
+
);
|
|
3842
|
+
continue;
|
|
3843
|
+
}
|
|
3844
|
+
if (stmt.kind === "onceIntent") {
|
|
3845
|
+
const markerScopeId = sha256Sync2(
|
|
3846
|
+
`${context.actionName}:${context.onceCounter}:onceIntent`
|
|
3847
|
+
);
|
|
3848
|
+
context.onceCounter += 1;
|
|
3849
|
+
const markerScopePath = {
|
|
3850
|
+
kind: "path",
|
|
3851
|
+
segments: [
|
|
3852
|
+
{
|
|
3853
|
+
kind: "propertySegment",
|
|
3854
|
+
name: "$mel",
|
|
3855
|
+
location: stmt.location
|
|
3856
|
+
},
|
|
3857
|
+
{
|
|
3858
|
+
kind: "propertySegment",
|
|
3859
|
+
name: "__onceScopeGuards",
|
|
3860
|
+
location: stmt.location
|
|
3861
|
+
},
|
|
3862
|
+
{
|
|
3863
|
+
kind: "propertySegment",
|
|
3864
|
+
name: markerScopeId,
|
|
3865
|
+
location: stmt.location
|
|
3866
|
+
}
|
|
3867
|
+
],
|
|
3868
|
+
location: stmt.location
|
|
3869
|
+
};
|
|
3870
|
+
const markerScopeExpr = pathToMelExpr(markerScopePath);
|
|
3871
|
+
const onceIntentGuardId = sha256Sync2(
|
|
3872
|
+
`${context.actionName}:${context.onceIntentCounter}:intent`
|
|
3873
|
+
);
|
|
3874
|
+
context.onceIntentCounter += 1;
|
|
3875
|
+
const markerLocation = stmt.location;
|
|
3876
|
+
const onceIntentGuardPath = {
|
|
3877
|
+
kind: "path",
|
|
3878
|
+
segments: [
|
|
3879
|
+
{
|
|
3880
|
+
kind: "propertySegment",
|
|
3881
|
+
name: "$mel",
|
|
3882
|
+
location: markerLocation
|
|
3883
|
+
},
|
|
3884
|
+
{
|
|
3885
|
+
kind: "propertySegment",
|
|
3886
|
+
name: "guards",
|
|
3887
|
+
location: markerLocation
|
|
3888
|
+
},
|
|
3889
|
+
{
|
|
3890
|
+
kind: "propertySegment",
|
|
3891
|
+
name: "intent",
|
|
3892
|
+
location: markerLocation
|
|
3893
|
+
},
|
|
3894
|
+
{
|
|
3895
|
+
kind: "propertySegment",
|
|
3896
|
+
name: onceIntentGuardId,
|
|
3897
|
+
location: markerLocation
|
|
3898
|
+
}
|
|
3899
|
+
],
|
|
3900
|
+
location: markerLocation
|
|
3901
|
+
};
|
|
3902
|
+
let onceIntentCondition = {
|
|
3903
|
+
kind: "call",
|
|
3904
|
+
fn: "neq",
|
|
3905
|
+
args: [
|
|
3906
|
+
pathToMelExpr(onceIntentGuardPath),
|
|
3907
|
+
{ kind: "sys", path: ["meta", "intentId"] }
|
|
3908
|
+
]
|
|
3909
|
+
};
|
|
3910
|
+
if (stmt.condition) {
|
|
3911
|
+
try {
|
|
3912
|
+
onceIntentCondition = this.conditionComposer.and(
|
|
3913
|
+
onceIntentCondition,
|
|
3914
|
+
this.deps.toMelExpr(stmt.condition)
|
|
3915
|
+
) ?? onceIntentCondition;
|
|
3916
|
+
} catch (error) {
|
|
3917
|
+
errors.push({
|
|
3918
|
+
severity: "error",
|
|
3919
|
+
code: "E_LOWER",
|
|
3920
|
+
message: error.message,
|
|
3921
|
+
location: this.deps.mapLocation(stmt.condition.location)
|
|
3922
|
+
});
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
const condition = this.conditionComposer.and(parentCondition, onceIntentCondition);
|
|
3926
|
+
const onceIntentGuardMapPath = {
|
|
3927
|
+
kind: "path",
|
|
3928
|
+
segments: [
|
|
3929
|
+
{
|
|
3930
|
+
kind: "propertySegment",
|
|
3931
|
+
name: "$mel",
|
|
3932
|
+
location: markerLocation
|
|
3933
|
+
},
|
|
3934
|
+
{
|
|
3935
|
+
kind: "propertySegment",
|
|
3936
|
+
name: "guards",
|
|
3937
|
+
location: markerLocation
|
|
3938
|
+
},
|
|
3939
|
+
{
|
|
3940
|
+
kind: "propertySegment",
|
|
3941
|
+
name: "intent",
|
|
3942
|
+
location: markerLocation
|
|
3943
|
+
}
|
|
3944
|
+
],
|
|
3945
|
+
location: markerLocation
|
|
3946
|
+
};
|
|
3947
|
+
const scopedBodyPatchStatements = this.collectPatchStatements(
|
|
3948
|
+
stmt.body,
|
|
3949
|
+
errors,
|
|
3950
|
+
context,
|
|
3951
|
+
markerScopeExpr
|
|
3952
|
+
);
|
|
3953
|
+
patchStatements.push(
|
|
3954
|
+
{
|
|
3955
|
+
patch: {
|
|
3956
|
+
kind: "patch",
|
|
3957
|
+
op: "set",
|
|
3958
|
+
path: markerScopePath,
|
|
3959
|
+
value: {
|
|
3960
|
+
kind: "literal",
|
|
3961
|
+
literalType: "boolean",
|
|
3962
|
+
value: true,
|
|
3963
|
+
location: markerLocation
|
|
3964
|
+
},
|
|
3965
|
+
location: markerLocation
|
|
3966
|
+
},
|
|
3967
|
+
condition
|
|
3968
|
+
},
|
|
3969
|
+
{
|
|
3970
|
+
patch: {
|
|
3971
|
+
kind: "patch",
|
|
3972
|
+
op: "merge",
|
|
3973
|
+
path: onceIntentGuardMapPath,
|
|
3974
|
+
value: {
|
|
3975
|
+
kind: "objectLiteral",
|
|
3976
|
+
properties: [
|
|
3977
|
+
{
|
|
3978
|
+
kind: "objectProperty",
|
|
3979
|
+
key: onceIntentGuardId,
|
|
3980
|
+
value: {
|
|
3981
|
+
kind: "systemIdent",
|
|
3982
|
+
path: ["meta", "intentId"],
|
|
3983
|
+
location: markerLocation
|
|
3984
|
+
},
|
|
3985
|
+
location: markerLocation
|
|
3986
|
+
}
|
|
3987
|
+
],
|
|
3988
|
+
location: markerLocation
|
|
3989
|
+
},
|
|
3990
|
+
location: markerLocation
|
|
3991
|
+
},
|
|
3992
|
+
condition: markerScopeExpr
|
|
3993
|
+
},
|
|
3994
|
+
...scopedBodyPatchStatements,
|
|
3995
|
+
{
|
|
3996
|
+
patch: {
|
|
3997
|
+
kind: "patch",
|
|
3998
|
+
op: "unset",
|
|
3999
|
+
path: markerScopePath,
|
|
4000
|
+
location: markerLocation
|
|
4001
|
+
},
|
|
4002
|
+
condition
|
|
4003
|
+
}
|
|
4004
|
+
);
|
|
4005
|
+
continue;
|
|
4006
|
+
}
|
|
4007
|
+
errors.push({
|
|
4008
|
+
severity: "error",
|
|
4009
|
+
code: "E_UNSUPPORTED_STMT",
|
|
4010
|
+
message: `Unsupported statement '${stmt.kind}' in patch text. Only patch statements are allowed.`,
|
|
4011
|
+
location: this.deps.mapLocation(stmt.location)
|
|
4012
|
+
});
|
|
4013
|
+
}
|
|
4014
|
+
return patchStatements;
|
|
4015
|
+
}
|
|
4016
|
+
};
|
|
4017
|
+
function compilePatchStmtToMelRuntime(patchStatement) {
|
|
4018
|
+
return {
|
|
4019
|
+
op: patchStatement.patch.op,
|
|
4020
|
+
path: toRuntimePatchPath(patchStatement.patch.path),
|
|
4021
|
+
...patchStatement.condition ? { condition: patchStatement.condition } : void 0,
|
|
4022
|
+
...patchStatement.patch.value ? { value: toMelExpr(patchStatement.patch.value) } : void 0
|
|
4023
|
+
};
|
|
4024
|
+
}
|
|
4025
|
+
function pathToMelExpr(path) {
|
|
4026
|
+
const segments = path.segments;
|
|
4027
|
+
let result;
|
|
4028
|
+
for (const segment of segments) {
|
|
4029
|
+
if (segment.kind === "propertySegment") {
|
|
4030
|
+
const prop = { kind: "prop", name: segment.name };
|
|
4031
|
+
if (!result) {
|
|
4032
|
+
result = { kind: "get", path: [prop] };
|
|
4033
|
+
} else {
|
|
4034
|
+
result = {
|
|
4035
|
+
kind: "call",
|
|
4036
|
+
fn: "field",
|
|
4037
|
+
args: [result, { kind: "lit", value: segment.name }]
|
|
4038
|
+
};
|
|
4039
|
+
}
|
|
4040
|
+
continue;
|
|
4041
|
+
}
|
|
4042
|
+
if (!result) {
|
|
4043
|
+
throw new Error("Path cannot start with index access in compileMelPatch guard.");
|
|
4044
|
+
}
|
|
4045
|
+
result = {
|
|
4046
|
+
kind: "call",
|
|
4047
|
+
fn: "at",
|
|
4048
|
+
args: [result, toMelExpr(segment.index)]
|
|
4049
|
+
};
|
|
4050
|
+
}
|
|
4051
|
+
if (!result) {
|
|
4052
|
+
throw new Error("Empty patch guard path.");
|
|
4053
|
+
}
|
|
4054
|
+
return result;
|
|
4055
|
+
}
|
|
4056
|
+
function toRuntimePatchPath(path) {
|
|
4057
|
+
return path.segments.map((segment) => {
|
|
4058
|
+
if (segment.kind === "propertySegment") {
|
|
4059
|
+
return { kind: "prop", name: segment.name };
|
|
4060
|
+
}
|
|
4061
|
+
return { kind: "expr", expr: toMelExpr(segment.index) };
|
|
4062
|
+
});
|
|
4063
|
+
}
|
|
4064
|
+
var ConditionComposer = class {
|
|
4065
|
+
and(lhs, rhs) {
|
|
4066
|
+
if (!lhs) {
|
|
4067
|
+
return rhs;
|
|
4068
|
+
}
|
|
4069
|
+
if (!rhs) {
|
|
4070
|
+
return lhs;
|
|
4071
|
+
}
|
|
4072
|
+
return {
|
|
4073
|
+
kind: "call",
|
|
4074
|
+
fn: "and",
|
|
4075
|
+
args: [lhs, rhs]
|
|
4076
|
+
};
|
|
4077
|
+
}
|
|
4078
|
+
};
|
|
4079
|
+
|
|
4080
|
+
// src/api/compile-mel-patch-location.ts
|
|
4081
|
+
var SYNTHETIC_PATCH_PREFIX_LINES = 3;
|
|
4082
|
+
var SYNTHETIC_PATCH_PREFIX_COLUMNS = 6;
|
|
4083
|
+
function computeLineStartOffsets(lines) {
|
|
4084
|
+
const offsets = [];
|
|
4085
|
+
let offset = 0;
|
|
4086
|
+
for (const line of lines) {
|
|
4087
|
+
offsets.push(offset);
|
|
4088
|
+
offset += line.length + 1;
|
|
4089
|
+
}
|
|
4090
|
+
return offsets;
|
|
4091
|
+
}
|
|
4092
|
+
function makePatchLocationMapper(patchLines, patchLineStarts) {
|
|
4093
|
+
return (location) => remapLocationToPatchSource(location, patchLines, patchLineStarts);
|
|
4094
|
+
}
|
|
4095
|
+
function remapDiagnosticsToPatchSource(diagnostics, patchLines, patchLineStarts) {
|
|
4096
|
+
return diagnostics.map((diagnostic) => remapDiagnosticToPatchSource(
|
|
4097
|
+
diagnostic,
|
|
4098
|
+
patchLines,
|
|
4099
|
+
patchLineStarts
|
|
4100
|
+
));
|
|
4101
|
+
}
|
|
4102
|
+
function remapDiagnosticToPatchSource(diagnostic, patchLines, patchLineStarts) {
|
|
4103
|
+
return {
|
|
4104
|
+
...diagnostic,
|
|
4105
|
+
location: remapLocationToPatchSource(
|
|
4106
|
+
diagnostic.location,
|
|
4107
|
+
patchLines,
|
|
4108
|
+
patchLineStarts
|
|
4109
|
+
)
|
|
4110
|
+
};
|
|
4111
|
+
}
|
|
4112
|
+
function remapLocationToPatchSource(location, patchLines, patchLineStarts) {
|
|
4113
|
+
return {
|
|
4114
|
+
...location,
|
|
4115
|
+
start: remapPositionToPatchSource(location.start, patchLines, patchLineStarts),
|
|
4116
|
+
end: remapPositionToPatchSource(location.end, patchLines, patchLineStarts)
|
|
4117
|
+
};
|
|
4118
|
+
}
|
|
4119
|
+
function remapPositionToPatchSource(position, patchLines, patchLineStarts) {
|
|
4120
|
+
const patchLine = position.line - SYNTHETIC_PATCH_PREFIX_LINES;
|
|
4121
|
+
const patchLineCount = patchLines.length;
|
|
4122
|
+
const clampedPatchLine = Math.min(Math.max(patchLine, 1), patchLineCount);
|
|
4123
|
+
const sourceLineStart = patchLineStarts[clampedPatchLine - 1] ?? 0;
|
|
4124
|
+
const sourceLineText = patchLines[clampedPatchLine - 1] ?? "";
|
|
4125
|
+
const maxSourceColumn = Math.max(sourceLineText.length + 1, 1);
|
|
4126
|
+
const sourceColumn = Math.min(
|
|
4127
|
+
Math.max(position.column - SYNTHETIC_PATCH_PREFIX_COLUMNS, 1),
|
|
4128
|
+
maxSourceColumn
|
|
4129
|
+
);
|
|
4130
|
+
return {
|
|
4131
|
+
line: clampedPatchLine,
|
|
4132
|
+
column: sourceColumn,
|
|
4133
|
+
offset: Math.max(sourceLineStart + sourceColumn - 1, 0)
|
|
4134
|
+
};
|
|
4135
|
+
}
|
|
4136
|
+
|
|
4137
|
+
// src/api/compile-mel-patch.ts
|
|
4138
|
+
var SYNTHETIC_ACTION = "__compileMelPatch";
|
|
4139
|
+
var SYNTHETIC_DOMAIN = "__patchDomain";
|
|
4140
|
+
var SYNTHETIC_PATCH_INDENT = " ";
|
|
4141
|
+
function compileMelPatchText(melText, options) {
|
|
4142
|
+
const trace = [];
|
|
4143
|
+
const warnings = [];
|
|
4144
|
+
const errors = [];
|
|
4145
|
+
const patchLines = melText.split("\n");
|
|
4146
|
+
const patchLineStarts = computeLineStartOffsets(patchLines);
|
|
4147
|
+
const mapLocation = makePatchLocationMapper(patchLines, patchLineStarts);
|
|
4148
|
+
const syntheticMel = buildSyntheticPatchProgram(melText, SYNTHETIC_ACTION);
|
|
4149
|
+
const lexStart = performance.now();
|
|
4150
|
+
let tokens;
|
|
4151
|
+
try {
|
|
4152
|
+
const lexResult = tokenize(syntheticMel);
|
|
4153
|
+
tokens = lexResult.tokens;
|
|
4154
|
+
const lexErrors = remapDiagnosticsToPatchSource(
|
|
4155
|
+
lexResult.diagnostics.filter((d) => d.severity === "error"),
|
|
4156
|
+
patchLines,
|
|
4157
|
+
patchLineStarts
|
|
4158
|
+
);
|
|
4159
|
+
if (lexErrors.length > 0) {
|
|
4160
|
+
errors.push(...lexErrors);
|
|
4161
|
+
trace.push({
|
|
4162
|
+
phase: "lex",
|
|
4163
|
+
durationMs: performance.now() - lexStart,
|
|
4164
|
+
details: { tokenCount: tokens.length }
|
|
4165
|
+
});
|
|
4166
|
+
return { ops: [], trace, warnings, errors };
|
|
4167
|
+
}
|
|
4168
|
+
} catch (error) {
|
|
4169
|
+
errors.push({
|
|
4170
|
+
severity: "error",
|
|
4171
|
+
code: "E_LEX",
|
|
4172
|
+
message: error.message,
|
|
4173
|
+
location: {
|
|
4174
|
+
start: { line: 1, column: 1, offset: 0 },
|
|
4175
|
+
end: { line: 1, column: 1, offset: 0 }
|
|
4176
|
+
}
|
|
4177
|
+
});
|
|
4178
|
+
trace.push({ phase: "lex", durationMs: performance.now() - lexStart });
|
|
4179
|
+
return { ops: [], trace, warnings, errors };
|
|
4180
|
+
}
|
|
4181
|
+
trace.push({
|
|
4182
|
+
phase: "lex",
|
|
4183
|
+
durationMs: performance.now() - lexStart,
|
|
4184
|
+
details: { tokenCount: tokens.length }
|
|
4185
|
+
});
|
|
4186
|
+
const parseStart = performance.now();
|
|
4187
|
+
let program;
|
|
4188
|
+
try {
|
|
4189
|
+
const parseResult = parse(tokens);
|
|
4190
|
+
const parsedDiagnostics = remapDiagnosticsToPatchSource(
|
|
4191
|
+
parseResult.diagnostics,
|
|
4192
|
+
patchLines,
|
|
4193
|
+
patchLineStarts
|
|
4194
|
+
);
|
|
4195
|
+
const parseErrors = parsedDiagnostics.filter((d) => d.severity === "error");
|
|
4196
|
+
const parseWarnings = parsedDiagnostics.filter((d) => d.severity !== "error");
|
|
4197
|
+
if (parseErrors.length > 0) {
|
|
4198
|
+
errors.push(...parseErrors);
|
|
4199
|
+
warnings.push(...parseWarnings);
|
|
4200
|
+
trace.push({ phase: "parse", durationMs: performance.now() - parseStart });
|
|
4201
|
+
return { ops: [], trace, warnings, errors };
|
|
4202
|
+
}
|
|
4203
|
+
if (!parseResult.program) {
|
|
4204
|
+
errors.push({
|
|
4205
|
+
severity: "error",
|
|
4206
|
+
code: "E_PARSE",
|
|
4207
|
+
message: "Failed to parse MEL patch program",
|
|
4208
|
+
location: {
|
|
4209
|
+
start: { line: 1, column: 1, offset: 0 },
|
|
4210
|
+
end: { line: 1, column: 1, offset: 0 }
|
|
4211
|
+
}
|
|
4212
|
+
});
|
|
4213
|
+
warnings.push(...parseWarnings);
|
|
4214
|
+
trace.push({ phase: "parse", durationMs: performance.now() - parseStart });
|
|
4215
|
+
return { ops: [], trace, warnings, errors };
|
|
4216
|
+
}
|
|
4217
|
+
program = parseResult.program;
|
|
4218
|
+
warnings.push(...parseWarnings);
|
|
4219
|
+
} catch (error) {
|
|
4220
|
+
errors.push({
|
|
4221
|
+
severity: "error",
|
|
4222
|
+
code: "E_PARSE",
|
|
4223
|
+
message: error.message,
|
|
4224
|
+
location: {
|
|
4225
|
+
start: { line: 1, column: 1, offset: 0 },
|
|
4226
|
+
end: { line: 1, column: 1, offset: 0 }
|
|
4227
|
+
}
|
|
4228
|
+
});
|
|
4229
|
+
trace.push({ phase: "parse", durationMs: performance.now() - parseStart });
|
|
4230
|
+
return { ops: [], trace, warnings, errors };
|
|
4231
|
+
}
|
|
4232
|
+
trace.push({ phase: "parse", durationMs: performance.now() - parseStart });
|
|
4233
|
+
const analyzeStart = performance.now();
|
|
4234
|
+
const collectContext = {
|
|
4235
|
+
actionName: options.actionName,
|
|
4236
|
+
onceCounter: 0,
|
|
4237
|
+
onceIntentCounter: 0,
|
|
4238
|
+
whenCounter: 0
|
|
4239
|
+
};
|
|
4240
|
+
const action = program.domain.members.find(
|
|
4241
|
+
(member) => member.kind === "action" && member.name === SYNTHETIC_ACTION
|
|
4242
|
+
);
|
|
4243
|
+
if (!action) {
|
|
4244
|
+
errors.push({
|
|
4245
|
+
severity: "error",
|
|
4246
|
+
code: "E_ANALYZE",
|
|
4247
|
+
message: `Synthetic patch action '${SYNTHETIC_ACTION}' not found during parsing`,
|
|
4248
|
+
location: {
|
|
4249
|
+
start: { line: 1, column: 1, offset: 0 },
|
|
4250
|
+
end: { line: 1, column: 1, offset: 0 }
|
|
4251
|
+
}
|
|
4252
|
+
});
|
|
4253
|
+
trace.push({ phase: "analyze", durationMs: performance.now() - analyzeStart });
|
|
4254
|
+
return { ops: [], trace, warnings, errors };
|
|
4255
|
+
}
|
|
4256
|
+
const expectedActionEndLine = patchLines.length + SYNTHETIC_PATCH_PREFIX_LINES + 2;
|
|
4257
|
+
if (action.location.end.line !== expectedActionEndLine) {
|
|
4258
|
+
errors.push({
|
|
4259
|
+
severity: "error",
|
|
4260
|
+
code: "E_PATCH_WRAPPER",
|
|
4261
|
+
message: `Malformed synthetic patch wrapper for action '${SYNTHETIC_ACTION}'.`,
|
|
4262
|
+
location: mapLocation(action.location)
|
|
4263
|
+
});
|
|
4264
|
+
trace.push({ phase: "analyze", durationMs: performance.now() - analyzeStart });
|
|
4265
|
+
return { ops: [], trace, warnings, errors };
|
|
4266
|
+
}
|
|
4267
|
+
const [patchRoot] = action.body;
|
|
4268
|
+
if (!patchRoot || patchRoot.kind !== "when" || !isSyntheticPatchCondition(patchRoot.condition) || action.body.length !== 1) {
|
|
4269
|
+
errors.push({
|
|
4270
|
+
severity: "error",
|
|
4271
|
+
code: "E_PATCH_WRAPPER",
|
|
4272
|
+
message: `Malformed synthetic patch wrapper for action '${SYNTHETIC_ACTION}'.`,
|
|
4273
|
+
location: mapLocation(action.location)
|
|
4274
|
+
});
|
|
4275
|
+
trace.push({ phase: "analyze", durationMs: performance.now() - analyzeStart });
|
|
4276
|
+
return { ops: [], trace, warnings, errors };
|
|
4277
|
+
}
|
|
4278
|
+
const collector = new PatchStatementCollector({
|
|
4279
|
+
mapLocation,
|
|
4280
|
+
toMelExpr
|
|
4281
|
+
});
|
|
4282
|
+
const patchStatements = collector.collect(
|
|
4283
|
+
patchRoot.body,
|
|
4284
|
+
errors,
|
|
4285
|
+
collectContext,
|
|
4286
|
+
void 0
|
|
4287
|
+
);
|
|
4288
|
+
if (errors.length === 0 && melText.trim() !== "" && patchStatements.length === 0) {
|
|
4289
|
+
errors.push({
|
|
4290
|
+
severity: "error",
|
|
4291
|
+
code: "E_PATCH_WRAPPER",
|
|
4292
|
+
message: "Patch wrapper parsing produced no patch statements. The patch source may be malformed.",
|
|
4293
|
+
location: mapLocation(patchRoot.location)
|
|
4294
|
+
});
|
|
4295
|
+
}
|
|
4296
|
+
trace.push({
|
|
4297
|
+
phase: "analyze",
|
|
4298
|
+
durationMs: performance.now() - analyzeStart,
|
|
4299
|
+
details: { count: patchStatements.length }
|
|
4300
|
+
});
|
|
4301
|
+
if (errors.length > 0) {
|
|
4302
|
+
return { ops: [], trace, warnings, errors };
|
|
4303
|
+
}
|
|
4304
|
+
const lowerStart = performance.now();
|
|
4305
|
+
const loweringContext = {
|
|
4306
|
+
mode: "action",
|
|
4307
|
+
allowSysPaths: options.allowSysPaths ?? { prefixes: ["meta", "input"] },
|
|
4308
|
+
fnTableVersion: options.fnTableVersion ?? "1.0",
|
|
4309
|
+
actionName: options.actionName
|
|
4310
|
+
};
|
|
4311
|
+
const loweredOps = [];
|
|
4312
|
+
for (const patchStatement of patchStatements) {
|
|
4313
|
+
try {
|
|
4314
|
+
const melPatch = compilePatchStmtToMelRuntime(patchStatement);
|
|
4315
|
+
loweredOps.push(lowerRuntimePatch(melPatch, loweringContext));
|
|
4316
|
+
} catch (error) {
|
|
4317
|
+
if (error instanceof LoweringError) {
|
|
4318
|
+
errors.push({
|
|
4319
|
+
severity: "error",
|
|
4320
|
+
code: error.code,
|
|
4321
|
+
message: error.message,
|
|
4322
|
+
location: mapLocation(patchStatement.patch.location)
|
|
4323
|
+
});
|
|
4324
|
+
} else if (error instanceof Error) {
|
|
4325
|
+
errors.push({
|
|
4326
|
+
severity: "error",
|
|
4327
|
+
code: "E_LOWER",
|
|
4328
|
+
message: error.message,
|
|
4329
|
+
location: mapLocation(patchStatement.patch.location)
|
|
4330
|
+
});
|
|
4331
|
+
} else {
|
|
4332
|
+
errors.push({
|
|
4333
|
+
severity: "error",
|
|
4334
|
+
code: "E_LOWER",
|
|
4335
|
+
message: "Unknown lowering failure",
|
|
4336
|
+
location: mapLocation(patchStatement.patch.location)
|
|
4337
|
+
});
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
}
|
|
4341
|
+
trace.push({
|
|
4342
|
+
phase: "lower",
|
|
4343
|
+
durationMs: performance.now() - lowerStart,
|
|
4344
|
+
details: { count: loweredOps.length }
|
|
4345
|
+
});
|
|
4346
|
+
if (errors.length > 0) {
|
|
4347
|
+
return { ops: [], trace, warnings, errors };
|
|
4348
|
+
}
|
|
4349
|
+
return {
|
|
4350
|
+
ops: loweredOps,
|
|
4351
|
+
trace,
|
|
4352
|
+
warnings,
|
|
4353
|
+
errors
|
|
4354
|
+
};
|
|
4355
|
+
}
|
|
4356
|
+
function buildSyntheticPatchProgram(melText, actionName) {
|
|
4357
|
+
const indentedPatchText = melText.split("\n").map((line) => `${SYNTHETIC_PATCH_INDENT}${line}`).join("\n");
|
|
4358
|
+
return [
|
|
4359
|
+
`domain ${SYNTHETIC_DOMAIN} {`,
|
|
4360
|
+
` action ${actionName}() {`,
|
|
4361
|
+
" when true {",
|
|
4362
|
+
indentedPatchText,
|
|
4363
|
+
" }",
|
|
4364
|
+
" }",
|
|
4365
|
+
"}"
|
|
4366
|
+
].join("\n");
|
|
4367
|
+
}
|
|
4368
|
+
|
|
4369
|
+
// src/api/compile-mel.ts
|
|
4370
|
+
function compileMelDomain(melText, options) {
|
|
4371
|
+
const trace = [];
|
|
4372
|
+
const warnings = [];
|
|
4373
|
+
const errors = [];
|
|
4374
|
+
const lexStart = performance.now();
|
|
4375
|
+
let tokens;
|
|
4376
|
+
try {
|
|
4377
|
+
const lexResult = tokenize(melText);
|
|
4378
|
+
tokens = lexResult.tokens;
|
|
4379
|
+
const lexErrors = lexResult.diagnostics.filter((d) => d.severity === "error");
|
|
4380
|
+
if (lexErrors.length > 0) {
|
|
4381
|
+
errors.push(...lexErrors);
|
|
4382
|
+
trace.push({ phase: "lex", durationMs: performance.now() - lexStart, details: { tokenCount: tokens.length } });
|
|
4383
|
+
return { schema: null, trace, warnings, errors };
|
|
4384
|
+
}
|
|
4385
|
+
const lexWarnings = lexResult.diagnostics.filter((d) => d.severity === "warning");
|
|
4386
|
+
warnings.push(...lexWarnings);
|
|
4387
|
+
} catch (e) {
|
|
4388
|
+
const error = e;
|
|
4389
|
+
errors.push({
|
|
4390
|
+
severity: "error",
|
|
4391
|
+
code: "E_LEX",
|
|
4392
|
+
message: error.message,
|
|
4393
|
+
location: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } }
|
|
4394
|
+
});
|
|
4395
|
+
trace.push({ phase: "lex", durationMs: performance.now() - lexStart });
|
|
4396
|
+
return { schema: null, trace, warnings, errors };
|
|
4397
|
+
}
|
|
4398
|
+
trace.push({ phase: "lex", durationMs: performance.now() - lexStart, details: { tokenCount: tokens.length } });
|
|
4399
|
+
const parseStart = performance.now();
|
|
4400
|
+
let ast;
|
|
4401
|
+
try {
|
|
4402
|
+
const parseResult = parse(tokens);
|
|
4403
|
+
const parseErrors = parseResult.diagnostics.filter((d) => d.severity === "error");
|
|
4404
|
+
if (parseErrors.length > 0) {
|
|
4405
|
+
errors.push(...parseErrors);
|
|
4406
|
+
trace.push({ phase: "parse", durationMs: performance.now() - parseStart });
|
|
4407
|
+
return { schema: null, trace, warnings, errors: capDiagnostics(errors) };
|
|
4408
|
+
}
|
|
4409
|
+
ast = parseResult.program;
|
|
4410
|
+
} catch (e) {
|
|
4411
|
+
const error = e;
|
|
4412
|
+
errors.push({
|
|
4413
|
+
severity: "error",
|
|
4414
|
+
code: "E_PARSE",
|
|
4415
|
+
message: error.message,
|
|
4416
|
+
location: { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } }
|
|
4417
|
+
});
|
|
4418
|
+
trace.push({ phase: "parse", durationMs: performance.now() - parseStart });
|
|
4419
|
+
return { schema: null, trace, warnings, errors };
|
|
4420
|
+
}
|
|
4421
|
+
trace.push({ phase: "parse", durationMs: performance.now() - parseStart });
|
|
4422
|
+
const analyzeStart = performance.now();
|
|
4423
|
+
const scopeResult = analyzeScope(ast);
|
|
4424
|
+
const validateResult = validateSemantics(ast);
|
|
4425
|
+
const analyzeErrors = [
|
|
4426
|
+
...scopeResult.diagnostics.filter((d) => d.severity === "error"),
|
|
4427
|
+
...validateResult.diagnostics.filter((d) => d.severity === "error")
|
|
4428
|
+
];
|
|
4429
|
+
const analyzeWarnings = [
|
|
4430
|
+
...scopeResult.diagnostics.filter((d) => d.severity === "warning"),
|
|
4431
|
+
...validateResult.diagnostics.filter((d) => d.severity === "warning")
|
|
4432
|
+
];
|
|
4433
|
+
warnings.push(...analyzeWarnings);
|
|
4434
|
+
trace.push({ phase: "analyze", durationMs: performance.now() - analyzeStart });
|
|
4435
|
+
if (analyzeErrors.length > 0) {
|
|
4436
|
+
errors.push(...analyzeErrors);
|
|
4437
|
+
return { schema: null, trace, warnings, errors };
|
|
4438
|
+
}
|
|
4439
|
+
const genStart = performance.now();
|
|
4440
|
+
const genResult = generate(ast);
|
|
4441
|
+
trace.push({ phase: "generate", durationMs: performance.now() - genStart });
|
|
4442
|
+
for (const diag of genResult.diagnostics) {
|
|
4443
|
+
if (diag.severity === "warning") {
|
|
4444
|
+
warnings.push(diag);
|
|
4445
|
+
} else {
|
|
4446
|
+
errors.push(diag);
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
4449
|
+
return {
|
|
4450
|
+
schema: genResult.schema,
|
|
4451
|
+
trace,
|
|
4452
|
+
warnings,
|
|
4453
|
+
errors: capDiagnostics(errors)
|
|
4454
|
+
};
|
|
4455
|
+
}
|
|
4456
|
+
var MAX_ERRORS = 10;
|
|
4457
|
+
function capDiagnostics(diagnostics) {
|
|
4458
|
+
if (diagnostics.length <= MAX_ERRORS) return diagnostics;
|
|
4459
|
+
const capped = diagnostics.slice(0, MAX_ERRORS);
|
|
4460
|
+
capped.push({
|
|
4461
|
+
severity: "error",
|
|
4462
|
+
code: "E_TOO_MANY",
|
|
4463
|
+
message: `... and ${diagnostics.length - MAX_ERRORS} more error(s). Fix the errors above first.`,
|
|
4464
|
+
location: { start: { line: 0, column: 0, offset: 0 }, end: { line: 0, column: 0, offset: 0 } }
|
|
4465
|
+
});
|
|
4466
|
+
return capped;
|
|
4467
|
+
}
|
|
4468
|
+
function compileMelPatch(melText, options) {
|
|
4469
|
+
return compileMelPatchText(melText, options);
|
|
4470
|
+
}
|
|
4471
|
+
|
|
4472
|
+
export {
|
|
4473
|
+
KEYWORDS,
|
|
4474
|
+
RESERVED_KEYWORDS,
|
|
4475
|
+
isKeyword,
|
|
4476
|
+
isReserved,
|
|
4477
|
+
getKeywordKind,
|
|
4478
|
+
createToken,
|
|
4479
|
+
createPosition,
|
|
4480
|
+
createLocation,
|
|
4481
|
+
createPointLocation,
|
|
4482
|
+
mergeLocations,
|
|
4483
|
+
Lexer,
|
|
4484
|
+
tokenize,
|
|
4485
|
+
isExprNode,
|
|
4486
|
+
isStmtNode,
|
|
4487
|
+
Precedence,
|
|
4488
|
+
getBinaryPrecedence,
|
|
4489
|
+
tokenToBinaryOp,
|
|
4490
|
+
isBinaryOp,
|
|
4491
|
+
isUnaryOp,
|
|
4492
|
+
isRightAssociative,
|
|
4493
|
+
Parser,
|
|
4494
|
+
parse,
|
|
4495
|
+
Scope,
|
|
4496
|
+
ScopeAnalyzer,
|
|
4497
|
+
analyzeScope,
|
|
4498
|
+
createError,
|
|
4499
|
+
createWarning,
|
|
4500
|
+
createInfo,
|
|
4501
|
+
isError,
|
|
4502
|
+
hasErrors,
|
|
4503
|
+
filterBySeverity,
|
|
4504
|
+
SemanticValidator,
|
|
4505
|
+
validateSemantics,
|
|
4506
|
+
normalizeExpr,
|
|
4507
|
+
normalizeFunctionCall,
|
|
4508
|
+
generate,
|
|
4509
|
+
LoweringError,
|
|
4510
|
+
invalidKindForContext,
|
|
4511
|
+
unknownCallFn,
|
|
4512
|
+
invalidSysPath,
|
|
4513
|
+
unsupportedBase,
|
|
4514
|
+
invalidShape,
|
|
4515
|
+
unknownNodeKind,
|
|
4516
|
+
lowerExprNode,
|
|
4517
|
+
lowerRuntimePatches,
|
|
4518
|
+
lowerRuntimePatch,
|
|
4519
|
+
compileMelDomain,
|
|
4520
|
+
compileMelPatch
|
|
4521
|
+
};
|
|
4522
|
+
//# sourceMappingURL=chunk-QP2LGMBA.js.map
|