@lokascript/framework 2.1.0 → 2.2.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/dist/api/index.js +111 -24
- package/dist/api/index.js.map +1 -1
- package/dist/core/index.js +133 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/pattern-matching/index.js.map +1 -1
- package/dist/core/tokenization/index.js +133 -0
- package/dist/core/tokenization/index.js.map +1 -1
- package/dist/core/tokenization/morphology/base-normalizer.d.ts +94 -0
- package/dist/core/tokenization/morphology/base-normalizer.d.ts.map +1 -0
- package/dist/core/tokenization/morphology/index.d.ts +3 -1
- package/dist/core/tokenization/morphology/index.d.ts.map +1 -1
- package/dist/core/types.d.ts +3 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/generation/index.js.map +1 -1
- package/dist/index.cjs +564 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +556 -46
- package/dist/index.js.map +1 -1
- package/dist/ir/explicit-parser.d.ts +62 -7
- package/dist/ir/explicit-parser.d.ts.map +1 -1
- package/dist/ir/explicit-renderer.d.ts +18 -1
- package/dist/ir/explicit-renderer.d.ts.map +1 -1
- package/dist/ir/index.d.ts +4 -2
- package/dist/ir/index.d.ts.map +1 -1
- package/dist/ir/index.js +423 -46
- package/dist/ir/index.js.map +1 -1
- package/dist/ir/protocol-json.d.ts.map +1 -1
- package/dist/ir/to-runtime-ast.d.ts +53 -0
- package/dist/ir/to-runtime-ast.d.ts.map +1 -0
- package/dist/ir/types.d.ts +14 -4
- package/dist/ir/types.d.ts.map +1 -1
- package/dist/parsing/index.js.map +1 -1
- package/dist/testing/index.js +8671 -8261
- package/dist/testing/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/tokenization/morphology/base-normalizer.ts +249 -0
- package/src/core/tokenization/morphology/index.ts +3 -1
- package/src/core/types.ts +4 -2
- package/src/index.ts +7 -0
- package/src/ir/conformance.test.ts +120 -0
- package/src/ir/explicit-parser.test.ts +307 -1
- package/src/ir/explicit-parser.ts +403 -29
- package/src/ir/explicit-renderer.test.ts +87 -2
- package/src/ir/explicit-renderer.ts +57 -5
- package/src/ir/index.ts +13 -2
- package/src/ir/protocol-json.test.ts +4 -2
- package/src/ir/protocol-json.ts +30 -24
- package/src/ir/to-runtime-ast.ts +203 -0
- package/src/ir/types.ts +14 -4
package/dist/ir/index.js
CHANGED
|
@@ -162,6 +162,8 @@ function isNestedCommand(value) {
|
|
|
162
162
|
}
|
|
163
163
|
function parseExplicit(input, options = {}) {
|
|
164
164
|
const trimmed = input.trim();
|
|
165
|
+
const collect = options.collectDiagnostics === true;
|
|
166
|
+
const diagnostics = [];
|
|
165
167
|
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) {
|
|
166
168
|
throw new Error("Explicit syntax must be wrapped in brackets: [command role:value ...]");
|
|
167
169
|
}
|
|
@@ -184,10 +186,30 @@ function parseExplicit(input, options = {}) {
|
|
|
184
186
|
const enabled = token.startsWith("+");
|
|
185
187
|
const flagName = token.slice(1);
|
|
186
188
|
if (!flagName) {
|
|
189
|
+
if (collect) {
|
|
190
|
+
diagnostics.push({
|
|
191
|
+
severity: "error",
|
|
192
|
+
code: "EMPTY_FLAG",
|
|
193
|
+
message: `Empty flag name: "${token}"`,
|
|
194
|
+
source: "schema"
|
|
195
|
+
});
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
187
198
|
throw new Error(`Empty flag name: "${token}"`);
|
|
188
199
|
}
|
|
189
200
|
if (validRoleNames && !validRoleNames.has(flagName)) {
|
|
190
201
|
const roleList = [...validRoleNames].join(", ");
|
|
202
|
+
if (collect) {
|
|
203
|
+
diagnostics.push({
|
|
204
|
+
severity: "error",
|
|
205
|
+
code: "UNKNOWN_FLAG",
|
|
206
|
+
message: `Unknown flag "${flagName}" for command "${command}"`,
|
|
207
|
+
source: "schema",
|
|
208
|
+
suggestions: [...validRoleNames].map((r) => `+${r}`)
|
|
209
|
+
});
|
|
210
|
+
roles.set(flagName, createFlag(flagName, enabled));
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
191
213
|
throw new Error(
|
|
192
214
|
`Unknown flag "${flagName}" for command "${command}". Valid roles: ${roleList}`
|
|
193
215
|
);
|
|
@@ -197,14 +219,33 @@ function parseExplicit(input, options = {}) {
|
|
|
197
219
|
}
|
|
198
220
|
const colonIndex = token.indexOf(":");
|
|
199
221
|
if (colonIndex === -1) {
|
|
222
|
+
if (collect) {
|
|
223
|
+
diagnostics.push({
|
|
224
|
+
severity: "error",
|
|
225
|
+
code: "INVALID_ROLE_FORMAT",
|
|
226
|
+
message: `Invalid role format: "${token}". Expected role:value or +flag`,
|
|
227
|
+
source: "schema"
|
|
228
|
+
});
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
200
231
|
throw new Error(`Invalid role format: "${token}". Expected role:value or +flag`);
|
|
201
232
|
}
|
|
202
233
|
const roleName = token.slice(0, colonIndex);
|
|
203
234
|
if (validRoleNames && !STRUCTURAL_ROLES.has(roleName) && !validRoleNames.has(roleName)) {
|
|
204
235
|
const roleList = [...validRoleNames].join(", ");
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
236
|
+
if (collect) {
|
|
237
|
+
diagnostics.push({
|
|
238
|
+
severity: "error",
|
|
239
|
+
code: "UNKNOWN_ROLE",
|
|
240
|
+
message: `Unknown role "${roleName}" for command "${command}"`,
|
|
241
|
+
source: "schema",
|
|
242
|
+
suggestions: [...validRoleNames]
|
|
243
|
+
});
|
|
244
|
+
} else {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`Unknown role "${roleName}" for command "${command}". Valid roles: ${roleList}`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
208
249
|
}
|
|
209
250
|
const role = roleName;
|
|
210
251
|
const valueStr = token.slice(colonIndex + 1);
|
|
@@ -220,37 +261,67 @@ function parseExplicit(input, options = {}) {
|
|
|
220
261
|
if (schema && command !== "on") {
|
|
221
262
|
for (const roleSpec of schema.roles) {
|
|
222
263
|
if (roleSpec.required && !roles.has(roleSpec.role) && !roleSpec.default) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
264
|
+
if (collect) {
|
|
265
|
+
diagnostics.push({
|
|
266
|
+
severity: "error",
|
|
267
|
+
code: "MISSING_REQUIRED_ROLE",
|
|
268
|
+
message: `Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`,
|
|
269
|
+
source: "schema"
|
|
270
|
+
});
|
|
271
|
+
} else {
|
|
272
|
+
throw new Error(
|
|
273
|
+
`Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
226
276
|
}
|
|
227
277
|
}
|
|
228
278
|
}
|
|
279
|
+
const attachDiagnostics = (node) => {
|
|
280
|
+
if (diagnostics.length === 0) return node;
|
|
281
|
+
return { ...node, diagnostics };
|
|
282
|
+
};
|
|
229
283
|
switch (command) {
|
|
230
284
|
case "on": {
|
|
231
285
|
const eventValue = roles.get("event");
|
|
232
286
|
if (!eventValue) {
|
|
287
|
+
if (collect) {
|
|
288
|
+
diagnostics.push({
|
|
289
|
+
severity: "error",
|
|
290
|
+
code: "MISSING_EVENT_ROLE",
|
|
291
|
+
message: "Event handler requires event role: [on event:click ...]",
|
|
292
|
+
source: "schema"
|
|
293
|
+
});
|
|
294
|
+
return attachDiagnostics(
|
|
295
|
+
createEventHandlerNode("on", roles, [], {
|
|
296
|
+
sourceLanguage: "explicit"
|
|
297
|
+
})
|
|
298
|
+
);
|
|
299
|
+
}
|
|
233
300
|
throw new Error("Event handler requires event role: [on event:click ...]");
|
|
234
301
|
}
|
|
235
302
|
const body = extractStructuralBody(roles, "body", options);
|
|
236
303
|
roles.delete("body");
|
|
237
|
-
return
|
|
238
|
-
|
|
239
|
-
|
|
304
|
+
return attachDiagnostics(
|
|
305
|
+
createEventHandlerNode("on", roles, body, {
|
|
306
|
+
sourceLanguage: "explicit"
|
|
307
|
+
})
|
|
308
|
+
);
|
|
240
309
|
}
|
|
241
310
|
case "if": {
|
|
242
311
|
const thenBranch = extractStructuralBody(roles, "then", options);
|
|
243
312
|
const elseBranch = extractStructuralBody(roles, "else", options);
|
|
244
313
|
roles.delete("then");
|
|
245
314
|
roles.delete("else");
|
|
246
|
-
return
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
315
|
+
return attachDiagnostics(
|
|
316
|
+
createConditionalNode(
|
|
317
|
+
command,
|
|
318
|
+
roles,
|
|
319
|
+
thenBranch,
|
|
320
|
+
elseBranch.length > 0 ? elseBranch : void 0,
|
|
321
|
+
{
|
|
322
|
+
sourceLanguage: "explicit"
|
|
323
|
+
}
|
|
324
|
+
)
|
|
254
325
|
);
|
|
255
326
|
}
|
|
256
327
|
case "repeat": {
|
|
@@ -276,20 +347,182 @@ function parseExplicit(input, options = {}) {
|
|
|
276
347
|
}
|
|
277
348
|
const loopVariable = variableValue?.type === "literal" && typeof variableValue.value === "string" ? variableValue.value : variableValue?.type === "expression" ? variableValue.raw : void 0;
|
|
278
349
|
const indexVariable = indexVariableValue?.type === "literal" && typeof indexVariableValue.value === "string" ? indexVariableValue.value : void 0;
|
|
279
|
-
return
|
|
280
|
-
|
|
281
|
-
|
|
350
|
+
return attachDiagnostics(
|
|
351
|
+
createLoopNode(command, roles, variant, loopBody, loopVariable, indexVariable, {
|
|
352
|
+
sourceLanguage: "explicit"
|
|
353
|
+
})
|
|
354
|
+
);
|
|
282
355
|
}
|
|
283
356
|
default:
|
|
284
|
-
return
|
|
285
|
-
|
|
286
|
-
|
|
357
|
+
return attachDiagnostics(
|
|
358
|
+
createCommandNode(command, roles, {
|
|
359
|
+
sourceLanguage: "explicit"
|
|
360
|
+
})
|
|
361
|
+
);
|
|
287
362
|
}
|
|
288
363
|
}
|
|
289
364
|
function isExplicitSyntax(input) {
|
|
290
365
|
const trimmed = input.trim();
|
|
291
366
|
return trimmed.startsWith("[") && trimmed.endsWith("]");
|
|
292
367
|
}
|
|
368
|
+
var CHAIN_KEYWORDS = /* @__PURE__ */ new Set(["then", "and", "async", "sequential"]);
|
|
369
|
+
function splitCompoundSegments(input) {
|
|
370
|
+
const segments = [];
|
|
371
|
+
let chainType = null;
|
|
372
|
+
let current = "";
|
|
373
|
+
let bracketDepth = 0;
|
|
374
|
+
let inString = false;
|
|
375
|
+
let stringChar = "";
|
|
376
|
+
for (let i = 0; i < input.length; i++) {
|
|
377
|
+
const char = input[i];
|
|
378
|
+
if (inString) {
|
|
379
|
+
current += char;
|
|
380
|
+
if (char === stringChar && input[i - 1] !== "\\") {
|
|
381
|
+
inString = false;
|
|
382
|
+
}
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (char === '"' || char === "'") {
|
|
386
|
+
inString = true;
|
|
387
|
+
stringChar = char;
|
|
388
|
+
current += char;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (char === "[") {
|
|
392
|
+
bracketDepth++;
|
|
393
|
+
current += char;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (char === "]") {
|
|
397
|
+
bracketDepth--;
|
|
398
|
+
current += char;
|
|
399
|
+
if (bracketDepth === 0) {
|
|
400
|
+
const rest = input.slice(i + 1);
|
|
401
|
+
const pipeMatch = rest.match(/^\s*\|>\s*/);
|
|
402
|
+
if (pipeMatch) {
|
|
403
|
+
segments.push(current.trim());
|
|
404
|
+
current = "";
|
|
405
|
+
chainType ?? (chainType = "pipe");
|
|
406
|
+
i += pipeMatch[0].length;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
const keywordMatch = rest.match(/^\s+(then|and|async|sequential)\s+/);
|
|
410
|
+
if (keywordMatch && CHAIN_KEYWORDS.has(keywordMatch[1])) {
|
|
411
|
+
segments.push(current.trim());
|
|
412
|
+
current = "";
|
|
413
|
+
chainType ?? (chainType = keywordMatch[1]);
|
|
414
|
+
i += keywordMatch[0].length;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
current += char;
|
|
421
|
+
}
|
|
422
|
+
if (current.trim()) {
|
|
423
|
+
segments.push(current.trim());
|
|
424
|
+
}
|
|
425
|
+
return { segments, chainType };
|
|
426
|
+
}
|
|
427
|
+
function parseCompound(input, options) {
|
|
428
|
+
const trimmed = input.trim();
|
|
429
|
+
const { annotations, remainder } = extractAnnotations(trimmed);
|
|
430
|
+
const { segments, chainType } = splitCompoundSegments(remainder);
|
|
431
|
+
let node;
|
|
432
|
+
if (segments.length <= 1) {
|
|
433
|
+
node = parseExplicit(segments[0] || remainder, options);
|
|
434
|
+
} else {
|
|
435
|
+
const statements = segments.map((seg) => parseExplicit(seg, options));
|
|
436
|
+
node = createCompoundNode(statements, chainType ?? "sequential");
|
|
437
|
+
}
|
|
438
|
+
if (annotations.length > 0) {
|
|
439
|
+
return { ...node, annotations };
|
|
440
|
+
}
|
|
441
|
+
return node;
|
|
442
|
+
}
|
|
443
|
+
function extractAnnotations(input) {
|
|
444
|
+
const annotations = [];
|
|
445
|
+
let pos = 0;
|
|
446
|
+
while (pos < input.length) {
|
|
447
|
+
while (pos < input.length && (input[pos] === " " || input[pos] === " ")) {
|
|
448
|
+
pos++;
|
|
449
|
+
}
|
|
450
|
+
if (pos >= input.length || input[pos] !== "@") {
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
pos++;
|
|
454
|
+
const nameStart = pos;
|
|
455
|
+
while (pos < input.length && /[A-Za-z0-9\-_]/.test(input[pos])) {
|
|
456
|
+
pos++;
|
|
457
|
+
}
|
|
458
|
+
const name = input.slice(nameStart, pos);
|
|
459
|
+
if (!name) break;
|
|
460
|
+
let value;
|
|
461
|
+
if (pos < input.length && input[pos] === "(") {
|
|
462
|
+
pos++;
|
|
463
|
+
const valueStart = pos;
|
|
464
|
+
if (pos < input.length && (input[pos] === '"' || input[pos] === "'")) {
|
|
465
|
+
const quote = input[pos];
|
|
466
|
+
pos++;
|
|
467
|
+
while (pos < input.length && input[pos] !== quote) {
|
|
468
|
+
if (input[pos] === "\\") pos++;
|
|
469
|
+
pos++;
|
|
470
|
+
}
|
|
471
|
+
pos++;
|
|
472
|
+
value = input.slice(valueStart + 1, pos - 1);
|
|
473
|
+
} else {
|
|
474
|
+
while (pos < input.length && input[pos] !== ")") {
|
|
475
|
+
pos++;
|
|
476
|
+
}
|
|
477
|
+
value = input.slice(valueStart, pos);
|
|
478
|
+
}
|
|
479
|
+
if (pos < input.length && input[pos] === ")") {
|
|
480
|
+
pos++;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
annotations.push(value !== void 0 ? { name, value } : { name });
|
|
484
|
+
}
|
|
485
|
+
return { annotations, remainder: input.slice(pos).trim() };
|
|
486
|
+
}
|
|
487
|
+
var VERSION_HEADER_RE = /^#!lse\s+(\d+\.\d+(?:\.\d+)?)\s*$/;
|
|
488
|
+
function parseDocument(input, options) {
|
|
489
|
+
const lines = input.split("\n");
|
|
490
|
+
let lseVersion = "1.0";
|
|
491
|
+
const nodes = [];
|
|
492
|
+
let startLine = 0;
|
|
493
|
+
for (let i = 0; i < lines.length; i++) {
|
|
494
|
+
const trimmed = lines[i].trim();
|
|
495
|
+
if (!trimmed) continue;
|
|
496
|
+
const versionMatch = trimmed.match(VERSION_HEADER_RE);
|
|
497
|
+
if (versionMatch) {
|
|
498
|
+
lseVersion = versionMatch[1];
|
|
499
|
+
startLine = i + 1;
|
|
500
|
+
}
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
504
|
+
const trimmed = lines[i].trim();
|
|
505
|
+
if (!trimmed) continue;
|
|
506
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("#") && !trimmed.startsWith("#!")) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
nodes.push(parseCompound(trimmed, options));
|
|
510
|
+
}
|
|
511
|
+
return { lseVersion, nodes };
|
|
512
|
+
}
|
|
513
|
+
function isCompoundSyntax(input) {
|
|
514
|
+
const { segments } = splitCompoundSegments(input.trim());
|
|
515
|
+
return segments.length > 1;
|
|
516
|
+
}
|
|
517
|
+
function isDocumentSyntax(input) {
|
|
518
|
+
const trimmed = input.trim();
|
|
519
|
+
if (trimmed.includes("\n")) return true;
|
|
520
|
+
if (VERSION_HEADER_RE.test(trimmed)) return true;
|
|
521
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("#") && !trimmed.startsWith("#!")) {
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
293
526
|
function extractStructuralBody(roles, roleName, options) {
|
|
294
527
|
const value = roles.get(roleName);
|
|
295
528
|
if (!value) return [];
|
|
@@ -391,11 +624,33 @@ function findMatchingBracket(str, start) {
|
|
|
391
624
|
|
|
392
625
|
// src/ir/explicit-renderer.ts
|
|
393
626
|
function renderExplicit(node) {
|
|
627
|
+
let result;
|
|
394
628
|
if (node.kind === "compound") {
|
|
395
629
|
const compoundNode = node;
|
|
396
630
|
const renderedStatements = compoundNode.statements.map((stmt) => renderExplicit(stmt));
|
|
397
|
-
|
|
631
|
+
result = renderedStatements.join(` ${compoundNode.chainType} `);
|
|
632
|
+
} else {
|
|
633
|
+
result = renderBracketCommand(node);
|
|
634
|
+
}
|
|
635
|
+
if (node.annotations && node.annotations.length > 0) {
|
|
636
|
+
const annParts = node.annotations.map(
|
|
637
|
+
(ann) => ann.value !== void 0 ? `@${ann.name}(${ann.value})` : `@${ann.name}`
|
|
638
|
+
);
|
|
639
|
+
return annParts.join(" ") + " " + result;
|
|
640
|
+
}
|
|
641
|
+
return result;
|
|
642
|
+
}
|
|
643
|
+
function renderDocument(envelope) {
|
|
644
|
+
const lines = [];
|
|
645
|
+
if (envelope.lseVersion && envelope.lseVersion !== "1.0") {
|
|
646
|
+
lines.push(`#!lse ${envelope.lseVersion}`);
|
|
398
647
|
}
|
|
648
|
+
for (const node of envelope.nodes) {
|
|
649
|
+
lines.push(renderExplicit(node));
|
|
650
|
+
}
|
|
651
|
+
return lines.join("\n");
|
|
652
|
+
}
|
|
653
|
+
function renderBracketCommand(node) {
|
|
399
654
|
const parts = [node.action];
|
|
400
655
|
for (const [role, value] of node.roles) {
|
|
401
656
|
if (value.type === "flag") {
|
|
@@ -733,12 +988,16 @@ function toProtocolJSON(node) {
|
|
|
733
988
|
);
|
|
734
989
|
}
|
|
735
990
|
if (node.diagnostics && node.diagnostics.length > 0) {
|
|
736
|
-
result.diagnostics = node.diagnostics.map((d) =>
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
991
|
+
result.diagnostics = node.diagnostics.map((d) => {
|
|
992
|
+
const json = {
|
|
993
|
+
level: d.severity,
|
|
994
|
+
message: d.message
|
|
995
|
+
};
|
|
996
|
+
if (d.code) json.code = d.code;
|
|
997
|
+
if (d.source) json.source = d.source;
|
|
998
|
+
if (d.suggestions && d.suggestions.length > 0) json.suggestions = [...d.suggestions];
|
|
999
|
+
return json;
|
|
1000
|
+
});
|
|
742
1001
|
}
|
|
743
1002
|
return result;
|
|
744
1003
|
}
|
|
@@ -786,10 +1045,11 @@ function fromProtocolJSON(json) {
|
|
|
786
1045
|
(a) => a.value !== void 0 ? { name: a.name, value: a.value } : { name: a.name }
|
|
787
1046
|
);
|
|
788
1047
|
const diagnostics = json.diagnostics?.map((d) => ({
|
|
789
|
-
|
|
790
|
-
role: d.role,
|
|
1048
|
+
severity: d.level,
|
|
791
1049
|
message: d.message,
|
|
792
|
-
code: d.code
|
|
1050
|
+
...d.code ? { code: d.code } : {},
|
|
1051
|
+
...d.source ? { source: d.source } : {},
|
|
1052
|
+
...d.suggestions && d.suggestions.length > 0 ? { suggestions: d.suggestions } : {}
|
|
793
1053
|
}));
|
|
794
1054
|
if (json.trigger) {
|
|
795
1055
|
const eventName = json.trigger.event;
|
|
@@ -930,7 +1190,7 @@ var VALID_VALUE_TYPES2 = /* @__PURE__ */ new Set([
|
|
|
930
1190
|
]);
|
|
931
1191
|
var VALID_CHAIN_TYPES = /* @__PURE__ */ new Set(["then", "and", "async", "sequential", "pipe"]);
|
|
932
1192
|
var VALID_ASYNC_VARIANTS = /* @__PURE__ */ new Set(["all", "race"]);
|
|
933
|
-
var VALID_DIAGNOSTIC_LEVELS = /* @__PURE__ */ new Set(["error", "warning"]);
|
|
1193
|
+
var VALID_DIAGNOSTIC_LEVELS = /* @__PURE__ */ new Set(["error", "warning", "info"]);
|
|
934
1194
|
function validateProtocolJSON(json) {
|
|
935
1195
|
const diagnostics = [];
|
|
936
1196
|
if (typeof json !== "object" || json === null) {
|
|
@@ -1079,14 +1339,7 @@ function validateProtocolJSON(json) {
|
|
|
1079
1339
|
diagnostics.push({
|
|
1080
1340
|
severity: "error",
|
|
1081
1341
|
code: "INVALID_DIAGNOSTIC_LEVEL",
|
|
1082
|
-
message: `diagnostics[${i}].level must be "error" or "
|
|
1083
|
-
});
|
|
1084
|
-
}
|
|
1085
|
-
if (typeof d.role !== "string") {
|
|
1086
|
-
diagnostics.push({
|
|
1087
|
-
severity: "error",
|
|
1088
|
-
code: "MISSING_DIAGNOSTIC_ROLE",
|
|
1089
|
-
message: `diagnostics[${i}] missing required field: role`
|
|
1342
|
+
message: `diagnostics[${i}].level must be "error", "warning", or "info"`
|
|
1090
1343
|
});
|
|
1091
1344
|
}
|
|
1092
1345
|
if (typeof d.message !== "string") {
|
|
@@ -1096,11 +1349,11 @@ function validateProtocolJSON(json) {
|
|
|
1096
1349
|
message: `diagnostics[${i}] missing required field: message`
|
|
1097
1350
|
});
|
|
1098
1351
|
}
|
|
1099
|
-
if (typeof d.code !== "string") {
|
|
1352
|
+
if ("code" in d && typeof d.code !== "string") {
|
|
1100
1353
|
diagnostics.push({
|
|
1101
|
-
severity: "
|
|
1102
|
-
code: "
|
|
1103
|
-
message: `diagnostics[${i}]
|
|
1354
|
+
severity: "warning",
|
|
1355
|
+
code: "INVALID_DIAGNOSTIC_CODE",
|
|
1356
|
+
message: `diagnostics[${i}].code must be a string if present`
|
|
1104
1357
|
});
|
|
1105
1358
|
}
|
|
1106
1359
|
}
|
|
@@ -1535,20 +1788,144 @@ function bridgeEventModifiers(mods) {
|
|
|
1535
1788
|
...from !== void 0 ? { from } : {}
|
|
1536
1789
|
};
|
|
1537
1790
|
}
|
|
1791
|
+
|
|
1792
|
+
// src/ir/to-runtime-ast.ts
|
|
1793
|
+
function semanticNodeToRuntimeAST(node) {
|
|
1794
|
+
switch (node.kind) {
|
|
1795
|
+
case "event-handler":
|
|
1796
|
+
return convertEventHandler(node);
|
|
1797
|
+
case "conditional":
|
|
1798
|
+
return convertConditional(node);
|
|
1799
|
+
case "loop":
|
|
1800
|
+
return convertLoop(node);
|
|
1801
|
+
case "compound": {
|
|
1802
|
+
const compound = node;
|
|
1803
|
+
const commands = (compound.statements ?? []).map(semanticNodeToRuntimeAST);
|
|
1804
|
+
return {
|
|
1805
|
+
type: "CommandSequence",
|
|
1806
|
+
commands
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
case "command":
|
|
1810
|
+
default:
|
|
1811
|
+
return convertCommand2(node);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
function convertEventHandler(eh) {
|
|
1815
|
+
const eventValue = eh.roles.get("event");
|
|
1816
|
+
const eventName = eventValue && "value" in eventValue ? String(eventValue.value) : "click";
|
|
1817
|
+
const modifiers = {};
|
|
1818
|
+
if (eh.eventModifiers) {
|
|
1819
|
+
if (eh.eventModifiers.once) modifiers.once = true;
|
|
1820
|
+
if (eh.eventModifiers.debounce) modifiers.debounce = eh.eventModifiers.debounce;
|
|
1821
|
+
if (eh.eventModifiers.throttle) modifiers.throttle = eh.eventModifiers.throttle;
|
|
1822
|
+
if (eh.eventModifiers.queue) modifiers.queue = eh.eventModifiers.queue;
|
|
1823
|
+
}
|
|
1824
|
+
return {
|
|
1825
|
+
type: "event",
|
|
1826
|
+
event: eventName,
|
|
1827
|
+
modifiers,
|
|
1828
|
+
body: (eh.body ?? []).map(semanticNodeToRuntimeAST)
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
function convertCommand2(node) {
|
|
1832
|
+
const roles = {};
|
|
1833
|
+
const args = [];
|
|
1834
|
+
for (const [roleName, value] of node.roles) {
|
|
1835
|
+
const astValue = semanticValueToAST(value);
|
|
1836
|
+
roles[roleName] = astValue;
|
|
1837
|
+
args.push(astValue);
|
|
1838
|
+
}
|
|
1839
|
+
return {
|
|
1840
|
+
type: "command",
|
|
1841
|
+
name: node.action,
|
|
1842
|
+
args,
|
|
1843
|
+
roles
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
function convertConditional(node) {
|
|
1847
|
+
const conditionValue = node.roles.get("condition");
|
|
1848
|
+
return {
|
|
1849
|
+
type: "command",
|
|
1850
|
+
name: "if",
|
|
1851
|
+
args: conditionValue ? [semanticValueToAST(conditionValue)] : [],
|
|
1852
|
+
condition: conditionValue ? semanticValueToAST(conditionValue) : void 0,
|
|
1853
|
+
thenBranch: (node.thenBranch ?? []).map(semanticNodeToRuntimeAST),
|
|
1854
|
+
elseBranch: (node.elseBranch ?? []).map(semanticNodeToRuntimeAST)
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
function convertLoop(node) {
|
|
1858
|
+
const countValue = node.roles.get("patient") || node.roles.get("count");
|
|
1859
|
+
return {
|
|
1860
|
+
type: "command",
|
|
1861
|
+
name: "repeat",
|
|
1862
|
+
args: countValue ? [semanticValueToAST(countValue)] : [],
|
|
1863
|
+
loopVariant: node.loopVariant ?? "forever",
|
|
1864
|
+
body: (node.body ?? []).map(semanticNodeToRuntimeAST),
|
|
1865
|
+
loopVariable: node.loopVariable,
|
|
1866
|
+
indexVariable: node.indexVariable
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
function semanticValueToAST(value) {
|
|
1870
|
+
switch (value.type) {
|
|
1871
|
+
case "selector":
|
|
1872
|
+
return { type: "selector", value: value.value };
|
|
1873
|
+
case "reference":
|
|
1874
|
+
return { type: "identifier", value: value.value };
|
|
1875
|
+
case "literal": {
|
|
1876
|
+
const lit = value;
|
|
1877
|
+
if (lit.dataType === "duration") {
|
|
1878
|
+
const str = String(lit.value);
|
|
1879
|
+
const match = /^(\d+(?:\.\d+)?)(ms|s)$/.exec(str);
|
|
1880
|
+
if (match) {
|
|
1881
|
+
const ms = match[2] === "s" ? parseFloat(match[1]) * 1e3 : parseFloat(match[1]);
|
|
1882
|
+
return { type: "literal", value: ms };
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
return { type: "literal", value: lit.value };
|
|
1886
|
+
}
|
|
1887
|
+
case "property-path": {
|
|
1888
|
+
const pp = value;
|
|
1889
|
+
const objectAST = semanticValueToAST(pp.object);
|
|
1890
|
+
return {
|
|
1891
|
+
type: "member",
|
|
1892
|
+
object: objectAST,
|
|
1893
|
+
property: pp.property
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
case "expression": {
|
|
1897
|
+
const expr = value;
|
|
1898
|
+
return { type: "expression", value: expr.raw };
|
|
1899
|
+
}
|
|
1900
|
+
case "flag": {
|
|
1901
|
+
const flag = value;
|
|
1902
|
+
return { type: "literal", value: flag.enabled };
|
|
1903
|
+
}
|
|
1904
|
+
default:
|
|
1905
|
+
return { type: "literal", value: String(value.value ?? "") };
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1538
1908
|
export {
|
|
1539
1909
|
DEFAULT_REFERENCES,
|
|
1540
1910
|
convertValue,
|
|
1541
1911
|
fromEnvelopeJSON,
|
|
1542
1912
|
fromInterchangeNode,
|
|
1543
1913
|
fromProtocolJSON,
|
|
1914
|
+
isCompoundSyntax,
|
|
1915
|
+
isDocumentSyntax,
|
|
1544
1916
|
isEnvelope,
|
|
1545
1917
|
isExplicitSyntax,
|
|
1546
1918
|
isValidReference,
|
|
1547
1919
|
jsonToSemanticNode,
|
|
1920
|
+
parseCompound,
|
|
1921
|
+
parseDocument,
|
|
1548
1922
|
parseExplicit,
|
|
1923
|
+
renderDocument,
|
|
1549
1924
|
renderExplicit,
|
|
1550
1925
|
renderExpr,
|
|
1551
1926
|
semanticNodeToJSON,
|
|
1927
|
+
semanticNodeToRuntimeAST,
|
|
1928
|
+
semanticValueToAST,
|
|
1552
1929
|
toEnvelopeJSON,
|
|
1553
1930
|
toProtocolJSON,
|
|
1554
1931
|
validateProtocolJSON,
|