@lokascript/framework 2.0.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/aot/index.d.ts +1 -0
- package/dist/aot/index.d.ts.map +1 -1
- package/dist/aot/registry-bridge.d.ts +27 -0
- package/dist/aot/registry-bridge.d.ts.map +1 -0
- package/dist/api/create-dsl.d.ts.map +1 -1
- package/dist/api/dispatcher.d.ts +22 -0
- package/dist/api/dispatcher.d.ts.map +1 -1
- package/dist/api/domain-registry.d.ts +41 -0
- package/dist/api/domain-registry.d.ts.map +1 -1
- package/dist/api/index.js +1438 -21
- package/dist/api/index.js.map +1 -1
- package/dist/core/index.js +247 -10
- package/dist/core/index.js.map +1 -1
- package/dist/core/pattern-matching/index.js +74 -10
- package/dist/core/pattern-matching/index.js.map +1 -1
- package/dist/core/pattern-matching/pattern-matcher.d.ts.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 +81 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +40 -0
- package/dist/core/types.js.map +1 -1
- package/dist/feedback/confidence-gate.d.ts +28 -0
- package/dist/feedback/confidence-gate.d.ts.map +1 -0
- package/dist/feedback/feedback-formatter.d.ts +21 -0
- package/dist/feedback/feedback-formatter.d.ts.map +1 -0
- package/dist/feedback/index.d.ts +11 -0
- package/dist/feedback/index.d.ts.map +1 -0
- package/dist/feedback/pattern-tracker.d.ts +47 -0
- package/dist/feedback/pattern-tracker.d.ts.map +1 -0
- package/dist/feedback/types.d.ts +113 -0
- package/dist/feedback/types.d.ts.map +1 -0
- package/dist/generation/index.js +2 -1
- package/dist/generation/index.js.map +1 -1
- package/dist/index.cjs +3264 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3223 -132
- package/dist/index.js.map +1 -1
- package/dist/ir/explicit-parser.d.ts +86 -0
- package/dist/ir/explicit-parser.d.ts.map +1 -0
- package/dist/ir/explicit-renderer.d.ts +34 -0
- package/dist/ir/explicit-renderer.d.ts.map +1 -0
- package/dist/ir/from-interchange.d.ts +51 -0
- package/dist/ir/from-interchange.d.ts.map +1 -0
- package/dist/ir/index.d.ts +31 -0
- package/dist/ir/index.d.ts.map +1 -0
- package/dist/ir/index.js +1934 -0
- package/dist/ir/index.js.map +1 -0
- package/dist/ir/json-schema.d.ts +40 -0
- package/dist/ir/json-schema.d.ts.map +1 -0
- package/dist/ir/protocol-json.d.ts +73 -0
- package/dist/ir/protocol-json.d.ts.map +1 -0
- package/dist/ir/references.d.ts +19 -0
- package/dist/ir/references.d.ts.map +1 -0
- 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 +151 -0
- package/dist/ir/types.d.ts.map +1 -0
- package/dist/parsing/index.js +74 -10
- package/dist/parsing/index.js.map +1 -1
- package/dist/prompts/index.d.ts +9 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/prompt-generator.d.ts +49 -0
- package/dist/prompts/prompt-generator.d.ts.map +1 -0
- package/dist/prompts/types.d.ts +64 -0
- package/dist/prompts/types.d.ts.map +1 -0
- package/dist/schema/command-schema.d.ts +17 -0
- package/dist/schema/command-schema.d.ts.map +1 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/testing/index.js +8673 -8260
- package/dist/testing/index.js.map +1 -1
- package/dist/training/index.d.ts +11 -0
- package/dist/training/index.d.ts.map +1 -0
- package/dist/training/jsonl-writer.d.ts +42 -0
- package/dist/training/jsonl-writer.d.ts.map +1 -0
- package/dist/training/schema-synthesizer.d.ts +20 -0
- package/dist/training/schema-synthesizer.d.ts.map +1 -0
- package/dist/training/types.d.ts +73 -0
- package/dist/training/types.d.ts.map +1 -0
- package/package.json +5 -1
- package/src/aot/index.ts +1 -0
- package/src/aot/registry-bridge.test.ts +104 -0
- package/src/aot/registry-bridge.ts +66 -0
- package/src/api/create-dsl.test.ts +306 -0
- package/src/api/create-dsl.ts +21 -0
- package/src/api/dispatcher.test.ts +66 -0
- package/src/api/dispatcher.ts +77 -9
- package/src/api/domain-registry.ts +120 -4
- package/src/core/pattern-matching/pattern-matcher.test.ts +131 -0
- package/src/core/pattern-matching/pattern-matcher.ts +102 -19
- package/src/core/tokenization/morphology/base-normalizer.ts +249 -0
- package/src/core/tokenization/morphology/index.ts +3 -1
- package/src/core/types.ts +147 -2
- package/src/feedback/confidence-gate.test.ts +111 -0
- package/src/feedback/confidence-gate.ts +70 -0
- package/src/feedback/feedback-formatter.test.ts +198 -0
- package/src/feedback/feedback-formatter.ts +298 -0
- package/src/feedback/index.ts +24 -0
- package/src/feedback/pattern-tracker.test.ts +198 -0
- package/src/feedback/pattern-tracker.ts +140 -0
- package/src/feedback/types.ts +149 -0
- package/src/generation/pattern-generator.ts +2 -2
- package/src/index.ts +29 -0
- package/src/integration/llm-lse-integration.test.ts +457 -0
- package/src/ir/conformance.test.ts +120 -0
- package/src/ir/explicit-parser.test.ts +612 -0
- package/src/ir/explicit-parser.ts +793 -0
- package/src/ir/explicit-renderer.test.ts +229 -0
- package/src/ir/explicit-renderer.ts +198 -0
- package/src/ir/from-interchange.test.ts +853 -0
- package/src/ir/from-interchange.ts +594 -0
- package/src/ir/index.ts +77 -0
- package/src/ir/json-schema.test.ts +281 -0
- package/src/ir/json-schema.ts +299 -0
- package/src/ir/protocol-json.test.ts +1187 -0
- package/src/ir/protocol-json.ts +751 -0
- package/src/ir/references.test.ts +38 -0
- package/src/ir/references.ts +33 -0
- package/src/ir/round-trip.test.ts +557 -0
- package/src/ir/to-runtime-ast.ts +203 -0
- package/src/ir/types.ts +191 -0
- package/src/prompts/index.ts +15 -0
- package/src/prompts/prompt-generator.test.ts +386 -0
- package/src/prompts/prompt-generator.ts +531 -0
- package/src/prompts/types.ts +89 -0
- package/src/schema/command-schema.ts +19 -0
- package/src/training/index.ts +13 -0
- package/src/training/jsonl-writer.test.ts +138 -0
- package/src/training/jsonl-writer.ts +67 -0
- package/src/training/schema-synthesizer.test.ts +250 -0
- package/src/training/schema-synthesizer.ts +306 -0
- package/src/training/types.ts +103 -0
package/dist/index.cjs
CHANGED
|
@@ -21,10 +21,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
AOTOrchestrator: () => AOTOrchestrator,
|
|
24
|
+
BaseMorphologicalNormalizer: () => BaseMorphologicalNormalizer,
|
|
24
25
|
BaseTokenizer: () => BaseTokenizer,
|
|
25
26
|
CrossDomainDispatcher: () => CrossDomainDispatcher,
|
|
26
27
|
DEFAULT_OPERATORS: () => DEFAULT_OPERATORS,
|
|
27
28
|
DEFAULT_PUNCTUATION: () => DEFAULT_PUNCTUATION,
|
|
29
|
+
DEFAULT_REFERENCES: () => DEFAULT_REFERENCES,
|
|
28
30
|
DomainAwareScanner: () => DomainAwareScanner,
|
|
29
31
|
DomainRegistry: () => DomainRegistry,
|
|
30
32
|
GrammarTransformer: () => GrammarTransformer,
|
|
@@ -36,24 +38,31 @@ __export(index_exports, {
|
|
|
36
38
|
NumberExtractor: () => NumberExtractor,
|
|
37
39
|
OperatorExtractor: () => OperatorExtractor,
|
|
38
40
|
PatternMatcher: () => PatternMatcher,
|
|
41
|
+
PatternTracker: () => PatternTracker,
|
|
39
42
|
PunctuationExtractor: () => PunctuationExtractor,
|
|
40
43
|
StringLiteralExtractor: () => StringLiteralExtractor,
|
|
41
44
|
TokenStreamImpl: () => TokenStreamImpl,
|
|
42
45
|
UnicodeIdentifierExtractor: () => UnicodeIdentifierExtractor,
|
|
43
46
|
WhitespaceExtractor: () => WhitespaceExtractor,
|
|
44
47
|
accumulateBlocks: () => accumulateBlocks,
|
|
48
|
+
buildDisambiguation: () => buildDisambiguation,
|
|
49
|
+
buildFeedback: () => buildFeedback,
|
|
45
50
|
buildPhrase: () => buildPhrase,
|
|
46
51
|
buildTablesFromProfiles: () => buildTablesFromProfiles,
|
|
47
52
|
combineClassifiers: () => combineClassifiers,
|
|
53
|
+
convertValue: () => convertValue,
|
|
54
|
+
createAsyncNode: () => createAsyncNode,
|
|
48
55
|
createCommandNode: () => createCommandNode,
|
|
49
56
|
createCompoundNode: () => createCompoundNode,
|
|
50
57
|
createConditionalNode: () => createConditionalNode,
|
|
51
58
|
createDiagnosticCollector: () => createDiagnosticCollector,
|
|
52
59
|
createEventHandlerNode: () => createEventHandlerNode,
|
|
53
60
|
createExpression: () => createExpression,
|
|
61
|
+
createFlag: () => createFlag,
|
|
54
62
|
createLatinCharClassifiers: () => createLatinCharClassifiers,
|
|
55
63
|
createLiteral: () => createLiteral,
|
|
56
64
|
createLoopNode: () => createLoopNode,
|
|
65
|
+
createMatchNode: () => createMatchNode,
|
|
57
66
|
createMultiStatementParser: () => createMultiStatementParser,
|
|
58
67
|
createMultilingualDSL: () => createMultilingualDSL,
|
|
59
68
|
createPosition: () => createPosition,
|
|
@@ -64,6 +73,7 @@ __export(index_exports, {
|
|
|
64
73
|
createSimpleTokenizer: () => createSimpleTokenizer,
|
|
65
74
|
createToken: () => createToken,
|
|
66
75
|
createTokenizerContext: () => createTokenizerContext,
|
|
76
|
+
createTryNode: () => createTryNode,
|
|
67
77
|
createUnicodeRangeClassifier: () => createUnicodeRangeClassifier,
|
|
68
78
|
defineCommand: () => defineCommand,
|
|
69
79
|
defineRole: () => defineRole,
|
|
@@ -75,9 +85,15 @@ __export(index_exports, {
|
|
|
75
85
|
extractUrl: () => extractUrl,
|
|
76
86
|
extractValue: () => extractValue,
|
|
77
87
|
filterBySeverity: () => filterBySeverity,
|
|
88
|
+
fromEnvelopeJSON: () => fromEnvelopeJSON,
|
|
78
89
|
fromError: () => fromError,
|
|
90
|
+
fromInterchangeNode: () => fromInterchangeNode,
|
|
91
|
+
fromProtocolJSON: () => fromProtocolJSON,
|
|
92
|
+
generateExamples: () => generateExamples,
|
|
79
93
|
generatePattern: () => generatePattern,
|
|
80
94
|
generatePatternVariants: () => generatePatternVariants,
|
|
95
|
+
generatePrompt: () => generatePrompt,
|
|
96
|
+
generateProtocolReference: () => generateProtocolReference,
|
|
81
97
|
getAllPossessiveKeywords: () => getAllPossessiveKeywords,
|
|
82
98
|
getDefaultExtractors: () => getDefaultExtractors,
|
|
83
99
|
getPossessiveReference: () => getPossessiveReference,
|
|
@@ -88,8 +104,12 @@ __export(index_exports, {
|
|
|
88
104
|
isCSSPropertyRef: () => isCSSPropertyRef,
|
|
89
105
|
isCSSSelector: () => isCSSSelector,
|
|
90
106
|
isClassName: () => isClassName,
|
|
107
|
+
isCompoundSyntax: () => isCompoundSyntax,
|
|
91
108
|
isContextAwareExtractor: () => isContextAwareExtractor,
|
|
92
109
|
isDigit: () => isDigit,
|
|
110
|
+
isDocumentSyntax: () => isDocumentSyntax,
|
|
111
|
+
isEnvelope: () => isEnvelope,
|
|
112
|
+
isExplicitSyntax: () => isExplicitSyntax,
|
|
93
113
|
isIdSelector: () => isIdSelector,
|
|
94
114
|
isNumericValue: () => isNumericValue,
|
|
95
115
|
isPossessiveKeyword: () => isPossessiveKeyword,
|
|
@@ -99,17 +119,38 @@ __export(index_exports, {
|
|
|
99
119
|
isSelectorStart: () => isSelectorStart,
|
|
100
120
|
isTypeCompatible: () => isTypeCompatible,
|
|
101
121
|
isUrlStart: () => isUrlStart,
|
|
122
|
+
isValidReference: () => isValidReference2,
|
|
102
123
|
isVariableRef: () => isVariableRef,
|
|
103
124
|
isWhitespace: () => isWhitespace,
|
|
104
125
|
joinTokens: () => joinTokens,
|
|
126
|
+
jsonToSemanticNode: () => jsonToSemanticNode,
|
|
105
127
|
lookupKeyword: () => lookupKeyword,
|
|
106
128
|
lookupMarker: () => lookupMarker,
|
|
107
129
|
matchBest: () => matchBest,
|
|
108
130
|
matchPattern: () => matchPattern,
|
|
131
|
+
needsDisambiguation: () => needsDisambiguation,
|
|
109
132
|
noChange: () => noChange,
|
|
110
133
|
normalized: () => normalized,
|
|
134
|
+
parseCompound: () => parseCompound,
|
|
135
|
+
parseDocument: () => parseDocument,
|
|
136
|
+
parseExplicit: () => parseExplicit,
|
|
137
|
+
parseJSONL: () => parseJSONL,
|
|
111
138
|
patternMatcher: () => patternMatcher,
|
|
139
|
+
registryToAOTBackends: () => registryToAOTBackends,
|
|
140
|
+
renderDocument: () => renderDocument,
|
|
141
|
+
renderExplicit: () => renderExplicit,
|
|
142
|
+
renderExpr: () => renderExpr,
|
|
112
143
|
reorderRoles: () => reorderRoles,
|
|
144
|
+
semanticNodeToJSON: () => semanticNodeToJSON,
|
|
145
|
+
semanticNodeToRuntimeAST: () => semanticNodeToRuntimeAST,
|
|
146
|
+
semanticValueToAST: () => semanticValueToAST,
|
|
147
|
+
synthesizeFromSchemas: () => synthesizeFromSchemas,
|
|
148
|
+
toEnvelopeJSON: () => toEnvelopeJSON,
|
|
149
|
+
toJSONL: () => toJSONL,
|
|
150
|
+
toJSONLRow: () => toJSONLRow,
|
|
151
|
+
toProtocolJSON: () => toProtocolJSON,
|
|
152
|
+
validateProtocolJSON: () => validateProtocolJSON,
|
|
153
|
+
validateSemanticJSON: () => validateSemanticJSON,
|
|
113
154
|
validateValueType: () => validateValueType,
|
|
114
155
|
withDefaultExtractors: () => withDefaultExtractors
|
|
115
156
|
});
|
|
@@ -131,6 +172,9 @@ function createPropertyPath(object, property) {
|
|
|
131
172
|
function createExpression(raw) {
|
|
132
173
|
return { type: "expression", raw };
|
|
133
174
|
}
|
|
175
|
+
function createFlag(name, enabled = true) {
|
|
176
|
+
return { type: "flag", name, enabled };
|
|
177
|
+
}
|
|
134
178
|
function createCommandNode(action, roles, metadata) {
|
|
135
179
|
const rolesMap = roles instanceof Map ? roles : new Map(Object.entries(roles));
|
|
136
180
|
const node = {
|
|
@@ -182,6 +226,7 @@ function createCompoundNode(statements, chainType = "sequential", metadata) {
|
|
|
182
226
|
return metadata ? { ...base, metadata } : base;
|
|
183
227
|
}
|
|
184
228
|
function extractValue(value) {
|
|
229
|
+
if (value.type === "flag") return value.name;
|
|
185
230
|
if ("raw" in value && value.raw !== void 0) return String(value.raw);
|
|
186
231
|
if ("value" in value && value.value !== void 0) return String(value.value);
|
|
187
232
|
if (value.type === "property-path") return `${extractValue(value.object)}.${value.property}`;
|
|
@@ -208,6 +253,38 @@ function createLoopNode(action, roles, loopVariant, body, loopVariable, indexVar
|
|
|
208
253
|
...metadata && { metadata }
|
|
209
254
|
};
|
|
210
255
|
}
|
|
256
|
+
function createTryNode(body, catchBranch, finallyBranch, metadata) {
|
|
257
|
+
return {
|
|
258
|
+
kind: "command",
|
|
259
|
+
action: "try",
|
|
260
|
+
roles: /* @__PURE__ */ new Map(),
|
|
261
|
+
body,
|
|
262
|
+
...catchBranch && catchBranch.length > 0 ? { catchBranch } : {},
|
|
263
|
+
...finallyBranch && finallyBranch.length > 0 ? { finallyBranch } : {},
|
|
264
|
+
...metadata ? { metadata } : {}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function createAsyncNode(variant, asyncBody, metadata) {
|
|
268
|
+
return {
|
|
269
|
+
kind: "command",
|
|
270
|
+
action: variant,
|
|
271
|
+
roles: /* @__PURE__ */ new Map(),
|
|
272
|
+
asyncVariant: variant,
|
|
273
|
+
asyncBody,
|
|
274
|
+
...metadata ? { metadata } : {}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function createMatchNode(roles, arms, defaultArm, metadata) {
|
|
278
|
+
const rolesMap = roles instanceof Map ? roles : new Map(Object.entries(roles));
|
|
279
|
+
return {
|
|
280
|
+
kind: "command",
|
|
281
|
+
action: "match",
|
|
282
|
+
roles: rolesMap,
|
|
283
|
+
arms,
|
|
284
|
+
...defaultArm && defaultArm.length > 0 ? { defaultArm } : {},
|
|
285
|
+
...metadata ? { metadata } : {}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
211
288
|
|
|
212
289
|
// src/core/logger.ts
|
|
213
290
|
var DebugLogger = class {
|
|
@@ -463,7 +540,7 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
463
540
|
* a token and the immediately following pattern token fails, the matcher
|
|
464
541
|
* resets to before the optional role and retries the failed token.
|
|
465
542
|
*/
|
|
466
|
-
matchTokenSequence(tokens, patternTokens, captured) {
|
|
543
|
+
matchTokenSequence(tokens, patternTokens, captured, parentStopMarkers) {
|
|
467
544
|
const firstPatternToken = patternTokens[0];
|
|
468
545
|
const patternExpectsConjunction = firstPatternToken?.type === "literal" && (firstPatternToken.value === "and" || firstPatternToken.value === "then" || firstPatternToken.alternatives?.includes("and") || firstPatternToken.alternatives?.includes("then"));
|
|
469
546
|
if (this.currentProfile?.code === "ar" && !patternExpectsConjunction) {
|
|
@@ -487,6 +564,9 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
487
564
|
);
|
|
488
565
|
if (patternToken.type === "role" && patternToken.greedy) {
|
|
489
566
|
const stopMarkers = this.collectStopMarkers(patternTokens, i + 1);
|
|
567
|
+
if (parentStopMarkers) {
|
|
568
|
+
for (const m of parentStopMarkers) stopMarkers.add(m);
|
|
569
|
+
}
|
|
490
570
|
const values = [];
|
|
491
571
|
while (!tokens.isAtEnd()) {
|
|
492
572
|
const nextToken = tokens.peek();
|
|
@@ -506,8 +586,58 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
506
586
|
return false;
|
|
507
587
|
}
|
|
508
588
|
}
|
|
589
|
+
if (patternToken.type === "group") {
|
|
590
|
+
const siblingStopMarkers = this.collectStopMarkers(patternTokens, i + 1);
|
|
591
|
+
if (parentStopMarkers) {
|
|
592
|
+
for (const m of parentStopMarkers) siblingStopMarkers.add(m);
|
|
593
|
+
}
|
|
594
|
+
const groupMatched = this.matchGroupToken(
|
|
595
|
+
tokens,
|
|
596
|
+
patternToken,
|
|
597
|
+
captured,
|
|
598
|
+
siblingStopMarkers
|
|
599
|
+
);
|
|
600
|
+
if (groupMatched) {
|
|
601
|
+
prevOptionalMark = null;
|
|
602
|
+
prevOptionalRole = null;
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
if (this.isOptional(patternToken)) {
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
509
610
|
const isOptionalRole = patternToken.type === "role" && patternToken.optional === true;
|
|
510
611
|
const markBefore = isOptionalRole ? tokens.mark() : null;
|
|
612
|
+
const isRoleToken = patternToken.type === "role";
|
|
613
|
+
if (isRoleToken && !isOptionalRole) {
|
|
614
|
+
const nextPT = i + 1 < patternTokens.length ? patternTokens[i + 1] : null;
|
|
615
|
+
if (nextPT?.type === "literal") {
|
|
616
|
+
const currentToken = tokens.peek();
|
|
617
|
+
if (currentToken && this.getMatchType(currentToken, nextPT.value) !== "none") {
|
|
618
|
+
this.logger.debug(
|
|
619
|
+
" >> MARKER-STEAL GUARD: current token matches next literal, skipping role"
|
|
620
|
+
);
|
|
621
|
+
this.logger.debug(" >> Token match FAILED");
|
|
622
|
+
if (this.isOptional(patternToken)) {
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
if (prevOptionalMark && prevOptionalRole) {
|
|
626
|
+
this.logger.debug(" >> BACKTRACKING: undoing optional role", prevOptionalRole);
|
|
627
|
+
tokens.reset(prevOptionalMark);
|
|
628
|
+
captured.delete(prevOptionalRole);
|
|
629
|
+
prevOptionalMark = null;
|
|
630
|
+
prevOptionalRole = null;
|
|
631
|
+
const retryMatched = this.matchPatternToken(tokens, patternToken, captured);
|
|
632
|
+
this.logger.debug(" >> Backtrack retry result:", retryMatched);
|
|
633
|
+
if (retryMatched) {
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
511
641
|
const matched = this.matchPatternToken(tokens, patternToken, captured);
|
|
512
642
|
this.logger.debug(" >> Match result:", matched);
|
|
513
643
|
if (matched) {
|
|
@@ -628,7 +758,7 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
628
758
|
const possessiveValue = this.tryMatchPossessiveExpression(tokens);
|
|
629
759
|
if (possessiveValue) {
|
|
630
760
|
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
631
|
-
if (!
|
|
761
|
+
if (!isTypeCompatible(possessiveValue.type, patternToken.expectedTypes)) {
|
|
632
762
|
return patternToken.optional || false;
|
|
633
763
|
}
|
|
634
764
|
}
|
|
@@ -638,7 +768,7 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
638
768
|
const methodCallValue = this.tryMatchMethodCallExpression(tokens);
|
|
639
769
|
if (methodCallValue) {
|
|
640
770
|
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
641
|
-
if (!
|
|
771
|
+
if (!isTypeCompatible(methodCallValue.type, patternToken.expectedTypes)) {
|
|
642
772
|
return patternToken.optional || false;
|
|
643
773
|
}
|
|
644
774
|
}
|
|
@@ -658,7 +788,7 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
658
788
|
const propertyAccessValue = this.tryMatchPropertyAccessExpression(tokens);
|
|
659
789
|
if (propertyAccessValue) {
|
|
660
790
|
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
661
|
-
if (!
|
|
791
|
+
if (!isTypeCompatible(propertyAccessValue.type, patternToken.expectedTypes)) {
|
|
662
792
|
return patternToken.optional || false;
|
|
663
793
|
}
|
|
664
794
|
}
|
|
@@ -978,10 +1108,15 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
978
1108
|
* (e.g., a value like 'hello') to sit between groups without blocking later
|
|
979
1109
|
* marker matches.
|
|
980
1110
|
*/
|
|
981
|
-
matchGroupToken(tokens, patternToken, captured) {
|
|
1111
|
+
matchGroupToken(tokens, patternToken, captured, parentStopMarkers) {
|
|
982
1112
|
const mark = tokens.mark();
|
|
983
1113
|
const capturedBefore = new Set(captured.keys());
|
|
984
|
-
const success = this.matchTokenSequence(
|
|
1114
|
+
const success = this.matchTokenSequence(
|
|
1115
|
+
tokens,
|
|
1116
|
+
patternToken.tokens,
|
|
1117
|
+
captured,
|
|
1118
|
+
parentStopMarkers
|
|
1119
|
+
);
|
|
985
1120
|
if (success) return true;
|
|
986
1121
|
tokens.reset(mark);
|
|
987
1122
|
for (const role of captured.keys()) {
|
|
@@ -1003,7 +1138,12 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
1003
1138
|
"- skipping intervening tokens"
|
|
1004
1139
|
);
|
|
1005
1140
|
for (let s = 0; s < offset; s++) tokens.advance();
|
|
1006
|
-
const retrySuccess = this.matchTokenSequence(
|
|
1141
|
+
const retrySuccess = this.matchTokenSequence(
|
|
1142
|
+
tokens,
|
|
1143
|
+
patternToken.tokens,
|
|
1144
|
+
captured,
|
|
1145
|
+
parentStopMarkers
|
|
1146
|
+
);
|
|
1007
1147
|
if (retrySuccess) return true;
|
|
1008
1148
|
tokens.reset(mark);
|
|
1009
1149
|
for (const role of captured.keys()) {
|
|
@@ -1070,7 +1210,7 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
1070
1210
|
break;
|
|
1071
1211
|
}
|
|
1072
1212
|
}
|
|
1073
|
-
break;
|
|
1213
|
+
if (!pt.optional) break;
|
|
1074
1214
|
}
|
|
1075
1215
|
}
|
|
1076
1216
|
return markers;
|
|
@@ -1080,8 +1220,9 @@ var _PatternMatcher = class _PatternMatcher {
|
|
|
1080
1220
|
*/
|
|
1081
1221
|
isStopMarker(token, stopMarkers) {
|
|
1082
1222
|
if (stopMarkers.size === 0) return false;
|
|
1083
|
-
|
|
1084
|
-
|
|
1223
|
+
if (stopMarkers.has(token.value.toLowerCase())) return true;
|
|
1224
|
+
if (token.normalized && stopMarkers.has(token.normalized.toLowerCase())) return true;
|
|
1225
|
+
return false;
|
|
1085
1226
|
}
|
|
1086
1227
|
/**
|
|
1087
1228
|
* Convert a language token to a semantic value.
|
|
@@ -1486,7 +1627,7 @@ function addRoleWithMarker(tokens, roleSpec, profile) {
|
|
|
1486
1627
|
if (marker) {
|
|
1487
1628
|
const markerInfo = profile.roleMarkers?.[roleSpec.role];
|
|
1488
1629
|
const defaultPosition = profile.wordOrder === "SOV" ? "after" : "before";
|
|
1489
|
-
const position = markerInfo?.position ?? defaultPosition;
|
|
1630
|
+
const position = roleSpec.markerPosition ?? markerInfo?.position ?? defaultPosition;
|
|
1490
1631
|
if (isOptional) {
|
|
1491
1632
|
if (position === "before") {
|
|
1492
1633
|
tokens.push({
|
|
@@ -2026,153 +2167,2674 @@ function createTokenizerContext(tokenizer) {
|
|
|
2026
2167
|
return ctx;
|
|
2027
2168
|
}
|
|
2028
2169
|
|
|
2029
|
-
// src/
|
|
2030
|
-
var
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2170
|
+
// src/ir/references.ts
|
|
2171
|
+
var DEFAULT_REFERENCES = /* @__PURE__ */ new Set([
|
|
2172
|
+
"me",
|
|
2173
|
+
"you",
|
|
2174
|
+
"it",
|
|
2175
|
+
"result",
|
|
2176
|
+
"event",
|
|
2177
|
+
"target",
|
|
2178
|
+
"body"
|
|
2179
|
+
]);
|
|
2180
|
+
function isValidReference2(value, referenceSet = DEFAULT_REFERENCES) {
|
|
2181
|
+
return referenceSet.has(value);
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
// src/ir/explicit-parser.ts
|
|
2185
|
+
var STRUCTURAL_ROLES = /* @__PURE__ */ new Set([
|
|
2186
|
+
"body",
|
|
2187
|
+
"then",
|
|
2188
|
+
"else",
|
|
2189
|
+
"condition",
|
|
2190
|
+
"loop-body",
|
|
2191
|
+
"variable",
|
|
2192
|
+
"catch",
|
|
2193
|
+
// v1.2: try/catch/finally
|
|
2194
|
+
"finally"
|
|
2195
|
+
// v1.2: try/catch/finally
|
|
2196
|
+
]);
|
|
2197
|
+
function isNestedCommand(value) {
|
|
2198
|
+
const inner = value.slice(1, -1);
|
|
2199
|
+
let depth = 0;
|
|
2200
|
+
for (const ch of inner) {
|
|
2201
|
+
if (ch === "[") depth++;
|
|
2202
|
+
else if (ch === "]") depth--;
|
|
2203
|
+
else if (depth === 0 && (ch === " " || ch === ":")) return true;
|
|
2204
|
+
}
|
|
2205
|
+
return false;
|
|
2206
|
+
}
|
|
2207
|
+
function parseExplicit(input, options = {}) {
|
|
2208
|
+
const trimmed = input.trim();
|
|
2209
|
+
const collect = options.collectDiagnostics === true;
|
|
2210
|
+
const diagnostics = [];
|
|
2211
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) {
|
|
2212
|
+
throw new Error("Explicit syntax must be wrapped in brackets: [command role:value ...]");
|
|
2213
|
+
}
|
|
2214
|
+
const content = trimmed.slice(1, -1).trim();
|
|
2215
|
+
if (!content) {
|
|
2216
|
+
throw new Error("Empty explicit statement");
|
|
2217
|
+
}
|
|
2218
|
+
const tokens = tokenizeExplicit(content);
|
|
2219
|
+
if (tokens.length === 0) {
|
|
2220
|
+
throw new Error("No command specified in explicit statement");
|
|
2221
|
+
}
|
|
2222
|
+
const command = tokens[0].toLowerCase();
|
|
2223
|
+
const roles = /* @__PURE__ */ new Map();
|
|
2224
|
+
const refSet = options.referenceSet ?? DEFAULT_REFERENCES;
|
|
2225
|
+
const schema = options.schemaLookup?.getSchema(command);
|
|
2226
|
+
const validRoleNames = schema ? new Set(schema.roles.map((r) => r.role)) : null;
|
|
2227
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
2228
|
+
const token = tokens[i];
|
|
2229
|
+
if (token.startsWith("+") || token.startsWith("~")) {
|
|
2230
|
+
const enabled = token.startsWith("+");
|
|
2231
|
+
const flagName = token.slice(1);
|
|
2232
|
+
if (!flagName) {
|
|
2233
|
+
if (collect) {
|
|
2234
|
+
diagnostics.push({
|
|
2235
|
+
severity: "error",
|
|
2236
|
+
code: "EMPTY_FLAG",
|
|
2237
|
+
message: `Empty flag name: "${token}"`,
|
|
2238
|
+
source: "schema"
|
|
2239
|
+
});
|
|
2240
|
+
continue;
|
|
2241
|
+
}
|
|
2242
|
+
throw new Error(`Empty flag name: "${token}"`);
|
|
2243
|
+
}
|
|
2244
|
+
if (validRoleNames && !validRoleNames.has(flagName)) {
|
|
2245
|
+
const roleList = [...validRoleNames].join(", ");
|
|
2246
|
+
if (collect) {
|
|
2247
|
+
diagnostics.push({
|
|
2248
|
+
severity: "error",
|
|
2249
|
+
code: "UNKNOWN_FLAG",
|
|
2250
|
+
message: `Unknown flag "${flagName}" for command "${command}"`,
|
|
2251
|
+
source: "schema",
|
|
2252
|
+
suggestions: [...validRoleNames].map((r) => `+${r}`)
|
|
2253
|
+
});
|
|
2254
|
+
roles.set(flagName, createFlag(flagName, enabled));
|
|
2255
|
+
continue;
|
|
2256
|
+
}
|
|
2257
|
+
throw new Error(
|
|
2258
|
+
`Unknown flag "${flagName}" for command "${command}". Valid roles: ${roleList}`
|
|
2259
|
+
);
|
|
2260
|
+
}
|
|
2261
|
+
roles.set(flagName, createFlag(flagName, enabled));
|
|
2262
|
+
continue;
|
|
2263
|
+
}
|
|
2264
|
+
const colonIndex = token.indexOf(":");
|
|
2265
|
+
if (colonIndex === -1) {
|
|
2266
|
+
if (collect) {
|
|
2267
|
+
diagnostics.push({
|
|
2268
|
+
severity: "error",
|
|
2269
|
+
code: "INVALID_ROLE_FORMAT",
|
|
2270
|
+
message: `Invalid role format: "${token}". Expected role:value or +flag`,
|
|
2271
|
+
source: "schema"
|
|
2272
|
+
});
|
|
2273
|
+
continue;
|
|
2274
|
+
}
|
|
2275
|
+
throw new Error(`Invalid role format: "${token}". Expected role:value or +flag`);
|
|
2276
|
+
}
|
|
2277
|
+
const roleName = token.slice(0, colonIndex);
|
|
2278
|
+
if (validRoleNames && !STRUCTURAL_ROLES.has(roleName) && !validRoleNames.has(roleName)) {
|
|
2279
|
+
const roleList = [...validRoleNames].join(", ");
|
|
2280
|
+
if (collect) {
|
|
2281
|
+
diagnostics.push({
|
|
2282
|
+
severity: "error",
|
|
2283
|
+
code: "UNKNOWN_ROLE",
|
|
2284
|
+
message: `Unknown role "${roleName}" for command "${command}"`,
|
|
2285
|
+
source: "schema",
|
|
2286
|
+
suggestions: [...validRoleNames]
|
|
2287
|
+
});
|
|
2288
|
+
} else {
|
|
2289
|
+
throw new Error(
|
|
2290
|
+
`Unknown role "${roleName}" for command "${command}". Valid roles: ${roleList}`
|
|
2291
|
+
);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
const role = roleName;
|
|
2295
|
+
const valueStr = token.slice(colonIndex + 1);
|
|
2296
|
+
if (STRUCTURAL_ROLES.has(roleName) && valueStr.startsWith("[") && isNestedCommand(valueStr)) {
|
|
2297
|
+
const nestedEnd = findMatchingBracket(token, colonIndex + 1);
|
|
2298
|
+
const nestedSyntax = token.slice(colonIndex + 1, nestedEnd + 1);
|
|
2299
|
+
roles.set(role, { type: "expression", raw: nestedSyntax });
|
|
2300
|
+
continue;
|
|
2301
|
+
}
|
|
2302
|
+
const value = parseExplicitValue(valueStr, refSet);
|
|
2303
|
+
roles.set(role, value);
|
|
2304
|
+
}
|
|
2305
|
+
if (schema && command !== "on") {
|
|
2306
|
+
for (const roleSpec of schema.roles) {
|
|
2307
|
+
if (roleSpec.required && !roles.has(roleSpec.role) && !roleSpec.default) {
|
|
2308
|
+
if (collect) {
|
|
2309
|
+
diagnostics.push({
|
|
2310
|
+
severity: "error",
|
|
2311
|
+
code: "MISSING_REQUIRED_ROLE",
|
|
2312
|
+
message: `Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`,
|
|
2313
|
+
source: "schema"
|
|
2314
|
+
});
|
|
2315
|
+
} else {
|
|
2316
|
+
throw new Error(
|
|
2317
|
+
`Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`
|
|
2318
|
+
);
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2037
2321
|
}
|
|
2038
2322
|
}
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2323
|
+
const attachDiagnostics = (node) => {
|
|
2324
|
+
if (diagnostics.length === 0) return node;
|
|
2325
|
+
return { ...node, diagnostics };
|
|
2326
|
+
};
|
|
2327
|
+
switch (command) {
|
|
2328
|
+
case "on": {
|
|
2329
|
+
const eventValue = roles.get("event");
|
|
2330
|
+
if (!eventValue) {
|
|
2331
|
+
if (collect) {
|
|
2332
|
+
diagnostics.push({
|
|
2333
|
+
severity: "error",
|
|
2334
|
+
code: "MISSING_EVENT_ROLE",
|
|
2335
|
+
message: "Event handler requires event role: [on event:click ...]",
|
|
2336
|
+
source: "schema"
|
|
2337
|
+
});
|
|
2338
|
+
return attachDiagnostics(
|
|
2339
|
+
createEventHandlerNode("on", roles, [], {
|
|
2340
|
+
sourceLanguage: "explicit"
|
|
2341
|
+
})
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
2344
|
+
throw new Error("Event handler requires event role: [on event:click ...]");
|
|
2345
|
+
}
|
|
2346
|
+
const body = extractStructuralBody(roles, "body", options);
|
|
2347
|
+
roles.delete("body");
|
|
2348
|
+
return attachDiagnostics(
|
|
2349
|
+
createEventHandlerNode("on", roles, body, {
|
|
2350
|
+
sourceLanguage: "explicit"
|
|
2351
|
+
})
|
|
2352
|
+
);
|
|
2045
2353
|
}
|
|
2046
|
-
|
|
2354
|
+
case "if": {
|
|
2355
|
+
const thenBranch = extractStructuralBody(roles, "then", options);
|
|
2356
|
+
const elseBranch = extractStructuralBody(roles, "else", options);
|
|
2357
|
+
roles.delete("then");
|
|
2358
|
+
roles.delete("else");
|
|
2359
|
+
return attachDiagnostics(
|
|
2360
|
+
createConditionalNode(
|
|
2361
|
+
command,
|
|
2362
|
+
roles,
|
|
2363
|
+
thenBranch,
|
|
2364
|
+
elseBranch.length > 0 ? elseBranch : void 0,
|
|
2365
|
+
{
|
|
2366
|
+
sourceLanguage: "explicit"
|
|
2367
|
+
}
|
|
2368
|
+
)
|
|
2369
|
+
);
|
|
2370
|
+
}
|
|
2371
|
+
case "repeat": {
|
|
2372
|
+
const loopBody = extractStructuralBody(roles, "loop-body", options);
|
|
2373
|
+
const loopVariantValue = roles.get("loop-variant");
|
|
2374
|
+
const variableValue = roles.get("variable");
|
|
2375
|
+
const indexVariableValue = roles.get("index-variable");
|
|
2376
|
+
roles.delete("loop-body");
|
|
2377
|
+
roles.delete("loop-variant");
|
|
2378
|
+
roles.delete("variable");
|
|
2379
|
+
roles.delete("index-variable");
|
|
2380
|
+
let variant;
|
|
2381
|
+
if (loopVariantValue && loopVariantValue.type === "literal" && typeof loopVariantValue.value === "string") {
|
|
2382
|
+
variant = loopVariantValue.value;
|
|
2383
|
+
} else if (roles.has("quantity")) {
|
|
2384
|
+
variant = "times";
|
|
2385
|
+
} else if (roles.has("source")) {
|
|
2386
|
+
variant = "for";
|
|
2387
|
+
} else if (roles.has("condition")) {
|
|
2388
|
+
variant = "while";
|
|
2389
|
+
} else {
|
|
2390
|
+
variant = "forever";
|
|
2391
|
+
}
|
|
2392
|
+
const loopVariable = variableValue?.type === "literal" && typeof variableValue.value === "string" ? variableValue.value : variableValue?.type === "expression" ? variableValue.raw : void 0;
|
|
2393
|
+
const indexVariable = indexVariableValue?.type === "literal" && typeof indexVariableValue.value === "string" ? indexVariableValue.value : void 0;
|
|
2394
|
+
return attachDiagnostics(
|
|
2395
|
+
createLoopNode(command, roles, variant, loopBody, loopVariable, indexVariable, {
|
|
2396
|
+
sourceLanguage: "explicit"
|
|
2397
|
+
})
|
|
2398
|
+
);
|
|
2399
|
+
}
|
|
2400
|
+
default:
|
|
2401
|
+
return attachDiagnostics(
|
|
2402
|
+
createCommandNode(command, roles, {
|
|
2403
|
+
sourceLanguage: "explicit"
|
|
2404
|
+
})
|
|
2405
|
+
);
|
|
2047
2406
|
}
|
|
2048
|
-
|
|
2049
|
-
|
|
2407
|
+
}
|
|
2408
|
+
function isExplicitSyntax(input) {
|
|
2409
|
+
const trimmed = input.trim();
|
|
2410
|
+
return trimmed.startsWith("[") && trimmed.endsWith("]");
|
|
2411
|
+
}
|
|
2412
|
+
var CHAIN_KEYWORDS = /* @__PURE__ */ new Set(["then", "and", "async", "sequential"]);
|
|
2413
|
+
function splitCompoundSegments(input) {
|
|
2414
|
+
const segments = [];
|
|
2415
|
+
let chainType = null;
|
|
2416
|
+
let current = "";
|
|
2417
|
+
let bracketDepth = 0;
|
|
2418
|
+
let inString = false;
|
|
2419
|
+
let stringChar = "";
|
|
2420
|
+
for (let i = 0; i < input.length; i++) {
|
|
2421
|
+
const char = input[i];
|
|
2422
|
+
if (inString) {
|
|
2423
|
+
current += char;
|
|
2424
|
+
if (char === stringChar && input[i - 1] !== "\\") {
|
|
2425
|
+
inString = false;
|
|
2426
|
+
}
|
|
2427
|
+
continue;
|
|
2428
|
+
}
|
|
2429
|
+
if (char === '"' || char === "'") {
|
|
2430
|
+
inString = true;
|
|
2431
|
+
stringChar = char;
|
|
2432
|
+
current += char;
|
|
2433
|
+
continue;
|
|
2434
|
+
}
|
|
2435
|
+
if (char === "[") {
|
|
2436
|
+
bracketDepth++;
|
|
2437
|
+
current += char;
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2440
|
+
if (char === "]") {
|
|
2441
|
+
bracketDepth--;
|
|
2442
|
+
current += char;
|
|
2443
|
+
if (bracketDepth === 0) {
|
|
2444
|
+
const rest = input.slice(i + 1);
|
|
2445
|
+
const pipeMatch = rest.match(/^\s*\|>\s*/);
|
|
2446
|
+
if (pipeMatch) {
|
|
2447
|
+
segments.push(current.trim());
|
|
2448
|
+
current = "";
|
|
2449
|
+
chainType ?? (chainType = "pipe");
|
|
2450
|
+
i += pipeMatch[0].length;
|
|
2451
|
+
continue;
|
|
2452
|
+
}
|
|
2453
|
+
const keywordMatch = rest.match(/^\s+(then|and|async|sequential)\s+/);
|
|
2454
|
+
if (keywordMatch && CHAIN_KEYWORDS.has(keywordMatch[1])) {
|
|
2455
|
+
segments.push(current.trim());
|
|
2456
|
+
current = "";
|
|
2457
|
+
chainType ?? (chainType = keywordMatch[1]);
|
|
2458
|
+
i += keywordMatch[0].length;
|
|
2459
|
+
continue;
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
continue;
|
|
2463
|
+
}
|
|
2464
|
+
current += char;
|
|
2050
2465
|
}
|
|
2051
|
-
|
|
2052
|
-
|
|
2466
|
+
if (current.trim()) {
|
|
2467
|
+
segments.push(current.trim());
|
|
2053
2468
|
}
|
|
2054
|
-
|
|
2055
|
-
|
|
2469
|
+
return { segments, chainType };
|
|
2470
|
+
}
|
|
2471
|
+
function parseCompound(input, options) {
|
|
2472
|
+
const trimmed = input.trim();
|
|
2473
|
+
const { annotations, remainder } = extractAnnotations(trimmed);
|
|
2474
|
+
const { segments, chainType } = splitCompoundSegments(remainder);
|
|
2475
|
+
let node;
|
|
2476
|
+
if (segments.length <= 1) {
|
|
2477
|
+
node = parseExplicit(segments[0] || remainder, options);
|
|
2478
|
+
} else {
|
|
2479
|
+
const statements = segments.map((seg) => parseExplicit(seg, options));
|
|
2480
|
+
node = createCompoundNode(statements, chainType ?? "sequential");
|
|
2056
2481
|
}
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2482
|
+
if (annotations.length > 0) {
|
|
2483
|
+
return { ...node, annotations };
|
|
2484
|
+
}
|
|
2485
|
+
return node;
|
|
2486
|
+
}
|
|
2487
|
+
function extractAnnotations(input) {
|
|
2488
|
+
const annotations = [];
|
|
2489
|
+
let pos = 0;
|
|
2490
|
+
while (pos < input.length) {
|
|
2491
|
+
while (pos < input.length && (input[pos] === " " || input[pos] === " ")) {
|
|
2492
|
+
pos++;
|
|
2065
2493
|
}
|
|
2494
|
+
if (pos >= input.length || input[pos] !== "@") {
|
|
2495
|
+
break;
|
|
2496
|
+
}
|
|
2497
|
+
pos++;
|
|
2498
|
+
const nameStart = pos;
|
|
2499
|
+
while (pos < input.length && /[A-Za-z0-9\-_]/.test(input[pos])) {
|
|
2500
|
+
pos++;
|
|
2501
|
+
}
|
|
2502
|
+
const name = input.slice(nameStart, pos);
|
|
2503
|
+
if (!name) break;
|
|
2504
|
+
let value;
|
|
2505
|
+
if (pos < input.length && input[pos] === "(") {
|
|
2506
|
+
pos++;
|
|
2507
|
+
const valueStart = pos;
|
|
2508
|
+
if (pos < input.length && (input[pos] === '"' || input[pos] === "'")) {
|
|
2509
|
+
const quote = input[pos];
|
|
2510
|
+
pos++;
|
|
2511
|
+
while (pos < input.length && input[pos] !== quote) {
|
|
2512
|
+
if (input[pos] === "\\") pos++;
|
|
2513
|
+
pos++;
|
|
2514
|
+
}
|
|
2515
|
+
pos++;
|
|
2516
|
+
value = input.slice(valueStart + 1, pos - 1);
|
|
2517
|
+
} else {
|
|
2518
|
+
while (pos < input.length && input[pos] !== ")") {
|
|
2519
|
+
pos++;
|
|
2520
|
+
}
|
|
2521
|
+
value = input.slice(valueStart, pos);
|
|
2522
|
+
}
|
|
2523
|
+
if (pos < input.length && input[pos] === ")") {
|
|
2524
|
+
pos++;
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
annotations.push(value !== void 0 ? { name, value } : { name });
|
|
2066
2528
|
}
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2529
|
+
return { annotations, remainder: input.slice(pos).trim() };
|
|
2530
|
+
}
|
|
2531
|
+
var VERSION_HEADER_RE = /^#!lse\s+(\d+\.\d+(?:\.\d+)?)\s*$/;
|
|
2532
|
+
function parseDocument(input, options) {
|
|
2533
|
+
const lines = input.split("\n");
|
|
2534
|
+
let lseVersion = "1.0";
|
|
2535
|
+
const nodes = [];
|
|
2536
|
+
let startLine = 0;
|
|
2537
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2538
|
+
const trimmed = lines[i].trim();
|
|
2539
|
+
if (!trimmed) continue;
|
|
2540
|
+
const versionMatch = trimmed.match(VERSION_HEADER_RE);
|
|
2541
|
+
if (versionMatch) {
|
|
2542
|
+
lseVersion = versionMatch[1];
|
|
2543
|
+
startLine = i + 1;
|
|
2544
|
+
}
|
|
2545
|
+
break;
|
|
2546
|
+
}
|
|
2547
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
2548
|
+
const trimmed = lines[i].trim();
|
|
2549
|
+
if (!trimmed) continue;
|
|
2550
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("#") && !trimmed.startsWith("#!")) {
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2553
|
+
nodes.push(parseCompound(trimmed, options));
|
|
2554
|
+
}
|
|
2555
|
+
return { lseVersion, nodes };
|
|
2556
|
+
}
|
|
2557
|
+
function isCompoundSyntax(input) {
|
|
2558
|
+
const { segments } = splitCompoundSegments(input.trim());
|
|
2559
|
+
return segments.length > 1;
|
|
2560
|
+
}
|
|
2561
|
+
function isDocumentSyntax(input) {
|
|
2562
|
+
const trimmed = input.trim();
|
|
2563
|
+
if (trimmed.includes("\n")) return true;
|
|
2564
|
+
if (VERSION_HEADER_RE.test(trimmed)) return true;
|
|
2565
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("#") && !trimmed.startsWith("#!")) {
|
|
2566
|
+
return true;
|
|
2070
2567
|
}
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2568
|
+
return false;
|
|
2569
|
+
}
|
|
2570
|
+
function extractStructuralBody(roles, roleName, options) {
|
|
2571
|
+
const value = roles.get(roleName);
|
|
2572
|
+
if (!value) return [];
|
|
2573
|
+
if (value.type === "expression") {
|
|
2574
|
+
return [parseExplicit(value.raw, options)];
|
|
2575
|
+
}
|
|
2576
|
+
return [];
|
|
2577
|
+
}
|
|
2578
|
+
function inferSelectorKind(value) {
|
|
2579
|
+
if (!value) return void 0;
|
|
2580
|
+
if (value.startsWith("#")) return "id";
|
|
2581
|
+
if (value.startsWith(".")) return "class";
|
|
2582
|
+
if (value.startsWith("[")) return "attribute";
|
|
2583
|
+
if (value.startsWith("<") || value.startsWith("*")) return "element";
|
|
2584
|
+
if (/[>+~ ]/.test(value) && value.length > 1) return "complex";
|
|
2585
|
+
return void 0;
|
|
2586
|
+
}
|
|
2587
|
+
function tokenizeExplicit(content) {
|
|
2588
|
+
const tokens = [];
|
|
2589
|
+
let current = "";
|
|
2590
|
+
let inString = false;
|
|
2591
|
+
let stringChar = "";
|
|
2592
|
+
let bracketDepth = 0;
|
|
2593
|
+
for (let i = 0; i < content.length; i++) {
|
|
2594
|
+
const char = content[i];
|
|
2595
|
+
if (inString) {
|
|
2596
|
+
current += char;
|
|
2597
|
+
if (char === stringChar && content[i - 1] !== "\\") {
|
|
2598
|
+
inString = false;
|
|
2599
|
+
}
|
|
2600
|
+
continue;
|
|
2601
|
+
}
|
|
2602
|
+
if (char === '"' || char === "'") {
|
|
2603
|
+
inString = true;
|
|
2604
|
+
stringChar = char;
|
|
2605
|
+
current += char;
|
|
2606
|
+
continue;
|
|
2607
|
+
}
|
|
2608
|
+
if (char === "[") {
|
|
2609
|
+
bracketDepth++;
|
|
2610
|
+
current += char;
|
|
2611
|
+
continue;
|
|
2612
|
+
}
|
|
2613
|
+
if (char === "]") {
|
|
2614
|
+
bracketDepth--;
|
|
2615
|
+
current += char;
|
|
2616
|
+
continue;
|
|
2617
|
+
}
|
|
2618
|
+
if (char === " " && bracketDepth === 0) {
|
|
2619
|
+
if (current) {
|
|
2620
|
+
tokens.push(current);
|
|
2621
|
+
current = "";
|
|
2622
|
+
}
|
|
2623
|
+
continue;
|
|
2075
2624
|
}
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2625
|
+
current += char;
|
|
2626
|
+
}
|
|
2627
|
+
if (current) {
|
|
2628
|
+
tokens.push(current);
|
|
2629
|
+
}
|
|
2630
|
+
return tokens;
|
|
2631
|
+
}
|
|
2632
|
+
function parseExplicitValue(valueStr, referenceSet) {
|
|
2633
|
+
if (valueStr.startsWith("#") || valueStr.startsWith(".") || valueStr.startsWith("[") || valueStr.startsWith("@") || valueStr.startsWith("*")) {
|
|
2634
|
+
return createSelector(valueStr, inferSelectorKind(valueStr));
|
|
2635
|
+
}
|
|
2636
|
+
if (valueStr.startsWith('"') || valueStr.startsWith("'")) {
|
|
2637
|
+
const inner = valueStr.slice(1, -1);
|
|
2638
|
+
return createLiteral(inner, "string");
|
|
2639
|
+
}
|
|
2640
|
+
if (valueStr === "true") return createLiteral(true, "boolean");
|
|
2641
|
+
if (valueStr === "false") return createLiteral(false, "boolean");
|
|
2642
|
+
const lowerRef = valueStr.toLowerCase();
|
|
2643
|
+
if (isValidReference2(lowerRef, referenceSet)) {
|
|
2644
|
+
return createReference(lowerRef);
|
|
2645
|
+
}
|
|
2646
|
+
const numMatch = valueStr.match(/^(-?\d+(?:\.\d+)?)(ms|s|m|h)?$/);
|
|
2647
|
+
if (numMatch) {
|
|
2648
|
+
const num = parseFloat(numMatch[1]);
|
|
2649
|
+
const suffix = numMatch[2];
|
|
2650
|
+
if (suffix) {
|
|
2651
|
+
return createLiteral(valueStr, "duration");
|
|
2652
|
+
}
|
|
2653
|
+
return createLiteral(num, "number");
|
|
2654
|
+
}
|
|
2655
|
+
return createLiteral(valueStr);
|
|
2656
|
+
}
|
|
2657
|
+
function findMatchingBracket(str, start) {
|
|
2658
|
+
let depth = 0;
|
|
2659
|
+
for (let i = start; i < str.length; i++) {
|
|
2660
|
+
if (str[i] === "[") depth++;
|
|
2661
|
+
else if (str[i] === "]") {
|
|
2662
|
+
depth--;
|
|
2663
|
+
if (depth === 0) return i;
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
return str.length - 1;
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
// src/ir/explicit-renderer.ts
|
|
2670
|
+
function renderExplicit(node) {
|
|
2671
|
+
let result;
|
|
2672
|
+
if (node.kind === "compound") {
|
|
2673
|
+
const compoundNode = node;
|
|
2674
|
+
const renderedStatements = compoundNode.statements.map((stmt) => renderExplicit(stmt));
|
|
2675
|
+
result = renderedStatements.join(` ${compoundNode.chainType} `);
|
|
2676
|
+
} else {
|
|
2677
|
+
result = renderBracketCommand(node);
|
|
2678
|
+
}
|
|
2679
|
+
if (node.annotations && node.annotations.length > 0) {
|
|
2680
|
+
const annParts = node.annotations.map(
|
|
2681
|
+
(ann) => ann.value !== void 0 ? `@${ann.name}(${ann.value})` : `@${ann.name}`
|
|
2682
|
+
);
|
|
2683
|
+
return annParts.join(" ") + " " + result;
|
|
2684
|
+
}
|
|
2685
|
+
return result;
|
|
2686
|
+
}
|
|
2687
|
+
function renderDocument(envelope) {
|
|
2688
|
+
const lines = [];
|
|
2689
|
+
if (envelope.lseVersion && envelope.lseVersion !== "1.0") {
|
|
2690
|
+
lines.push(`#!lse ${envelope.lseVersion}`);
|
|
2691
|
+
}
|
|
2692
|
+
for (const node of envelope.nodes) {
|
|
2693
|
+
lines.push(renderExplicit(node));
|
|
2694
|
+
}
|
|
2695
|
+
return lines.join("\n");
|
|
2696
|
+
}
|
|
2697
|
+
function renderBracketCommand(node) {
|
|
2698
|
+
const parts = [node.action];
|
|
2699
|
+
for (const [role, value] of node.roles) {
|
|
2700
|
+
if (value.type === "flag") {
|
|
2701
|
+
parts.push(value.enabled ? `+${value.name}` : `~${value.name}`);
|
|
2702
|
+
} else {
|
|
2703
|
+
parts.push(`${role}:${valueToString(value)}`);
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
if (node.kind === "event-handler") {
|
|
2707
|
+
const eventNode = node;
|
|
2708
|
+
if (eventNode.body && eventNode.body.length > 0) {
|
|
2709
|
+
const bodyParts = eventNode.body.map((n) => renderExplicit(n));
|
|
2710
|
+
parts.push(`body:${bodyParts.join(" ")}`);
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
if (node.kind === "conditional") {
|
|
2714
|
+
const condNode = node;
|
|
2715
|
+
if (condNode.thenBranch && condNode.thenBranch.length > 0) {
|
|
2716
|
+
const thenParts = condNode.thenBranch.map((n) => renderExplicit(n));
|
|
2717
|
+
parts.push(`then:${thenParts.join(" ")}`);
|
|
2718
|
+
}
|
|
2719
|
+
if (condNode.elseBranch && condNode.elseBranch.length > 0) {
|
|
2720
|
+
const elseParts = condNode.elseBranch.map((n) => renderExplicit(n));
|
|
2721
|
+
parts.push(`else:${elseParts.join(" ")}`);
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
if (node.kind === "loop") {
|
|
2725
|
+
const loopNode = node;
|
|
2726
|
+
if (loopNode.loopVariant) {
|
|
2727
|
+
parts.push(`loop-variant:${loopNode.loopVariant}`);
|
|
2728
|
+
}
|
|
2729
|
+
if (loopNode.loopVariable) {
|
|
2730
|
+
parts.push(`variable:${loopNode.loopVariable}`);
|
|
2731
|
+
}
|
|
2732
|
+
if (loopNode.indexVariable) {
|
|
2733
|
+
parts.push(`index-variable:${loopNode.indexVariable}`);
|
|
2734
|
+
}
|
|
2735
|
+
if (loopNode.body && loopNode.body.length > 0) {
|
|
2736
|
+
const bodyParts = loopNode.body.map((n) => renderExplicit(n));
|
|
2737
|
+
parts.push(`loop-body:${bodyParts.join(" ")}`);
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
if (node.kind === "command") {
|
|
2741
|
+
const cmd = node;
|
|
2742
|
+
if (cmd.body && cmd.body.length > 0) {
|
|
2743
|
+
const bodyParts = cmd.body.map((n) => renderExplicit(n));
|
|
2744
|
+
parts.push(`body:${bodyParts.join(" ")}`);
|
|
2745
|
+
}
|
|
2746
|
+
if (cmd.catchBranch && cmd.catchBranch.length > 0) {
|
|
2747
|
+
const catchParts = cmd.catchBranch.map((n) => renderExplicit(n));
|
|
2748
|
+
parts.push(`catch:${catchParts.join(" ")}`);
|
|
2749
|
+
}
|
|
2750
|
+
if (cmd.finallyBranch && cmd.finallyBranch.length > 0) {
|
|
2751
|
+
const finallyParts = cmd.finallyBranch.map((n) => renderExplicit(n));
|
|
2752
|
+
parts.push(`finally:${finallyParts.join(" ")}`);
|
|
2753
|
+
}
|
|
2754
|
+
if (cmd.asyncVariant) {
|
|
2755
|
+
parts.push(`async-variant:${cmd.asyncVariant}`);
|
|
2756
|
+
}
|
|
2757
|
+
if (cmd.asyncBody && cmd.asyncBody.length > 0) {
|
|
2758
|
+
const asyncParts = cmd.asyncBody.map((n) => renderExplicit(n));
|
|
2759
|
+
parts.push(`async-body:${asyncParts.join(" ")}`);
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
return `[${parts.join(" ")}]`;
|
|
2763
|
+
}
|
|
2764
|
+
function valueToString(value) {
|
|
2765
|
+
switch (value.type) {
|
|
2766
|
+
case "literal":
|
|
2767
|
+
if (typeof value.value === "string") {
|
|
2768
|
+
if (value.dataType === "string" || /\s/.test(value.value)) {
|
|
2769
|
+
return `"${value.value}"`;
|
|
2770
|
+
}
|
|
2771
|
+
return value.value;
|
|
2772
|
+
}
|
|
2773
|
+
return String(value.value);
|
|
2774
|
+
case "selector":
|
|
2775
|
+
return value.value;
|
|
2776
|
+
case "reference":
|
|
2777
|
+
return value.value;
|
|
2778
|
+
case "property-path":
|
|
2779
|
+
return `${valueToString(value.object)}'s ${value.property}`;
|
|
2780
|
+
case "expression":
|
|
2781
|
+
return value.raw;
|
|
2782
|
+
case "flag":
|
|
2783
|
+
return value.name;
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
// src/ir/json-schema.ts
|
|
2788
|
+
var VALID_VALUE_TYPES = /* @__PURE__ */ new Set([
|
|
2789
|
+
"selector",
|
|
2790
|
+
"literal",
|
|
2791
|
+
"reference",
|
|
2792
|
+
"expression",
|
|
2793
|
+
"property-path",
|
|
2794
|
+
"flag"
|
|
2795
|
+
]);
|
|
2796
|
+
function validateSemanticJSON(input) {
|
|
2797
|
+
const diagnostics = [];
|
|
2798
|
+
if (!input.action || typeof input.action !== "string") {
|
|
2799
|
+
diagnostics.push({
|
|
2800
|
+
severity: "error",
|
|
2801
|
+
code: "INVALID_ACTION",
|
|
2802
|
+
message: 'Field "action" is required and must be a string.',
|
|
2803
|
+
suggestion: 'Provide a command name like "toggle", "add", "set", etc.'
|
|
2804
|
+
});
|
|
2805
|
+
return diagnostics;
|
|
2806
|
+
}
|
|
2807
|
+
if (input.roles && typeof input.roles === "object") {
|
|
2808
|
+
for (const [role, value] of Object.entries(input.roles)) {
|
|
2809
|
+
if (!value || typeof value !== "object") {
|
|
2810
|
+
diagnostics.push({
|
|
2811
|
+
severity: "error",
|
|
2812
|
+
code: "INVALID_ROLE_VALUE",
|
|
2813
|
+
message: `Role "${role}" must be an object with "type" and "value" fields.`,
|
|
2814
|
+
suggestion: 'Use: { "type": "selector", "value": ".active" }'
|
|
2815
|
+
});
|
|
2816
|
+
continue;
|
|
2817
|
+
}
|
|
2818
|
+
const v = value;
|
|
2819
|
+
if (!VALID_VALUE_TYPES.has(v.type)) {
|
|
2820
|
+
diagnostics.push({
|
|
2821
|
+
severity: "error",
|
|
2822
|
+
code: "INVALID_VALUE_TYPE",
|
|
2823
|
+
message: `Role "${role}" has invalid type "${v.type}".`,
|
|
2824
|
+
suggestion: "Valid types: selector, literal, reference, expression, property-path."
|
|
2825
|
+
});
|
|
2826
|
+
}
|
|
2827
|
+
if (v.value === void 0 || v.value === null) {
|
|
2828
|
+
diagnostics.push({
|
|
2829
|
+
severity: "error",
|
|
2830
|
+
code: "MISSING_VALUE",
|
|
2831
|
+
message: `Role "${role}" is missing the "value" field.`
|
|
2832
|
+
});
|
|
2096
2833
|
}
|
|
2097
2834
|
}
|
|
2098
|
-
throw new Error(`No pattern matched for input: ${input}`);
|
|
2099
2835
|
}
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
return {
|
|
2109
|
-
valid: false,
|
|
2110
|
-
errors: [error instanceof Error ? error.message : String(error)]
|
|
2111
|
-
};
|
|
2836
|
+
if (input.trigger) {
|
|
2837
|
+
if (!input.trigger.event || typeof input.trigger.event !== "string") {
|
|
2838
|
+
diagnostics.push({
|
|
2839
|
+
severity: "error",
|
|
2840
|
+
code: "INVALID_TRIGGER",
|
|
2841
|
+
message: 'Trigger "event" is required and must be a string.',
|
|
2842
|
+
suggestion: 'Use an event name like "click", "mouseover", "keydown".'
|
|
2843
|
+
});
|
|
2112
2844
|
}
|
|
2113
2845
|
}
|
|
2114
|
-
|
|
2115
|
-
|
|
2846
|
+
return diagnostics;
|
|
2847
|
+
}
|
|
2848
|
+
function jsonToSemanticNode(input) {
|
|
2849
|
+
const roles = /* @__PURE__ */ new Map();
|
|
2850
|
+
if (input.roles) {
|
|
2851
|
+
for (const [role, value] of Object.entries(input.roles)) {
|
|
2852
|
+
roles.set(role, convertJSONValue(value));
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
if (input.trigger) {
|
|
2856
|
+
roles.set("event", createLiteral(input.trigger.event, "string"));
|
|
2857
|
+
const bodyRoles = new Map(roles);
|
|
2858
|
+
bodyRoles.delete("event");
|
|
2859
|
+
const bodyNode = createCommandNode(input.action, bodyRoles);
|
|
2860
|
+
return createEventHandlerNode(
|
|
2861
|
+
"on",
|
|
2862
|
+
roles,
|
|
2863
|
+
[bodyNode],
|
|
2864
|
+
{ sourceLanguage: "json" },
|
|
2865
|
+
input.trigger.modifiers ?? {}
|
|
2866
|
+
);
|
|
2116
2867
|
}
|
|
2117
|
-
|
|
2868
|
+
return createCommandNode(input.action, roles, {
|
|
2869
|
+
sourceLanguage: "json"
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
function semanticNodeToJSON(node) {
|
|
2873
|
+
if (node.kind === "compound") {
|
|
2874
|
+
const compound = node;
|
|
2875
|
+
if (compound.statements.length > 0) {
|
|
2876
|
+
return semanticNodeToJSON(compound.statements[0]);
|
|
2877
|
+
}
|
|
2878
|
+
return { action: "compound", roles: {} };
|
|
2879
|
+
}
|
|
2880
|
+
if (node.kind === "event-handler") {
|
|
2881
|
+
const eventNode = node;
|
|
2882
|
+
const bodyAction = eventNode.body?.[0]?.action ?? node.action;
|
|
2883
|
+
const roles2 = {};
|
|
2884
|
+
if (eventNode.body?.[0]) {
|
|
2885
|
+
for (const [role, value] of eventNode.body[0].roles) {
|
|
2886
|
+
roles2[role] = semanticValueToJSON(value);
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
const eventValue = node.roles.get("event");
|
|
2890
|
+
const eventName = eventValue ? "value" in eventValue ? String(eventValue.value) : "raw" in eventValue ? eventValue.raw : "unknown" : "unknown";
|
|
2891
|
+
const result = {
|
|
2892
|
+
action: bodyAction === "on" ? eventNode.body?.[0]?.action ?? "on" : bodyAction,
|
|
2893
|
+
roles: roles2,
|
|
2894
|
+
trigger: {
|
|
2895
|
+
event: eventName
|
|
2896
|
+
}
|
|
2897
|
+
};
|
|
2898
|
+
if (eventNode.eventModifiers && Object.keys(eventNode.eventModifiers).length > 0) {
|
|
2899
|
+
result.trigger.modifiers = eventNode.eventModifiers;
|
|
2900
|
+
}
|
|
2901
|
+
return result;
|
|
2902
|
+
}
|
|
2903
|
+
const roles = {};
|
|
2904
|
+
for (const [role, value] of node.roles) {
|
|
2905
|
+
roles[role] = semanticValueToJSON(value);
|
|
2906
|
+
}
|
|
2907
|
+
return { action: node.action, roles };
|
|
2908
|
+
}
|
|
2909
|
+
function convertJSONValue(value) {
|
|
2910
|
+
switch (value.type) {
|
|
2911
|
+
case "selector":
|
|
2912
|
+
return createSelector(String(value.value), detectSelectorKind(String(value.value)));
|
|
2913
|
+
case "literal":
|
|
2914
|
+
return createLiteral(
|
|
2915
|
+
value.value,
|
|
2916
|
+
typeof value.value === "number" ? "number" : typeof value.value === "boolean" ? "boolean" : "string"
|
|
2917
|
+
);
|
|
2918
|
+
case "reference":
|
|
2919
|
+
return createReference(String(value.value));
|
|
2920
|
+
case "expression":
|
|
2921
|
+
return createExpression(String(value.value));
|
|
2922
|
+
case "property-path":
|
|
2923
|
+
return createExpression(String(value.value));
|
|
2924
|
+
case "flag":
|
|
2925
|
+
return createFlag(String(value.value), true);
|
|
2926
|
+
default:
|
|
2927
|
+
return createLiteral(String(value.value), "string");
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
function semanticValueToJSON(value) {
|
|
2931
|
+
switch (value.type) {
|
|
2932
|
+
case "selector":
|
|
2933
|
+
return { type: "selector", value: value.value };
|
|
2934
|
+
case "literal":
|
|
2935
|
+
return { type: "literal", value: value.value };
|
|
2936
|
+
case "reference":
|
|
2937
|
+
return { type: "reference", value: value.value };
|
|
2938
|
+
case "expression":
|
|
2939
|
+
return { type: "expression", value: value.raw };
|
|
2940
|
+
case "property-path":
|
|
2941
|
+
return {
|
|
2942
|
+
type: "property-path",
|
|
2943
|
+
value: `${semanticValueToJSON(value.object).value}.${value.property}`
|
|
2944
|
+
};
|
|
2945
|
+
case "flag":
|
|
2946
|
+
return { type: "flag", value: value.enabled };
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
function detectSelectorKind(selector) {
|
|
2950
|
+
if (selector.startsWith("#")) return "id";
|
|
2951
|
+
if (selector.startsWith(".")) return "class";
|
|
2952
|
+
if (selector.startsWith("[")) return "attribute";
|
|
2953
|
+
return "complex";
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
// src/ir/protocol-json.ts
|
|
2957
|
+
function toProtocolJSON(node) {
|
|
2958
|
+
const roles = rolesToProtocol(node.roles);
|
|
2959
|
+
let result;
|
|
2960
|
+
switch (node.kind) {
|
|
2961
|
+
case "command": {
|
|
2962
|
+
const cmd = node;
|
|
2963
|
+
result = { kind: "command", action: node.action, roles };
|
|
2964
|
+
if (cmd.body && cmd.body.length > 0) {
|
|
2965
|
+
result.body = cmd.body.map(toProtocolJSON);
|
|
2966
|
+
}
|
|
2967
|
+
if (cmd.catchBranch && cmd.catchBranch.length > 0) {
|
|
2968
|
+
result.catchBranch = cmd.catchBranch.map(toProtocolJSON);
|
|
2969
|
+
}
|
|
2970
|
+
if (cmd.finallyBranch && cmd.finallyBranch.length > 0) {
|
|
2971
|
+
result.finallyBranch = cmd.finallyBranch.map(toProtocolJSON);
|
|
2972
|
+
}
|
|
2973
|
+
if (cmd.asyncVariant) {
|
|
2974
|
+
result.asyncVariant = cmd.asyncVariant;
|
|
2975
|
+
}
|
|
2976
|
+
if (cmd.asyncBody && cmd.asyncBody.length > 0) {
|
|
2977
|
+
result.asyncBody = cmd.asyncBody.map(toProtocolJSON);
|
|
2978
|
+
}
|
|
2979
|
+
if (cmd.arms && cmd.arms.length > 0) {
|
|
2980
|
+
result.arms = cmd.arms.map((arm) => ({
|
|
2981
|
+
pattern: valueToProtocol(arm.pattern),
|
|
2982
|
+
body: arm.body.map(toProtocolJSON)
|
|
2983
|
+
}));
|
|
2984
|
+
}
|
|
2985
|
+
if (cmd.defaultArm && cmd.defaultArm.length > 0) {
|
|
2986
|
+
result.defaultArm = cmd.defaultArm.map(toProtocolJSON);
|
|
2987
|
+
}
|
|
2988
|
+
break;
|
|
2989
|
+
}
|
|
2990
|
+
case "event-handler": {
|
|
2991
|
+
const eh = node;
|
|
2992
|
+
result = {
|
|
2993
|
+
kind: "event-handler",
|
|
2994
|
+
action: node.action,
|
|
2995
|
+
roles,
|
|
2996
|
+
body: eh.body.map(toProtocolJSON)
|
|
2997
|
+
};
|
|
2998
|
+
break;
|
|
2999
|
+
}
|
|
3000
|
+
case "compound": {
|
|
3001
|
+
const c = node;
|
|
3002
|
+
result = {
|
|
3003
|
+
kind: "compound",
|
|
3004
|
+
action: "compound",
|
|
3005
|
+
statements: c.statements.map(toProtocolJSON),
|
|
3006
|
+
chainType: c.chainType
|
|
3007
|
+
};
|
|
3008
|
+
break;
|
|
3009
|
+
}
|
|
3010
|
+
case "conditional": {
|
|
3011
|
+
const cond = node;
|
|
3012
|
+
result = { kind: "command", action: node.action, roles };
|
|
3013
|
+
result.thenBranch = cond.thenBranch.map(toProtocolJSON);
|
|
3014
|
+
if (cond.elseBranch && cond.elseBranch.length > 0) {
|
|
3015
|
+
result.elseBranch = cond.elseBranch.map(toProtocolJSON);
|
|
3016
|
+
}
|
|
3017
|
+
break;
|
|
3018
|
+
}
|
|
3019
|
+
case "loop": {
|
|
3020
|
+
const loop = node;
|
|
3021
|
+
result = { kind: "command", action: node.action, roles };
|
|
3022
|
+
result.loopVariant = loop.loopVariant;
|
|
3023
|
+
result.loopBody = loop.body.map(toProtocolJSON);
|
|
3024
|
+
if (loop.loopVariable) result.loopVariable = loop.loopVariable;
|
|
3025
|
+
if (loop.indexVariable) result.indexVariable = loop.indexVariable;
|
|
3026
|
+
break;
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
if (node.annotations && node.annotations.length > 0) {
|
|
3030
|
+
result.annotations = node.annotations.map(
|
|
3031
|
+
(a) => a.value !== void 0 ? { name: a.name, value: a.value } : { name: a.name }
|
|
3032
|
+
);
|
|
3033
|
+
}
|
|
3034
|
+
if (node.diagnostics && node.diagnostics.length > 0) {
|
|
3035
|
+
result.diagnostics = node.diagnostics.map((d) => {
|
|
3036
|
+
const json = {
|
|
3037
|
+
level: d.severity,
|
|
3038
|
+
message: d.message
|
|
3039
|
+
};
|
|
3040
|
+
if (d.code) json.code = d.code;
|
|
3041
|
+
if (d.source) json.source = d.source;
|
|
3042
|
+
if (d.suggestions && d.suggestions.length > 0) json.suggestions = [...d.suggestions];
|
|
3043
|
+
return json;
|
|
3044
|
+
});
|
|
3045
|
+
}
|
|
3046
|
+
return result;
|
|
3047
|
+
}
|
|
3048
|
+
function rolesToProtocol(roles) {
|
|
3049
|
+
const result = {};
|
|
3050
|
+
for (const [role, value] of roles) {
|
|
3051
|
+
result[role] = valueToProtocol(value);
|
|
3052
|
+
}
|
|
3053
|
+
return result;
|
|
3054
|
+
}
|
|
3055
|
+
function valueToProtocol(value) {
|
|
3056
|
+
switch (value.type) {
|
|
3057
|
+
case "selector": {
|
|
3058
|
+
const PROTOCOL_SELECTOR_KINDS = /* @__PURE__ */ new Set([
|
|
3059
|
+
"id",
|
|
3060
|
+
"class",
|
|
3061
|
+
"attribute",
|
|
3062
|
+
"element",
|
|
3063
|
+
"complex"
|
|
3064
|
+
]);
|
|
3065
|
+
const sk = "selectorKind" in value ? value.selectorKind : void 0;
|
|
3066
|
+
const sv = { type: "selector", value: value.value };
|
|
3067
|
+
if (sk && PROTOCOL_SELECTOR_KINDS.has(sk)) {
|
|
3068
|
+
sv.selectorKind = sk;
|
|
3069
|
+
}
|
|
3070
|
+
return sv;
|
|
3071
|
+
}
|
|
3072
|
+
case "literal":
|
|
3073
|
+
return value.dataType ? { type: "literal", value: value.value, dataType: value.dataType } : { type: "literal", value: value.value };
|
|
3074
|
+
case "reference":
|
|
3075
|
+
return { type: "reference", value: value.value };
|
|
3076
|
+
case "expression":
|
|
3077
|
+
return { type: "expression", raw: value.raw };
|
|
3078
|
+
case "property-path":
|
|
3079
|
+
return { type: "expression", raw: extractValue(value) };
|
|
3080
|
+
case "flag":
|
|
3081
|
+
return { type: "flag", name: value.name, enabled: value.enabled };
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
function fromProtocolJSON(json) {
|
|
3085
|
+
const roles = new Map(
|
|
3086
|
+
Object.entries(json.roles ?? {}).map(([role, val]) => [role, valueFromProtocol(val)])
|
|
3087
|
+
);
|
|
3088
|
+
const annotations = json.annotations?.map(
|
|
3089
|
+
(a) => a.value !== void 0 ? { name: a.name, value: a.value } : { name: a.name }
|
|
3090
|
+
);
|
|
3091
|
+
const diagnostics = json.diagnostics?.map((d) => ({
|
|
3092
|
+
severity: d.level,
|
|
3093
|
+
message: d.message,
|
|
3094
|
+
...d.code ? { code: d.code } : {},
|
|
3095
|
+
...d.source ? { source: d.source } : {},
|
|
3096
|
+
...d.suggestions && d.suggestions.length > 0 ? { suggestions: d.suggestions } : {}
|
|
3097
|
+
}));
|
|
3098
|
+
if (json.trigger) {
|
|
3099
|
+
const eventName = json.trigger.event;
|
|
3100
|
+
roles.set("event", createLiteral(eventName, "string"));
|
|
3101
|
+
const bodyRoles = new Map(roles);
|
|
3102
|
+
bodyRoles.delete("event");
|
|
3103
|
+
const bodyNode = createCommandNode(json.action, bodyRoles);
|
|
3104
|
+
const node2 = createEventHandlerNode(
|
|
3105
|
+
"on",
|
|
3106
|
+
roles,
|
|
3107
|
+
[bodyNode],
|
|
3108
|
+
{ sourceLanguage: "json" },
|
|
3109
|
+
json.trigger.modifiers ?? {}
|
|
3110
|
+
);
|
|
3111
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
3112
|
+
}
|
|
3113
|
+
const kind = json.kind ?? "command";
|
|
3114
|
+
if (kind === "event-handler") {
|
|
3115
|
+
const body = (json.body ?? []).map(fromProtocolJSON);
|
|
3116
|
+
const node2 = createEventHandlerNode(json.action, roles, body);
|
|
3117
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
3118
|
+
}
|
|
3119
|
+
if (kind === "compound") {
|
|
3120
|
+
const statements = (json.statements ?? []).map(fromProtocolJSON);
|
|
3121
|
+
const node2 = createCompoundNode(
|
|
3122
|
+
statements,
|
|
3123
|
+
json.chainType ?? "sequential"
|
|
3124
|
+
);
|
|
3125
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
3126
|
+
}
|
|
3127
|
+
if (json.thenBranch && json.thenBranch.length > 0) {
|
|
3128
|
+
const thenBranch = json.thenBranch.map(fromProtocolJSON);
|
|
3129
|
+
const elseBranch = json.elseBranch ? json.elseBranch.map(fromProtocolJSON) : void 0;
|
|
3130
|
+
const node2 = createConditionalNode(json.action, roles, thenBranch, elseBranch);
|
|
3131
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
3132
|
+
}
|
|
3133
|
+
if (json.loopVariant) {
|
|
3134
|
+
const loopBody = (json.loopBody ?? []).map(fromProtocolJSON);
|
|
3135
|
+
const node2 = createLoopNode(
|
|
3136
|
+
json.action,
|
|
3137
|
+
roles,
|
|
3138
|
+
json.loopVariant,
|
|
3139
|
+
loopBody,
|
|
3140
|
+
json.loopVariable,
|
|
3141
|
+
json.indexVariable
|
|
3142
|
+
);
|
|
3143
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
3144
|
+
}
|
|
3145
|
+
if (json.catchBranch || json.finallyBranch) {
|
|
3146
|
+
const body = (json.body ?? []).map(fromProtocolJSON);
|
|
3147
|
+
const catchBranch = json.catchBranch ? json.catchBranch.map(fromProtocolJSON) : void 0;
|
|
3148
|
+
const finallyBranch = json.finallyBranch ? json.finallyBranch.map(fromProtocolJSON) : void 0;
|
|
3149
|
+
const node2 = createTryNode(body, catchBranch, finallyBranch);
|
|
3150
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
3151
|
+
}
|
|
3152
|
+
if (json.asyncVariant) {
|
|
3153
|
+
const asyncBody = (json.asyncBody ?? []).map(fromProtocolJSON);
|
|
3154
|
+
const node2 = createAsyncNode(json.asyncVariant, asyncBody);
|
|
3155
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
3156
|
+
}
|
|
3157
|
+
if (json.arms && json.arms.length > 0) {
|
|
3158
|
+
const arms = json.arms.map((arm) => ({
|
|
3159
|
+
pattern: valueFromProtocol(arm.pattern),
|
|
3160
|
+
body: arm.body.map(fromProtocolJSON)
|
|
3161
|
+
}));
|
|
3162
|
+
const defaultArm = json.defaultArm ? json.defaultArm.map(fromProtocolJSON) : void 0;
|
|
3163
|
+
const node2 = createMatchNode(roles, arms, defaultArm);
|
|
3164
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
3165
|
+
}
|
|
3166
|
+
if (json.body && json.body.length > 0 && kind === "command") {
|
|
3167
|
+
const body = json.body.map(fromProtocolJSON);
|
|
3168
|
+
const node2 = createTryNode(body);
|
|
3169
|
+
return applyV12Metadata(node2, annotations, diagnostics);
|
|
3170
|
+
}
|
|
3171
|
+
const node = createCommandNode(json.action, roles);
|
|
3172
|
+
return applyV12Metadata(node, annotations, diagnostics);
|
|
3173
|
+
}
|
|
3174
|
+
function applyV12Metadata(node, annotations, diagnostics) {
|
|
3175
|
+
if ((!annotations || annotations.length === 0) && (!diagnostics || diagnostics.length === 0)) {
|
|
3176
|
+
return node;
|
|
3177
|
+
}
|
|
3178
|
+
return {
|
|
3179
|
+
...node,
|
|
3180
|
+
...annotations && annotations.length > 0 ? { annotations } : {},
|
|
3181
|
+
...diagnostics && diagnostics.length > 0 ? { diagnostics } : {}
|
|
3182
|
+
};
|
|
3183
|
+
}
|
|
3184
|
+
function valueFromProtocol(value) {
|
|
3185
|
+
switch (value.type) {
|
|
3186
|
+
case "selector":
|
|
3187
|
+
return createSelector(String(value.value ?? ""), value.selectorKind);
|
|
3188
|
+
case "literal":
|
|
3189
|
+
return createLiteral(
|
|
3190
|
+
value.value !== void 0 ? value.value : "",
|
|
3191
|
+
value.dataType
|
|
3192
|
+
);
|
|
3193
|
+
case "reference":
|
|
3194
|
+
return createReference(String(value.value ?? ""));
|
|
3195
|
+
case "expression":
|
|
3196
|
+
return createExpression(value.raw ?? String(value.value ?? ""));
|
|
3197
|
+
case "flag":
|
|
3198
|
+
return createFlag(
|
|
3199
|
+
value.name ?? String(value.value ?? ""),
|
|
3200
|
+
value.enabled ?? (typeof value.value === "boolean" ? value.value : true)
|
|
3201
|
+
);
|
|
3202
|
+
case "property-path":
|
|
3203
|
+
return createExpression(String(value.value ?? value.raw ?? ""));
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
function toEnvelopeJSON(envelope) {
|
|
3207
|
+
const result = {
|
|
3208
|
+
lseVersion: envelope.lseVersion,
|
|
3209
|
+
nodes: envelope.nodes.map(toProtocolJSON)
|
|
3210
|
+
};
|
|
3211
|
+
if (envelope.features && envelope.features.length > 0) {
|
|
3212
|
+
result.features = [...envelope.features];
|
|
3213
|
+
}
|
|
3214
|
+
return result;
|
|
3215
|
+
}
|
|
3216
|
+
function fromEnvelopeJSON(json) {
|
|
3217
|
+
return {
|
|
3218
|
+
lseVersion: json.lseVersion,
|
|
3219
|
+
...json.features && json.features.length > 0 ? { features: json.features } : {},
|
|
3220
|
+
nodes: json.nodes.map(fromProtocolJSON)
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3223
|
+
function isEnvelope(json) {
|
|
3224
|
+
return typeof json === "object" && json !== null && "lseVersion" in json && "nodes" in json && Array.isArray(json.nodes);
|
|
3225
|
+
}
|
|
3226
|
+
var VALID_KINDS = /* @__PURE__ */ new Set(["command", "event-handler", "compound"]);
|
|
3227
|
+
var VALID_VALUE_TYPES2 = /* @__PURE__ */ new Set([
|
|
3228
|
+
"selector",
|
|
3229
|
+
"literal",
|
|
3230
|
+
"reference",
|
|
3231
|
+
"expression",
|
|
3232
|
+
"property-path",
|
|
3233
|
+
"flag"
|
|
3234
|
+
]);
|
|
3235
|
+
var VALID_CHAIN_TYPES = /* @__PURE__ */ new Set(["then", "and", "async", "sequential", "pipe"]);
|
|
3236
|
+
var VALID_ASYNC_VARIANTS = /* @__PURE__ */ new Set(["all", "race"]);
|
|
3237
|
+
var VALID_DIAGNOSTIC_LEVELS = /* @__PURE__ */ new Set(["error", "warning", "info"]);
|
|
3238
|
+
function validateProtocolJSON(json) {
|
|
3239
|
+
const diagnostics = [];
|
|
3240
|
+
if (typeof json !== "object" || json === null) {
|
|
3241
|
+
return [{ severity: "error", code: "INVALID_ROOT", message: "Root must be a JSON object" }];
|
|
3242
|
+
}
|
|
3243
|
+
const node = json;
|
|
3244
|
+
const kind = "kind" in node ? String(node.kind) : "command";
|
|
3245
|
+
if ("kind" in node && !VALID_KINDS.has(kind)) {
|
|
3246
|
+
diagnostics.push({
|
|
3247
|
+
severity: "error",
|
|
3248
|
+
code: "INVALID_KIND",
|
|
3249
|
+
message: `Invalid kind "${node.kind}". Must be one of: command, event-handler, compound`
|
|
3250
|
+
});
|
|
3251
|
+
}
|
|
3252
|
+
if (!("action" in node)) {
|
|
3253
|
+
diagnostics.push({
|
|
3254
|
+
severity: "error",
|
|
3255
|
+
code: "MISSING_ACTION",
|
|
3256
|
+
message: "Missing required field: action"
|
|
3257
|
+
});
|
|
3258
|
+
} else if (typeof node.action !== "string" || node.action.length === 0) {
|
|
3259
|
+
diagnostics.push({
|
|
3260
|
+
severity: "error",
|
|
3261
|
+
code: "INVALID_ACTION",
|
|
3262
|
+
message: "action must be a non-empty string"
|
|
3263
|
+
});
|
|
3264
|
+
}
|
|
3265
|
+
if (!("roles" in node) && kind !== "compound") {
|
|
3266
|
+
diagnostics.push({
|
|
3267
|
+
severity: "error",
|
|
3268
|
+
code: "MISSING_ROLES",
|
|
3269
|
+
message: "Missing required field: roles"
|
|
3270
|
+
});
|
|
3271
|
+
} else if ("roles" in node && (typeof node.roles !== "object" || node.roles === null || Array.isArray(node.roles))) {
|
|
3272
|
+
diagnostics.push({
|
|
3273
|
+
severity: "error",
|
|
3274
|
+
code: "INVALID_ROLES",
|
|
3275
|
+
message: "roles must be a plain object"
|
|
3276
|
+
});
|
|
3277
|
+
} else if ("roles" in node && node.roles) {
|
|
3278
|
+
for (const [role, value] of Object.entries(node.roles)) {
|
|
3279
|
+
if (typeof value !== "object" || value === null) {
|
|
3280
|
+
diagnostics.push({
|
|
3281
|
+
severity: "error",
|
|
3282
|
+
code: "INVALID_ROLE_VALUE",
|
|
3283
|
+
message: `Role "${role}" value must be an object`
|
|
3284
|
+
});
|
|
3285
|
+
continue;
|
|
3286
|
+
}
|
|
3287
|
+
const v = value;
|
|
3288
|
+
if (!("type" in v) || !VALID_VALUE_TYPES2.has(String(v.type))) {
|
|
3289
|
+
diagnostics.push({
|
|
3290
|
+
severity: "error",
|
|
3291
|
+
code: "INVALID_VALUE_TYPE",
|
|
3292
|
+
message: `Role "${role}" has invalid type "${v.type}"`
|
|
3293
|
+
});
|
|
3294
|
+
}
|
|
3295
|
+
if (v.type === "flag") {
|
|
3296
|
+
const hasName = typeof v.name === "string";
|
|
3297
|
+
const hasEnabled = typeof v.enabled === "boolean";
|
|
3298
|
+
const hasValue = "value" in v;
|
|
3299
|
+
if (!hasName && !hasValue) {
|
|
3300
|
+
diagnostics.push({
|
|
3301
|
+
severity: "error",
|
|
3302
|
+
code: "MISSING_FLAG_NAME",
|
|
3303
|
+
message: `Flag role "${role}" missing required field: name (or value)`
|
|
3304
|
+
});
|
|
3305
|
+
}
|
|
3306
|
+
if (!hasEnabled && !hasValue) {
|
|
3307
|
+
diagnostics.push({
|
|
3308
|
+
severity: "error",
|
|
3309
|
+
code: "MISSING_FLAG_ENABLED",
|
|
3310
|
+
message: `Flag role "${role}" missing required field: enabled (or value)`
|
|
3311
|
+
});
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
if (v.type === "expression" && typeof v.raw !== "string" && !("value" in v)) {
|
|
3315
|
+
diagnostics.push({
|
|
3316
|
+
severity: "error",
|
|
3317
|
+
code: "MISSING_EXPRESSION_RAW",
|
|
3318
|
+
message: `Expression role "${role}" missing required field: raw (or value)`
|
|
3319
|
+
});
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
if ("trigger" in node && node.trigger) {
|
|
3324
|
+
const trigger = node.trigger;
|
|
3325
|
+
if (typeof trigger !== "object" || trigger === null) {
|
|
3326
|
+
diagnostics.push({
|
|
3327
|
+
severity: "error",
|
|
3328
|
+
code: "INVALID_TRIGGER",
|
|
3329
|
+
message: "trigger must be an object"
|
|
3330
|
+
});
|
|
3331
|
+
} else if (!trigger.event || typeof trigger.event !== "string") {
|
|
3332
|
+
diagnostics.push({
|
|
3333
|
+
severity: "error",
|
|
3334
|
+
code: "INVALID_TRIGGER",
|
|
3335
|
+
message: "trigger.event is required and must be a non-empty string"
|
|
3336
|
+
});
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
if (kind === "event-handler" && !("trigger" in node)) {
|
|
3340
|
+
if (!("body" in node) || !Array.isArray(node.body)) {
|
|
3341
|
+
diagnostics.push({
|
|
3342
|
+
severity: "error",
|
|
3343
|
+
code: "MISSING_BODY",
|
|
3344
|
+
message: "event-handler node requires a body array"
|
|
3345
|
+
});
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
if (kind === "compound") {
|
|
3349
|
+
if (!("statements" in node) || !Array.isArray(node.statements)) {
|
|
3350
|
+
diagnostics.push({
|
|
3351
|
+
severity: "error",
|
|
3352
|
+
code: "MISSING_STATEMENTS",
|
|
3353
|
+
message: "compound node requires a statements array"
|
|
3354
|
+
});
|
|
3355
|
+
}
|
|
3356
|
+
if ("chainType" in node && !VALID_CHAIN_TYPES.has(String(node.chainType))) {
|
|
3357
|
+
diagnostics.push({
|
|
3358
|
+
severity: "error",
|
|
3359
|
+
code: "INVALID_CHAIN_TYPE",
|
|
3360
|
+
message: `Invalid chainType "${node.chainType}". Must be one of: then, and, async, sequential, pipe`
|
|
3361
|
+
});
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
if ("diagnostics" in node) {
|
|
3365
|
+
if (!Array.isArray(node.diagnostics)) {
|
|
3366
|
+
diagnostics.push({
|
|
3367
|
+
severity: "error",
|
|
3368
|
+
code: "INVALID_DIAGNOSTICS",
|
|
3369
|
+
message: "diagnostics must be an array"
|
|
3370
|
+
});
|
|
3371
|
+
} else {
|
|
3372
|
+
for (let i = 0; i < node.diagnostics.length; i++) {
|
|
3373
|
+
const d = node.diagnostics[i];
|
|
3374
|
+
if (!d || typeof d !== "object") {
|
|
3375
|
+
diagnostics.push({
|
|
3376
|
+
severity: "error",
|
|
3377
|
+
code: "INVALID_DIAGNOSTIC_ENTRY",
|
|
3378
|
+
message: `diagnostics[${i}] must be an object`
|
|
3379
|
+
});
|
|
3380
|
+
continue;
|
|
3381
|
+
}
|
|
3382
|
+
if (!VALID_DIAGNOSTIC_LEVELS.has(String(d.level))) {
|
|
3383
|
+
diagnostics.push({
|
|
3384
|
+
severity: "error",
|
|
3385
|
+
code: "INVALID_DIAGNOSTIC_LEVEL",
|
|
3386
|
+
message: `diagnostics[${i}].level must be "error", "warning", or "info"`
|
|
3387
|
+
});
|
|
3388
|
+
}
|
|
3389
|
+
if (typeof d.message !== "string") {
|
|
3390
|
+
diagnostics.push({
|
|
3391
|
+
severity: "error",
|
|
3392
|
+
code: "MISSING_DIAGNOSTIC_MESSAGE",
|
|
3393
|
+
message: `diagnostics[${i}] missing required field: message`
|
|
3394
|
+
});
|
|
3395
|
+
}
|
|
3396
|
+
if ("code" in d && typeof d.code !== "string") {
|
|
3397
|
+
diagnostics.push({
|
|
3398
|
+
severity: "warning",
|
|
3399
|
+
code: "INVALID_DIAGNOSTIC_CODE",
|
|
3400
|
+
message: `diagnostics[${i}].code must be a string if present`
|
|
3401
|
+
});
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
if ("annotations" in node) {
|
|
3407
|
+
if (!Array.isArray(node.annotations)) {
|
|
3408
|
+
diagnostics.push({
|
|
3409
|
+
severity: "error",
|
|
3410
|
+
code: "INVALID_ANNOTATIONS",
|
|
3411
|
+
message: "annotations must be an array"
|
|
3412
|
+
});
|
|
3413
|
+
} else {
|
|
3414
|
+
for (let i = 0; i < node.annotations.length; i++) {
|
|
3415
|
+
const a = node.annotations[i];
|
|
3416
|
+
if (!a || typeof a !== "object") {
|
|
3417
|
+
diagnostics.push({
|
|
3418
|
+
severity: "error",
|
|
3419
|
+
code: "INVALID_ANNOTATION_ENTRY",
|
|
3420
|
+
message: `annotations[${i}] must be an object`
|
|
3421
|
+
});
|
|
3422
|
+
continue;
|
|
3423
|
+
}
|
|
3424
|
+
if (typeof a.name !== "string" || a.name.length === 0) {
|
|
3425
|
+
diagnostics.push({
|
|
3426
|
+
severity: "error",
|
|
3427
|
+
code: "MISSING_ANNOTATION_NAME",
|
|
3428
|
+
message: `annotations[${i}] missing required field: name`
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
if ("asyncVariant" in node && !VALID_ASYNC_VARIANTS.has(String(node.asyncVariant))) {
|
|
3435
|
+
diagnostics.push({
|
|
3436
|
+
severity: "error",
|
|
3437
|
+
code: "INVALID_ASYNC_VARIANT",
|
|
3438
|
+
message: `Invalid asyncVariant "${node.asyncVariant}". Must be "all" or "race"`
|
|
3439
|
+
});
|
|
3440
|
+
}
|
|
3441
|
+
if ("arms" in node) {
|
|
3442
|
+
if (!Array.isArray(node.arms)) {
|
|
3443
|
+
diagnostics.push({
|
|
3444
|
+
severity: "error",
|
|
3445
|
+
code: "INVALID_ARMS",
|
|
3446
|
+
message: "arms must be an array"
|
|
3447
|
+
});
|
|
3448
|
+
} else {
|
|
3449
|
+
for (let i = 0; i < node.arms.length; i++) {
|
|
3450
|
+
const arm = node.arms[i];
|
|
3451
|
+
if (!arm || typeof arm !== "object") {
|
|
3452
|
+
diagnostics.push({
|
|
3453
|
+
severity: "error",
|
|
3454
|
+
code: "INVALID_ARM_ENTRY",
|
|
3455
|
+
message: `arms[${i}] must be an object`
|
|
3456
|
+
});
|
|
3457
|
+
continue;
|
|
3458
|
+
}
|
|
3459
|
+
if (!arm.pattern || typeof arm.pattern !== "object") {
|
|
3460
|
+
diagnostics.push({
|
|
3461
|
+
severity: "error",
|
|
3462
|
+
code: "MISSING_ARM_PATTERN",
|
|
3463
|
+
message: `arms[${i}] missing required field: pattern`
|
|
3464
|
+
});
|
|
3465
|
+
}
|
|
3466
|
+
if (!Array.isArray(arm.body)) {
|
|
3467
|
+
diagnostics.push({
|
|
3468
|
+
severity: "error",
|
|
3469
|
+
code: "MISSING_ARM_BODY",
|
|
3470
|
+
message: `arms[${i}] missing required field: body (array)`
|
|
3471
|
+
});
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
return diagnostics;
|
|
3477
|
+
}
|
|
3478
|
+
|
|
3479
|
+
// src/ir/from-interchange.ts
|
|
3480
|
+
function fromInterchangeNode(node) {
|
|
3481
|
+
if (!node || typeof node !== "object") {
|
|
3482
|
+
return createCommandNode("unknown", {});
|
|
3483
|
+
}
|
|
3484
|
+
switch (node.type) {
|
|
3485
|
+
case "event":
|
|
3486
|
+
return convertEvent(node);
|
|
3487
|
+
case "command":
|
|
3488
|
+
return convertCommand(node);
|
|
3489
|
+
case "if":
|
|
3490
|
+
return convertIf(node);
|
|
3491
|
+
case "repeat":
|
|
3492
|
+
return convertRepeat(node);
|
|
3493
|
+
case "foreach":
|
|
3494
|
+
return convertForEach(node);
|
|
3495
|
+
case "while":
|
|
3496
|
+
return convertWhile(node);
|
|
3497
|
+
// Expression nodes — wrap as `get` command with patient role
|
|
3498
|
+
default:
|
|
3499
|
+
return createCommandNode("get", { patient: convertValue(node) });
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
function convertEvent(node) {
|
|
3503
|
+
const event = String(node.event ?? "click");
|
|
3504
|
+
const bodyNodes = Array.isArray(node.body) ? node.body : [];
|
|
3505
|
+
const body = bodyNodes.map(fromInterchangeNode);
|
|
3506
|
+
const roles = {
|
|
3507
|
+
event: createLiteral(event)
|
|
3508
|
+
};
|
|
3509
|
+
if (node.target && typeof node.target === "object") {
|
|
3510
|
+
roles.source = convertValue(node.target);
|
|
3511
|
+
}
|
|
3512
|
+
const modifiers = node.modifiers;
|
|
3513
|
+
let eventModifiers;
|
|
3514
|
+
if (modifiers) {
|
|
3515
|
+
eventModifiers = bridgeEventModifiers(modifiers);
|
|
3516
|
+
if (modifiers.prevent) roles.prevent = createLiteral(true);
|
|
3517
|
+
if (modifiers.stop) roles.stop = createLiteral(true);
|
|
3518
|
+
if (modifiers.passive) roles.passive = createLiteral(true);
|
|
3519
|
+
if (modifiers.capture) roles.capture = createLiteral(true);
|
|
3520
|
+
}
|
|
3521
|
+
return createEventHandlerNode("on", roles, body, void 0, eventModifiers);
|
|
3522
|
+
}
|
|
3523
|
+
function convertCommand(node) {
|
|
3524
|
+
const name = String(node.name ?? "unknown");
|
|
3525
|
+
const args = Array.isArray(node.args) ? node.args : [];
|
|
3526
|
+
const target = node.target && typeof node.target === "object" ? node.target : void 0;
|
|
3527
|
+
const existingRoles = node.roles;
|
|
3528
|
+
const modifiers = node.modifiers;
|
|
3529
|
+
let roles;
|
|
3530
|
+
if (existingRoles && typeof existingRoles === "object" && Object.keys(existingRoles).length > 0) {
|
|
3531
|
+
roles = {};
|
|
3532
|
+
for (const [role, value] of Object.entries(existingRoles)) {
|
|
3533
|
+
if (value && typeof value === "object" && "type" in value) {
|
|
3534
|
+
roles[role] = convertValue(value);
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
} else {
|
|
3538
|
+
roles = inferRoles(name, args, modifiers, target);
|
|
3539
|
+
}
|
|
3540
|
+
return createCommandNode(name, roles);
|
|
3541
|
+
}
|
|
3542
|
+
function convertIf(node) {
|
|
3543
|
+
const condition = node.condition && typeof node.condition === "object" ? convertValue(node.condition) : createExpression("true");
|
|
3544
|
+
const thenBody = Array.isArray(node.thenBranch) ? node.thenBranch : [];
|
|
3545
|
+
const thenBranch = thenBody.map(fromInterchangeNode);
|
|
3546
|
+
let elseBranch;
|
|
3547
|
+
const elseIfBranches = node.elseIfBranches;
|
|
3548
|
+
if (elseIfBranches && elseIfBranches.length > 0) {
|
|
3549
|
+
elseBranch = [chainElseIfs(elseIfBranches, node.elseBranch)];
|
|
3550
|
+
} else if (Array.isArray(node.elseBranch) && node.elseBranch.length > 0) {
|
|
3551
|
+
elseBranch = node.elseBranch.map(fromInterchangeNode);
|
|
3552
|
+
}
|
|
3553
|
+
const roles = { condition };
|
|
3554
|
+
return createConditionalNode("if", roles, thenBranch, elseBranch);
|
|
3555
|
+
}
|
|
3556
|
+
function chainElseIfs(branches, finalElse) {
|
|
3557
|
+
const [first, ...rest] = branches;
|
|
3558
|
+
const condition = convertValue(first.condition);
|
|
3559
|
+
const thenBranch = first.body.map(fromInterchangeNode);
|
|
3560
|
+
let elseBranch;
|
|
3561
|
+
if (rest.length > 0) {
|
|
3562
|
+
elseBranch = [chainElseIfs(rest, finalElse)];
|
|
3563
|
+
} else if (finalElse && finalElse.length > 0) {
|
|
3564
|
+
elseBranch = finalElse.map(fromInterchangeNode);
|
|
3565
|
+
}
|
|
3566
|
+
return createConditionalNode("if", { condition }, thenBranch, elseBranch);
|
|
3567
|
+
}
|
|
3568
|
+
function convertRepeat(node) {
|
|
3569
|
+
const bodyNodes = Array.isArray(node.body) ? node.body : [];
|
|
3570
|
+
const body = bodyNodes.map(fromInterchangeNode);
|
|
3571
|
+
const roles = {};
|
|
3572
|
+
let variant;
|
|
3573
|
+
if (node.count !== void 0 && node.count !== null) {
|
|
3574
|
+
variant = "times";
|
|
3575
|
+
if (typeof node.count === "number") {
|
|
3576
|
+
roles.quantity = createLiteral(node.count, "number");
|
|
3577
|
+
} else if (typeof node.count === "object") {
|
|
3578
|
+
roles.quantity = convertValue(node.count);
|
|
3579
|
+
}
|
|
3580
|
+
} else if (node.whileCondition && typeof node.whileCondition === "object") {
|
|
3581
|
+
variant = "while";
|
|
3582
|
+
roles.condition = convertValue(node.whileCondition);
|
|
3583
|
+
} else if (typeof node.untilEvent === "string") {
|
|
3584
|
+
variant = "until";
|
|
3585
|
+
roles.condition = createLiteral(node.untilEvent);
|
|
3586
|
+
} else {
|
|
3587
|
+
variant = "forever";
|
|
3588
|
+
}
|
|
3589
|
+
return createLoopNode("repeat", roles, variant, body);
|
|
3590
|
+
}
|
|
3591
|
+
function convertForEach(node) {
|
|
3592
|
+
const bodyNodes = Array.isArray(node.body) ? node.body : [];
|
|
3593
|
+
const body = bodyNodes.map(fromInterchangeNode);
|
|
3594
|
+
const roles = {};
|
|
3595
|
+
if (node.collection && typeof node.collection === "object") {
|
|
3596
|
+
roles.source = convertValue(node.collection);
|
|
3597
|
+
}
|
|
3598
|
+
const itemName = typeof node.itemName === "string" ? node.itemName : void 0;
|
|
3599
|
+
const indexName = typeof node.indexName === "string" ? node.indexName : void 0;
|
|
3600
|
+
return createLoopNode("repeat", roles, "for", body, itemName, indexName);
|
|
3601
|
+
}
|
|
3602
|
+
function convertWhile(node) {
|
|
3603
|
+
const bodyNodes = Array.isArray(node.body) ? node.body : [];
|
|
3604
|
+
const body = bodyNodes.map(fromInterchangeNode);
|
|
3605
|
+
const roles = {};
|
|
3606
|
+
if (node.condition && typeof node.condition === "object") {
|
|
3607
|
+
roles.condition = convertValue(node.condition);
|
|
3608
|
+
}
|
|
3609
|
+
return createLoopNode("repeat", roles, "while", body);
|
|
3610
|
+
}
|
|
3611
|
+
function convertValue(node) {
|
|
3612
|
+
switch (node.type) {
|
|
3613
|
+
case "literal":
|
|
3614
|
+
return convertLiteral(node);
|
|
3615
|
+
case "selector":
|
|
3616
|
+
return createSelector(String(node.value ?? ""), inferSelectorKind2(String(node.value ?? "")));
|
|
3617
|
+
case "identifier":
|
|
3618
|
+
return convertIdentifier(node);
|
|
3619
|
+
case "variable":
|
|
3620
|
+
return convertVariable(node);
|
|
3621
|
+
case "possessive":
|
|
3622
|
+
return createPropertyPath(
|
|
3623
|
+
node.object && typeof node.object === "object" ? convertValue(node.object) : createReference("me"),
|
|
3624
|
+
String(node.property ?? "")
|
|
3625
|
+
);
|
|
3626
|
+
// Complex expressions → collapse to raw string
|
|
3627
|
+
case "binary":
|
|
3628
|
+
case "unary":
|
|
3629
|
+
case "member":
|
|
3630
|
+
case "call":
|
|
3631
|
+
case "positional":
|
|
3632
|
+
return createExpression(renderExpr(node));
|
|
3633
|
+
default:
|
|
3634
|
+
if ("value" in node) return createLiteral(String(node.value ?? ""));
|
|
3635
|
+
return createExpression(renderExpr(node));
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
function convertLiteral(node) {
|
|
3639
|
+
const value = node.value;
|
|
3640
|
+
if (value === null || value === void 0) return createLiteral("null");
|
|
3641
|
+
const dataType = typeof value === "number" ? "number" : typeof value === "boolean" ? "boolean" : void 0;
|
|
3642
|
+
return createLiteral(value, dataType);
|
|
3643
|
+
}
|
|
3644
|
+
function convertIdentifier(node) {
|
|
3645
|
+
const value = String(node.value ?? "");
|
|
3646
|
+
if (DEFAULT_REFERENCES.has(value)) {
|
|
3647
|
+
return createReference(value);
|
|
3648
|
+
}
|
|
3649
|
+
return createLiteral(value);
|
|
3650
|
+
}
|
|
3651
|
+
function convertVariable(node) {
|
|
3652
|
+
const name = String(node.name ?? "");
|
|
3653
|
+
const scope = String(node.scope ?? "local");
|
|
3654
|
+
switch (scope) {
|
|
3655
|
+
case "element":
|
|
3656
|
+
return createExpression(`:${name}`);
|
|
3657
|
+
case "global":
|
|
3658
|
+
return createExpression(`$${name}`);
|
|
3659
|
+
default:
|
|
3660
|
+
return createExpression(name);
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
function renderExpr(node) {
|
|
3664
|
+
if (!node || typeof node !== "object") return "";
|
|
3665
|
+
switch (node.type) {
|
|
3666
|
+
case "literal": {
|
|
3667
|
+
const v = node.value;
|
|
3668
|
+
if (v === null || v === void 0) return "null";
|
|
3669
|
+
if (typeof v === "string") return /\s/.test(v) ? `"${v}"` : v;
|
|
3670
|
+
return String(v);
|
|
3671
|
+
}
|
|
3672
|
+
case "identifier":
|
|
3673
|
+
return String(node.value ?? "");
|
|
3674
|
+
case "selector":
|
|
3675
|
+
return String(node.value ?? "");
|
|
3676
|
+
case "variable": {
|
|
3677
|
+
const name = String(node.name ?? "");
|
|
3678
|
+
const scope = String(node.scope ?? "local");
|
|
3679
|
+
if (scope === "element") return `:${name}`;
|
|
3680
|
+
if (scope === "global") return `$${name}`;
|
|
3681
|
+
return name;
|
|
3682
|
+
}
|
|
3683
|
+
case "binary": {
|
|
3684
|
+
const left = node.left && typeof node.left === "object" ? renderExpr(node.left) : "";
|
|
3685
|
+
const right = node.right && typeof node.right === "object" ? renderExpr(node.right) : "";
|
|
3686
|
+
return `${left} ${node.operator ?? ""} ${right}`;
|
|
3687
|
+
}
|
|
3688
|
+
case "unary": {
|
|
3689
|
+
const operand = node.operand && typeof node.operand === "object" ? renderExpr(node.operand) : "";
|
|
3690
|
+
return `${node.operator ?? ""} ${operand}`;
|
|
3691
|
+
}
|
|
3692
|
+
case "member": {
|
|
3693
|
+
const obj = node.object && typeof node.object === "object" ? renderExpr(node.object) : "";
|
|
3694
|
+
if (node.computed && node.property && typeof node.property === "object") {
|
|
3695
|
+
return `${obj}[${renderExpr(node.property)}]`;
|
|
3696
|
+
}
|
|
3697
|
+
const prop = typeof node.property === "string" ? node.property : node.property && typeof node.property === "object" ? renderExpr(node.property) : "";
|
|
3698
|
+
return `${obj}.${prop}`;
|
|
3699
|
+
}
|
|
3700
|
+
case "possessive": {
|
|
3701
|
+
const obj = node.object && typeof node.object === "object" ? renderExpr(node.object) : "";
|
|
3702
|
+
return `${obj}'s ${node.property ?? ""}`;
|
|
3703
|
+
}
|
|
3704
|
+
case "call": {
|
|
3705
|
+
const callee = node.callee && typeof node.callee === "object" ? renderExpr(node.callee) : "";
|
|
3706
|
+
const args = Array.isArray(node.args) ? node.args.map((a) => renderExpr(a)).join(", ") : "";
|
|
3707
|
+
return `${callee}(${args})`;
|
|
3708
|
+
}
|
|
3709
|
+
case "positional": {
|
|
3710
|
+
const target = node.target && typeof node.target === "object" ? renderExpr(node.target) : "";
|
|
3711
|
+
return target ? `${node.position ?? ""} ${target}` : String(node.position ?? "");
|
|
3712
|
+
}
|
|
3713
|
+
default:
|
|
3714
|
+
if ("value" in node) return String(node.value ?? "");
|
|
3715
|
+
return "";
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
function inferRoles(name, args, modifiers, target) {
|
|
3719
|
+
const roles = {};
|
|
3720
|
+
switch (name) {
|
|
3721
|
+
case "set": {
|
|
3722
|
+
if (args[0]) roles.destination = convertValue(args[0]);
|
|
3723
|
+
const toVal = modifiers?.to;
|
|
3724
|
+
if (toVal && typeof toVal === "object" && "type" in toVal) {
|
|
3725
|
+
roles.patient = convertValue(toVal);
|
|
3726
|
+
} else if (args[1]) {
|
|
3727
|
+
roles.patient = convertValue(args[1]);
|
|
3728
|
+
}
|
|
3729
|
+
break;
|
|
3730
|
+
}
|
|
3731
|
+
case "put": {
|
|
3732
|
+
if (args[0]) roles.patient = convertValue(args[0]);
|
|
3733
|
+
if (modifiers) {
|
|
3734
|
+
for (const key of ["into", "before", "after"]) {
|
|
3735
|
+
const val = modifiers[key];
|
|
3736
|
+
if (val && typeof val === "object" && "type" in val) {
|
|
3737
|
+
roles.destination = convertValue(val);
|
|
3738
|
+
roles.method = createLiteral(key);
|
|
3739
|
+
break;
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
if (!roles.destination && target) {
|
|
3744
|
+
roles.destination = convertValue(target);
|
|
3745
|
+
}
|
|
3746
|
+
break;
|
|
3747
|
+
}
|
|
3748
|
+
case "increment":
|
|
3749
|
+
case "decrement": {
|
|
3750
|
+
if (args[0]) roles.destination = convertValue(args[0]);
|
|
3751
|
+
const byVal = modifiers?.by;
|
|
3752
|
+
if (byVal && typeof byVal === "object" && "type" in byVal) {
|
|
3753
|
+
roles.quantity = convertValue(byVal);
|
|
3754
|
+
} else if (args[1]) {
|
|
3755
|
+
roles.quantity = convertValue(args[1]);
|
|
3756
|
+
}
|
|
3757
|
+
break;
|
|
3758
|
+
}
|
|
3759
|
+
case "fetch": {
|
|
3760
|
+
if (args[0]) roles.source = convertValue(args[0]);
|
|
3761
|
+
const asVal = modifiers?.as;
|
|
3762
|
+
if (asVal && typeof asVal === "object" && "type" in asVal) {
|
|
3763
|
+
roles.responseType = convertValue(asVal);
|
|
3764
|
+
} else if (typeof asVal === "string") {
|
|
3765
|
+
roles.responseType = createLiteral(asVal);
|
|
3766
|
+
}
|
|
3767
|
+
break;
|
|
3768
|
+
}
|
|
3769
|
+
case "wait":
|
|
3770
|
+
case "settle": {
|
|
3771
|
+
if (args[0]) roles.duration = convertValue(args[0]);
|
|
3772
|
+
break;
|
|
3773
|
+
}
|
|
3774
|
+
case "toggle":
|
|
3775
|
+
case "add":
|
|
3776
|
+
case "show":
|
|
3777
|
+
case "hide": {
|
|
3778
|
+
if (args[0]) roles.patient = convertValue(args[0]);
|
|
3779
|
+
if (target) roles.destination = convertValue(target);
|
|
3780
|
+
break;
|
|
3781
|
+
}
|
|
3782
|
+
case "remove": {
|
|
3783
|
+
if (args[0]) roles.patient = convertValue(args[0]);
|
|
3784
|
+
if (target) roles.source = convertValue(target);
|
|
3785
|
+
break;
|
|
3786
|
+
}
|
|
3787
|
+
case "send":
|
|
3788
|
+
case "trigger": {
|
|
3789
|
+
if (args[0]) roles.patient = convertValue(args[0]);
|
|
3790
|
+
if (target) roles.destination = convertValue(target);
|
|
3791
|
+
break;
|
|
3792
|
+
}
|
|
3793
|
+
case "log": {
|
|
3794
|
+
if (args[0]) roles.patient = convertValue(args[0]);
|
|
3795
|
+
break;
|
|
3796
|
+
}
|
|
3797
|
+
default: {
|
|
3798
|
+
if (args[0]) roles.patient = convertValue(args[0]);
|
|
3799
|
+
if (target) roles.destination = convertValue(target);
|
|
3800
|
+
break;
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
return roles;
|
|
3804
|
+
}
|
|
3805
|
+
function inferSelectorKind2(value) {
|
|
3806
|
+
if (!value) return void 0;
|
|
3807
|
+
if (value.startsWith("#")) return "id";
|
|
3808
|
+
if (value.startsWith(".")) return "class";
|
|
3809
|
+
if (value.startsWith("[")) return "attribute";
|
|
3810
|
+
if (value.startsWith("<") || value.startsWith("*")) return "element";
|
|
3811
|
+
if (/[>\+~ ]/.test(value) && value.length > 1) return "complex";
|
|
3812
|
+
return void 0;
|
|
3813
|
+
}
|
|
3814
|
+
function inferValueFromString(value) {
|
|
3815
|
+
if (DEFAULT_REFERENCES.has(value)) return createReference(value);
|
|
3816
|
+
const kind = inferSelectorKind2(value);
|
|
3817
|
+
if (kind) return createSelector(value, kind);
|
|
3818
|
+
return createLiteral(value);
|
|
3819
|
+
}
|
|
3820
|
+
function bridgeEventModifiers(mods) {
|
|
3821
|
+
const once = mods.once === true ? true : void 0;
|
|
3822
|
+
const debounce = typeof mods.debounce === "number" ? mods.debounce : void 0;
|
|
3823
|
+
const throttle = typeof mods.throttle === "number" ? mods.throttle : void 0;
|
|
3824
|
+
const from = typeof mods.from === "string" ? inferValueFromString(mods.from) : void 0;
|
|
3825
|
+
if (once === void 0 && debounce === void 0 && throttle === void 0 && from === void 0) {
|
|
3826
|
+
return void 0;
|
|
3827
|
+
}
|
|
3828
|
+
return {
|
|
3829
|
+
...once !== void 0 ? { once } : {},
|
|
3830
|
+
...debounce !== void 0 ? { debounce } : {},
|
|
3831
|
+
...throttle !== void 0 ? { throttle } : {},
|
|
3832
|
+
...from !== void 0 ? { from } : {}
|
|
3833
|
+
};
|
|
3834
|
+
}
|
|
3835
|
+
|
|
3836
|
+
// src/ir/to-runtime-ast.ts
|
|
3837
|
+
function semanticNodeToRuntimeAST(node) {
|
|
3838
|
+
switch (node.kind) {
|
|
3839
|
+
case "event-handler":
|
|
3840
|
+
return convertEventHandler(node);
|
|
3841
|
+
case "conditional":
|
|
3842
|
+
return convertConditional(node);
|
|
3843
|
+
case "loop":
|
|
3844
|
+
return convertLoop(node);
|
|
3845
|
+
case "compound": {
|
|
3846
|
+
const compound = node;
|
|
3847
|
+
const commands = (compound.statements ?? []).map(semanticNodeToRuntimeAST);
|
|
3848
|
+
return {
|
|
3849
|
+
type: "CommandSequence",
|
|
3850
|
+
commands
|
|
3851
|
+
};
|
|
3852
|
+
}
|
|
3853
|
+
case "command":
|
|
3854
|
+
default:
|
|
3855
|
+
return convertCommand2(node);
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
function convertEventHandler(eh) {
|
|
3859
|
+
const eventValue = eh.roles.get("event");
|
|
3860
|
+
const eventName = eventValue && "value" in eventValue ? String(eventValue.value) : "click";
|
|
3861
|
+
const modifiers = {};
|
|
3862
|
+
if (eh.eventModifiers) {
|
|
3863
|
+
if (eh.eventModifiers.once) modifiers.once = true;
|
|
3864
|
+
if (eh.eventModifiers.debounce) modifiers.debounce = eh.eventModifiers.debounce;
|
|
3865
|
+
if (eh.eventModifiers.throttle) modifiers.throttle = eh.eventModifiers.throttle;
|
|
3866
|
+
if (eh.eventModifiers.queue) modifiers.queue = eh.eventModifiers.queue;
|
|
3867
|
+
}
|
|
3868
|
+
return {
|
|
3869
|
+
type: "event",
|
|
3870
|
+
event: eventName,
|
|
3871
|
+
modifiers,
|
|
3872
|
+
body: (eh.body ?? []).map(semanticNodeToRuntimeAST)
|
|
3873
|
+
};
|
|
3874
|
+
}
|
|
3875
|
+
function convertCommand2(node) {
|
|
3876
|
+
const roles = {};
|
|
3877
|
+
const args = [];
|
|
3878
|
+
for (const [roleName, value] of node.roles) {
|
|
3879
|
+
const astValue = semanticValueToAST(value);
|
|
3880
|
+
roles[roleName] = astValue;
|
|
3881
|
+
args.push(astValue);
|
|
3882
|
+
}
|
|
3883
|
+
return {
|
|
3884
|
+
type: "command",
|
|
3885
|
+
name: node.action,
|
|
3886
|
+
args,
|
|
3887
|
+
roles
|
|
3888
|
+
};
|
|
3889
|
+
}
|
|
3890
|
+
function convertConditional(node) {
|
|
3891
|
+
const conditionValue = node.roles.get("condition");
|
|
3892
|
+
return {
|
|
3893
|
+
type: "command",
|
|
3894
|
+
name: "if",
|
|
3895
|
+
args: conditionValue ? [semanticValueToAST(conditionValue)] : [],
|
|
3896
|
+
condition: conditionValue ? semanticValueToAST(conditionValue) : void 0,
|
|
3897
|
+
thenBranch: (node.thenBranch ?? []).map(semanticNodeToRuntimeAST),
|
|
3898
|
+
elseBranch: (node.elseBranch ?? []).map(semanticNodeToRuntimeAST)
|
|
3899
|
+
};
|
|
3900
|
+
}
|
|
3901
|
+
function convertLoop(node) {
|
|
3902
|
+
const countValue = node.roles.get("patient") || node.roles.get("count");
|
|
3903
|
+
return {
|
|
3904
|
+
type: "command",
|
|
3905
|
+
name: "repeat",
|
|
3906
|
+
args: countValue ? [semanticValueToAST(countValue)] : [],
|
|
3907
|
+
loopVariant: node.loopVariant ?? "forever",
|
|
3908
|
+
body: (node.body ?? []).map(semanticNodeToRuntimeAST),
|
|
3909
|
+
loopVariable: node.loopVariable,
|
|
3910
|
+
indexVariable: node.indexVariable
|
|
3911
|
+
};
|
|
3912
|
+
}
|
|
3913
|
+
function semanticValueToAST(value) {
|
|
3914
|
+
switch (value.type) {
|
|
3915
|
+
case "selector":
|
|
3916
|
+
return { type: "selector", value: value.value };
|
|
3917
|
+
case "reference":
|
|
3918
|
+
return { type: "identifier", value: value.value };
|
|
3919
|
+
case "literal": {
|
|
3920
|
+
const lit = value;
|
|
3921
|
+
if (lit.dataType === "duration") {
|
|
3922
|
+
const str = String(lit.value);
|
|
3923
|
+
const match = /^(\d+(?:\.\d+)?)(ms|s)$/.exec(str);
|
|
3924
|
+
if (match) {
|
|
3925
|
+
const ms = match[2] === "s" ? parseFloat(match[1]) * 1e3 : parseFloat(match[1]);
|
|
3926
|
+
return { type: "literal", value: ms };
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
return { type: "literal", value: lit.value };
|
|
3930
|
+
}
|
|
3931
|
+
case "property-path": {
|
|
3932
|
+
const pp = value;
|
|
3933
|
+
const objectAST = semanticValueToAST(pp.object);
|
|
3934
|
+
return {
|
|
3935
|
+
type: "member",
|
|
3936
|
+
object: objectAST,
|
|
3937
|
+
property: pp.property
|
|
3938
|
+
};
|
|
3939
|
+
}
|
|
3940
|
+
case "expression": {
|
|
3941
|
+
const expr = value;
|
|
3942
|
+
return { type: "expression", value: expr.raw };
|
|
3943
|
+
}
|
|
3944
|
+
case "flag": {
|
|
3945
|
+
const flag = value;
|
|
3946
|
+
return { type: "literal", value: flag.enabled };
|
|
3947
|
+
}
|
|
3948
|
+
default:
|
|
3949
|
+
return { type: "literal", value: String(value.value ?? "") };
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
|
|
3953
|
+
// src/api/create-dsl.ts
|
|
3954
|
+
var DSLRegistry = class {
|
|
3955
|
+
constructor(config) {
|
|
3956
|
+
this.patterns = /* @__PURE__ */ new Map();
|
|
3957
|
+
this.tokenizers = /* @__PURE__ */ new Map();
|
|
3958
|
+
this.schemas = config.schemas;
|
|
3959
|
+
for (const lang of config.languages) {
|
|
3960
|
+
this.registerLanguage(lang);
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
registerLanguage(lang) {
|
|
3964
|
+
this.tokenizers.set(lang.code, lang.tokenizer);
|
|
3965
|
+
const patterns = [];
|
|
3966
|
+
for (const schema of this.schemas) {
|
|
3967
|
+
const pattern = generatePattern(schema, lang.patternProfile);
|
|
3968
|
+
patterns.push(pattern);
|
|
3969
|
+
}
|
|
3970
|
+
this.patterns.set(lang.code, patterns);
|
|
3971
|
+
}
|
|
3972
|
+
getPatterns(language) {
|
|
3973
|
+
return this.patterns.get(language) || [];
|
|
3974
|
+
}
|
|
3975
|
+
getTokenizer(language) {
|
|
3976
|
+
return this.tokenizers.get(language);
|
|
3977
|
+
}
|
|
3978
|
+
getSupportedLanguages() {
|
|
3979
|
+
return Array.from(this.patterns.keys());
|
|
3980
|
+
}
|
|
3981
|
+
};
|
|
3982
|
+
var MultilingualDSLImpl = class {
|
|
3983
|
+
constructor(config, registry, transformer) {
|
|
3984
|
+
this.registry = registry;
|
|
3985
|
+
this.matcher = new PatternMatcher();
|
|
3986
|
+
this.transformer = transformer;
|
|
3987
|
+
if (config.codeGenerator) {
|
|
3988
|
+
this.codeGenerator = config.codeGenerator;
|
|
3989
|
+
}
|
|
3990
|
+
const schemaMap = new Map(config.schemas.map((s) => [s.action, s]));
|
|
3991
|
+
this.schemaLookup = {
|
|
3992
|
+
getSchema: (action) => schemaMap.get(action)
|
|
3993
|
+
};
|
|
3994
|
+
}
|
|
3995
|
+
parse(input, language) {
|
|
3996
|
+
const result = this.parseWithConfidence(input, language);
|
|
3997
|
+
return result.node;
|
|
3998
|
+
}
|
|
3999
|
+
parseWithConfidence(input, language) {
|
|
4000
|
+
if (isExplicitSyntax(input)) {
|
|
4001
|
+
const node = parseExplicit(input, { schemaLookup: this.schemaLookup });
|
|
4002
|
+
if (!this.schemaLookup.getSchema(node.action)) {
|
|
4003
|
+
throw new Error(`No schema for action "${node.action}" in this DSL`);
|
|
4004
|
+
}
|
|
4005
|
+
return { node, confidence: 1 };
|
|
4006
|
+
}
|
|
4007
|
+
const tokenizer = this.registry.getTokenizer(language);
|
|
4008
|
+
if (!tokenizer) {
|
|
4009
|
+
throw new Error(`No tokenizer registered for language: ${language}`);
|
|
4010
|
+
}
|
|
4011
|
+
const tokens = tokenizer.tokenize(input);
|
|
4012
|
+
const patterns = this.registry.getPatterns(language);
|
|
4013
|
+
const profile = {
|
|
4014
|
+
code: language
|
|
4015
|
+
};
|
|
4016
|
+
for (const pattern of patterns) {
|
|
4017
|
+
const match = this.matcher.matchPattern(tokens, pattern, profile);
|
|
4018
|
+
if (match) {
|
|
4019
|
+
const node = {
|
|
4020
|
+
kind: "command",
|
|
4021
|
+
action: pattern.command,
|
|
4022
|
+
roles: match.captured,
|
|
4023
|
+
metadata: {
|
|
4024
|
+
sourceLanguage: language,
|
|
4025
|
+
sourceText: input,
|
|
4026
|
+
patternId: pattern.id,
|
|
4027
|
+
confidence: match.confidence
|
|
4028
|
+
}
|
|
4029
|
+
};
|
|
4030
|
+
return { node, confidence: match.confidence };
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
throw new Error(`No pattern matched for input: ${input}`);
|
|
4034
|
+
}
|
|
4035
|
+
validate(input, language) {
|
|
4036
|
+
try {
|
|
4037
|
+
const result = this.parseWithConfidence(input, language);
|
|
4038
|
+
return {
|
|
4039
|
+
valid: true,
|
|
4040
|
+
node: result.node
|
|
4041
|
+
};
|
|
4042
|
+
} catch (error) {
|
|
4043
|
+
return {
|
|
4044
|
+
valid: false,
|
|
4045
|
+
errors: [error instanceof Error ? error.message : String(error)]
|
|
4046
|
+
};
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
translate(input, fromLanguage, toLanguage) {
|
|
4050
|
+
if (isExplicitSyntax(input)) return input;
|
|
4051
|
+
return this.transformer.transform(input, fromLanguage, toLanguage);
|
|
4052
|
+
}
|
|
4053
|
+
compile(input, language) {
|
|
2118
4054
|
if (!this.codeGenerator) {
|
|
2119
4055
|
return {
|
|
2120
4056
|
ok: false,
|
|
2121
4057
|
errors: ["No code generator configured for this DSL"]
|
|
2122
4058
|
};
|
|
2123
4059
|
}
|
|
2124
|
-
try {
|
|
2125
|
-
const result = this.parseWithConfidence(input, language);
|
|
2126
|
-
const code = this.codeGenerator.generate(result.node);
|
|
4060
|
+
try {
|
|
4061
|
+
const result = this.parseWithConfidence(input, language);
|
|
4062
|
+
const code = this.codeGenerator.generate(result.node);
|
|
4063
|
+
return {
|
|
4064
|
+
ok: true,
|
|
4065
|
+
code,
|
|
4066
|
+
node: result.node,
|
|
4067
|
+
metadata: {
|
|
4068
|
+
parser: "semantic",
|
|
4069
|
+
confidence: result.confidence
|
|
4070
|
+
}
|
|
4071
|
+
};
|
|
4072
|
+
} catch (error) {
|
|
4073
|
+
return {
|
|
4074
|
+
ok: false,
|
|
4075
|
+
errors: [error instanceof Error ? error.message : String(error)]
|
|
4076
|
+
};
|
|
4077
|
+
}
|
|
4078
|
+
}
|
|
4079
|
+
getSupportedLanguages() {
|
|
4080
|
+
return this.registry.getSupportedLanguages();
|
|
4081
|
+
}
|
|
4082
|
+
};
|
|
4083
|
+
function createDefaultDictionary(config) {
|
|
4084
|
+
const translations = {};
|
|
4085
|
+
for (const lang of config.languages) {
|
|
4086
|
+
const keywords = {};
|
|
4087
|
+
for (const [canonical, keywordDef] of Object.entries(lang.patternProfile.keywords)) {
|
|
4088
|
+
keywords[canonical] = keywordDef.primary;
|
|
4089
|
+
}
|
|
4090
|
+
translations[lang.code] = keywords;
|
|
4091
|
+
}
|
|
4092
|
+
return new InMemoryDictionary(translations);
|
|
4093
|
+
}
|
|
4094
|
+
function createDefaultProfileProvider(config) {
|
|
4095
|
+
const profiles = {};
|
|
4096
|
+
for (const lang of config.languages) {
|
|
4097
|
+
if (lang.grammarProfile) {
|
|
4098
|
+
profiles[lang.code] = lang.grammarProfile;
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
return new InMemoryProfileProvider(profiles);
|
|
4102
|
+
}
|
|
4103
|
+
function createMultilingualDSL(config) {
|
|
4104
|
+
const dictionary = config.dictionary ?? createDefaultDictionary(config);
|
|
4105
|
+
const profileProvider = config.profileProvider ?? createDefaultProfileProvider(config);
|
|
4106
|
+
const transformer = new GrammarTransformer({
|
|
4107
|
+
dictionary,
|
|
4108
|
+
profileProvider
|
|
4109
|
+
});
|
|
4110
|
+
const registry = new DSLRegistry(config);
|
|
4111
|
+
return new MultilingualDSLImpl(config, registry, transformer);
|
|
4112
|
+
}
|
|
4113
|
+
|
|
4114
|
+
// src/prompts/prompt-generator.ts
|
|
4115
|
+
function generatePrompt(config) {
|
|
4116
|
+
const outputFormat = config.outputFormat ?? "both";
|
|
4117
|
+
const examplesPerCommand = config.examplesPerCommand ?? 2;
|
|
4118
|
+
const sections = [
|
|
4119
|
+
buildProtocolSection(),
|
|
4120
|
+
buildValueTypeSection(),
|
|
4121
|
+
buildCommandsSection(config.schemas, outputFormat, examplesPerCommand),
|
|
4122
|
+
buildOutputFormatSection(outputFormat),
|
|
4123
|
+
buildErrorRecoverySection()
|
|
4124
|
+
];
|
|
4125
|
+
let finalSections = sections;
|
|
4126
|
+
if (config.maxTokens && config.maxTokens > 0) {
|
|
4127
|
+
finalSections = truncateToTokenBudget(sections, config.maxTokens);
|
|
4128
|
+
}
|
|
4129
|
+
const text = finalSections.map((s) => `## ${s.title}
|
|
4130
|
+
|
|
4131
|
+
${s.content}`).join("\n\n---\n\n");
|
|
4132
|
+
const totalRoles = config.schemas.reduce((sum, s) => sum + s.roles.length, 0);
|
|
4133
|
+
const approximateTokens = estimateTokens(text);
|
|
4134
|
+
const metadata = {
|
|
4135
|
+
domain: config.domain,
|
|
4136
|
+
commandCount: config.schemas.length,
|
|
4137
|
+
roleCount: totalRoles,
|
|
4138
|
+
approximateTokens
|
|
4139
|
+
};
|
|
4140
|
+
return { text, sections: finalSections, metadata };
|
|
4141
|
+
}
|
|
4142
|
+
function generateExamples(schema, count = 2) {
|
|
4143
|
+
const combos = generateRoleCombinations(schema);
|
|
4144
|
+
const examples = [];
|
|
4145
|
+
for (let i = 0; i < Math.min(count, combos.length); i++) {
|
|
4146
|
+
const combo = combos[i];
|
|
4147
|
+
const roles = /* @__PURE__ */ new Map();
|
|
4148
|
+
for (const role of combo) {
|
|
4149
|
+
const sample = sampleValue(role);
|
|
4150
|
+
roles.set(role.role, sample);
|
|
4151
|
+
}
|
|
4152
|
+
const nodeRoles = /* @__PURE__ */ new Map();
|
|
4153
|
+
for (const [roleName, sample] of roles) {
|
|
4154
|
+
nodeRoles.set(roleName, toSemanticValue(sample));
|
|
4155
|
+
}
|
|
4156
|
+
const node = createCommandNode(schema.action, nodeRoles);
|
|
4157
|
+
const explicit = renderExplicit(node);
|
|
4158
|
+
const jsonRoles = {};
|
|
4159
|
+
for (const [roleName, sample] of roles) {
|
|
4160
|
+
jsonRoles[roleName] = sample;
|
|
4161
|
+
}
|
|
4162
|
+
const json = { action: schema.action, roles: jsonRoles };
|
|
4163
|
+
examples.push({ explicit, json });
|
|
4164
|
+
}
|
|
4165
|
+
return examples;
|
|
4166
|
+
}
|
|
4167
|
+
function generateProtocolReference() {
|
|
4168
|
+
const protocol = buildProtocolSection();
|
|
4169
|
+
const valueTypes = buildValueTypeSection();
|
|
4170
|
+
return `# LokaScript Explicit Syntax (LSE) Quick Reference
|
|
4171
|
+
|
|
4172
|
+
${protocol.content}
|
|
4173
|
+
|
|
4174
|
+
${valueTypes.content}`;
|
|
4175
|
+
}
|
|
4176
|
+
function buildProtocolSection() {
|
|
4177
|
+
const content = `LokaScript Explicit Syntax (LSE) is a bracket-based format for imperative commands.
|
|
4178
|
+
|
|
4179
|
+
### Syntax
|
|
4180
|
+
|
|
4181
|
+
\`\`\`
|
|
4182
|
+
[command role1:value1 role2:value2 +flag1 ~flag2]
|
|
4183
|
+
\`\`\`
|
|
4184
|
+
|
|
4185
|
+
- **Command**: The first token inside brackets (lowercased)
|
|
4186
|
+
- **Role pair**: \`name:value\` \u2014 a named semantic role with a typed value (no space around colon)
|
|
4187
|
+
- **Enabled flag**: \`+name\` \u2014 boolean attribute present
|
|
4188
|
+
- **Disabled flag**: \`~name\` \u2014 boolean attribute negated
|
|
4189
|
+
- **Nested body**: \`body:[command ...]\` \u2014 a bracket command inside a role value
|
|
4190
|
+
|
|
4191
|
+
### Rules
|
|
4192
|
+
|
|
4193
|
+
1. Commands are always lowercased
|
|
4194
|
+
2. Role names preserve their original case
|
|
4195
|
+
3. No spaces around the colon in role:value pairs
|
|
4196
|
+
4. Strings with spaces must be quoted: \`patient:"hello world"\`
|
|
4197
|
+
5. Selectors start with \`#\`, \`.\`, \`[\`, \`@\`, or \`*\`
|
|
4198
|
+
6. Output must be valid bracket syntax: \`[action role:value ...]\``;
|
|
4199
|
+
return {
|
|
4200
|
+
id: "protocol",
|
|
4201
|
+
title: "LSE Protocol",
|
|
4202
|
+
content,
|
|
4203
|
+
approximateTokens: estimateTokens(content)
|
|
4204
|
+
};
|
|
4205
|
+
}
|
|
4206
|
+
function buildValueTypeSection() {
|
|
4207
|
+
const content = `Values in LSE are classified by their syntactic form (first match wins):
|
|
4208
|
+
|
|
4209
|
+
| Type | Syntax | Example |
|
|
4210
|
+
|------|--------|---------|
|
|
4211
|
+
| Selector | Starts with \`#\` \`.\` \`[\` \`@\` \`*\` | \`#button\`, \`.active\`, \`[data-id]\` |
|
|
4212
|
+
| String | Quoted with \`"\` or \`'\` | \`"hello world"\`, \`'json'\` |
|
|
4213
|
+
| Boolean | Exact: \`true\` / \`false\` | \`visible:true\` |
|
|
4214
|
+
| Reference | Built-in name | \`me\`, \`you\`, \`it\`, \`result\`, \`event\`, \`target\`, \`body\` |
|
|
4215
|
+
| Duration | Number + suffix | \`500ms\`, \`2s\`, \`1m\`, \`1h\` |
|
|
4216
|
+
| Number | Digits with optional decimal | \`5\`, \`3.14\`, \`-1\` |
|
|
4217
|
+
| Plain | Fallback (any non-whitespace) | \`/api/users\`, \`json\`, \`production\` |
|
|
4218
|
+
|
|
4219
|
+
**Important**: Classification is by prefix, not by intent. \`#true\` is a selector (not boolean). \`event\` as a value is a reference (not plain text).`;
|
|
4220
|
+
return {
|
|
4221
|
+
id: "value-types",
|
|
4222
|
+
title: "Value Types",
|
|
4223
|
+
content,
|
|
4224
|
+
approximateTokens: estimateTokens(content)
|
|
4225
|
+
};
|
|
4226
|
+
}
|
|
4227
|
+
function buildCommandsSection(schemas, outputFormat, examplesPerCommand) {
|
|
4228
|
+
const parts = [];
|
|
4229
|
+
for (const schema of schemas) {
|
|
4230
|
+
parts.push(formatCommandDoc(schema, outputFormat, examplesPerCommand));
|
|
4231
|
+
}
|
|
4232
|
+
const content = parts.join("\n\n");
|
|
4233
|
+
return {
|
|
4234
|
+
id: "commands",
|
|
4235
|
+
title: "Available Commands",
|
|
4236
|
+
content,
|
|
4237
|
+
approximateTokens: estimateTokens(content)
|
|
4238
|
+
};
|
|
4239
|
+
}
|
|
4240
|
+
function buildOutputFormatSection(outputFormat) {
|
|
4241
|
+
let content;
|
|
4242
|
+
if (outputFormat === "explicit") {
|
|
4243
|
+
content = `Output valid LSE bracket syntax. Each command must be wrapped in brackets:
|
|
4244
|
+
|
|
4245
|
+
\`\`\`
|
|
4246
|
+
[command role:value ...]
|
|
4247
|
+
\`\`\`
|
|
4248
|
+
|
|
4249
|
+
Do NOT output JSON. Only use bracket syntax.`;
|
|
4250
|
+
} else if (outputFormat === "json") {
|
|
4251
|
+
content = `Output valid JSON in the LLM-simplified format:
|
|
4252
|
+
|
|
4253
|
+
\`\`\`json
|
|
4254
|
+
{
|
|
4255
|
+
"action": "command-name",
|
|
4256
|
+
"roles": {
|
|
4257
|
+
"roleName": { "type": "valueType", "value": "theValue" }
|
|
4258
|
+
}
|
|
4259
|
+
}
|
|
4260
|
+
\`\`\`
|
|
4261
|
+
|
|
4262
|
+
Valid value types: \`selector\`, \`literal\`, \`reference\`, \`expression\`.
|
|
4263
|
+
Do NOT output bracket syntax. Only use JSON.`;
|
|
4264
|
+
} else {
|
|
4265
|
+
content = `You may output EITHER format:
|
|
4266
|
+
|
|
4267
|
+
**Bracket syntax** (preferred for single commands):
|
|
4268
|
+
\`\`\`
|
|
4269
|
+
[command role:value ...]
|
|
4270
|
+
\`\`\`
|
|
4271
|
+
|
|
4272
|
+
**JSON format** (preferred when structured data is needed):
|
|
4273
|
+
\`\`\`json
|
|
4274
|
+
{
|
|
4275
|
+
"action": "command-name",
|
|
4276
|
+
"roles": {
|
|
4277
|
+
"roleName": { "type": "valueType", "value": "theValue" }
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
\`\`\`
|
|
4281
|
+
|
|
4282
|
+
Both formats are equally valid. Use whichever is more natural for the context.`;
|
|
4283
|
+
}
|
|
4284
|
+
return {
|
|
4285
|
+
id: "output-format",
|
|
4286
|
+
title: "Output Format",
|
|
4287
|
+
content,
|
|
4288
|
+
approximateTokens: estimateTokens(content)
|
|
4289
|
+
};
|
|
4290
|
+
}
|
|
4291
|
+
function buildErrorRecoverySection() {
|
|
4292
|
+
const content = `If you're unsure about a command or role:
|
|
4293
|
+
|
|
4294
|
+
1. **Unknown command**: Use the closest matching command from the Available Commands section. Do not invent commands.
|
|
4295
|
+
2. **Unknown role**: Use only the roles listed for each command. Do not add roles not in the schema.
|
|
4296
|
+
3. **Ambiguous value type**: When in doubt, use \`expression\` type (it's the most permissive).
|
|
4297
|
+
4. **Missing required role**: Always include all required roles. Check the Required/Optional labels.
|
|
4298
|
+
5. **Selector vs plain value**: If a value starts with \`#\`, \`.\`, \`[\`, \`@\`, or \`*\`, it's a selector. URLs like \`/api/data\` are plain values.
|
|
4299
|
+
|
|
4300
|
+
When generating LSE, prefer explicit role labeling over positional guessing. Every value should have a role name.`;
|
|
4301
|
+
return {
|
|
4302
|
+
id: "error-recovery",
|
|
4303
|
+
title: "Error Recovery",
|
|
4304
|
+
content,
|
|
4305
|
+
approximateTokens: estimateTokens(content)
|
|
4306
|
+
};
|
|
4307
|
+
}
|
|
4308
|
+
function formatCommandDoc(schema, outputFormat, examplesPerCommand) {
|
|
4309
|
+
const lines = [];
|
|
4310
|
+
lines.push(`### \`${schema.action}\` \u2014 ${schema.description}`);
|
|
4311
|
+
lines.push("");
|
|
4312
|
+
const required = schema.roles.filter((r) => r.required);
|
|
4313
|
+
const optional = schema.roles.filter((r) => !r.required);
|
|
4314
|
+
if (required.length > 0) {
|
|
4315
|
+
lines.push("**Required roles:**");
|
|
4316
|
+
for (const role of required) {
|
|
4317
|
+
lines.push(`- \`${role.role}\`: ${role.description} (type: ${formatExpectedTypes(role)})`);
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
if (optional.length > 0) {
|
|
4321
|
+
lines.push("**Optional roles:**");
|
|
4322
|
+
for (const role of optional) {
|
|
4323
|
+
lines.push(`- \`${role.role}\`: ${role.description} (type: ${formatExpectedTypes(role)})`);
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4326
|
+
const examples = generateExamples(schema, examplesPerCommand);
|
|
4327
|
+
if (examples.length > 0) {
|
|
4328
|
+
lines.push("");
|
|
4329
|
+
if (outputFormat === "explicit" || outputFormat === "both") {
|
|
4330
|
+
lines.push("**Bracket syntax:**");
|
|
4331
|
+
for (const ex of examples) {
|
|
4332
|
+
lines.push(`\`\`\`
|
|
4333
|
+
${ex.explicit}
|
|
4334
|
+
\`\`\``);
|
|
4335
|
+
}
|
|
4336
|
+
}
|
|
4337
|
+
if (outputFormat === "json" || outputFormat === "both") {
|
|
4338
|
+
lines.push("**JSON format:**");
|
|
4339
|
+
for (const ex of examples) {
|
|
4340
|
+
lines.push(`\`\`\`json
|
|
4341
|
+
${JSON.stringify(ex.json, null, 2)}
|
|
4342
|
+
\`\`\``);
|
|
4343
|
+
}
|
|
4344
|
+
}
|
|
4345
|
+
}
|
|
4346
|
+
return lines.join("\n");
|
|
4347
|
+
}
|
|
4348
|
+
function formatExpectedTypes(role) {
|
|
4349
|
+
return role.expectedTypes.join(" | ");
|
|
4350
|
+
}
|
|
4351
|
+
function generateRoleCombinations(schema) {
|
|
4352
|
+
const required = schema.roles.filter((r) => r.required);
|
|
4353
|
+
const optional = schema.roles.filter((r) => !r.required);
|
|
4354
|
+
const combos = [];
|
|
4355
|
+
if (required.length > 0) {
|
|
4356
|
+
combos.push(required);
|
|
4357
|
+
}
|
|
4358
|
+
for (const opt of optional) {
|
|
4359
|
+
combos.push([...required, opt]);
|
|
4360
|
+
}
|
|
4361
|
+
if (optional.length >= 2) {
|
|
4362
|
+
combos.push([...required, ...optional]);
|
|
4363
|
+
}
|
|
4364
|
+
if (combos.length === 0) {
|
|
4365
|
+
combos.push(schema.roles.slice());
|
|
4366
|
+
}
|
|
4367
|
+
return combos;
|
|
4368
|
+
}
|
|
4369
|
+
function sampleValue(role) {
|
|
4370
|
+
const primaryType = role.expectedTypes[0] || "expression";
|
|
4371
|
+
switch (primaryType) {
|
|
4372
|
+
case "selector":
|
|
4373
|
+
return sampleSelector(role.role);
|
|
4374
|
+
case "literal":
|
|
4375
|
+
return sampleLiteral(role.role);
|
|
4376
|
+
case "reference":
|
|
4377
|
+
return { type: "reference", value: "me" };
|
|
4378
|
+
case "expression":
|
|
4379
|
+
return sampleExpression(role.role);
|
|
4380
|
+
case "flag":
|
|
4381
|
+
return { type: "flag", value: true };
|
|
4382
|
+
default:
|
|
4383
|
+
return { type: "literal", value: "example" };
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4386
|
+
function sampleSelector(roleName) {
|
|
4387
|
+
const selectors = {
|
|
4388
|
+
patient: ".active",
|
|
4389
|
+
destination: "#output",
|
|
4390
|
+
source: "#input",
|
|
4391
|
+
target: "#target"
|
|
4392
|
+
};
|
|
4393
|
+
return { type: "selector", value: selectors[roleName] || `#${roleName}` };
|
|
4394
|
+
}
|
|
4395
|
+
function sampleLiteral(roleName) {
|
|
4396
|
+
const literals = {
|
|
4397
|
+
duration: "30s",
|
|
4398
|
+
interval: "5s",
|
|
4399
|
+
delay: "500ms",
|
|
4400
|
+
timeout: "10s",
|
|
4401
|
+
quantity: 5,
|
|
4402
|
+
limit: 10,
|
|
4403
|
+
style: "json",
|
|
4404
|
+
manner: "rolling",
|
|
4405
|
+
format: "html"
|
|
4406
|
+
};
|
|
4407
|
+
return { type: "literal", value: literals[roleName] || roleName };
|
|
4408
|
+
}
|
|
4409
|
+
function sampleExpression(roleName) {
|
|
4410
|
+
const expressions = {
|
|
4411
|
+
source: "/api/data",
|
|
4412
|
+
destination: "#output",
|
|
4413
|
+
patient: ".active",
|
|
4414
|
+
instrument: "toUpperCase",
|
|
4415
|
+
condition: "age > 18",
|
|
4416
|
+
style: "json",
|
|
4417
|
+
duration: "5s",
|
|
4418
|
+
url: "/api/users"
|
|
4419
|
+
};
|
|
4420
|
+
return { type: "expression", value: expressions[roleName] || `/api/${roleName}` };
|
|
4421
|
+
}
|
|
4422
|
+
function toSemanticValue(sample) {
|
|
4423
|
+
switch (sample.type) {
|
|
4424
|
+
case "selector":
|
|
4425
|
+
return {
|
|
4426
|
+
type: "selector",
|
|
4427
|
+
value: String(sample.value),
|
|
4428
|
+
selectorKind: detectSelectorKind2(String(sample.value))
|
|
4429
|
+
};
|
|
4430
|
+
case "literal":
|
|
4431
|
+
return {
|
|
4432
|
+
type: "literal",
|
|
4433
|
+
value: sample.value,
|
|
4434
|
+
dataType: typeof sample.value === "number" ? "number" : typeof sample.value === "boolean" ? "boolean" : "string"
|
|
4435
|
+
};
|
|
4436
|
+
case "reference":
|
|
4437
|
+
return { type: "reference", value: String(sample.value) };
|
|
4438
|
+
case "expression":
|
|
4439
|
+
return { type: "expression", value: String(sample.value), raw: String(sample.value) };
|
|
4440
|
+
case "flag":
|
|
4441
|
+
return { type: "flag", value: sample.value };
|
|
4442
|
+
default:
|
|
4443
|
+
return { type: "literal", value: String(sample.value), dataType: "string" };
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
function detectSelectorKind2(selector) {
|
|
4447
|
+
if (selector.startsWith("#")) return "id";
|
|
4448
|
+
if (selector.startsWith(".")) return "class";
|
|
4449
|
+
if (selector.startsWith("[")) return "attribute";
|
|
4450
|
+
return "complex";
|
|
4451
|
+
}
|
|
4452
|
+
function estimateTokens(text) {
|
|
4453
|
+
return Math.ceil(text.length / 4);
|
|
4454
|
+
}
|
|
4455
|
+
function truncateToTokenBudget(sections, maxTokens) {
|
|
4456
|
+
const result = [];
|
|
4457
|
+
let budget = maxTokens;
|
|
4458
|
+
for (const section of sections) {
|
|
4459
|
+
if (section.approximateTokens <= budget) {
|
|
4460
|
+
result.push(section);
|
|
4461
|
+
budget -= section.approximateTokens;
|
|
4462
|
+
} else {
|
|
4463
|
+
const charBudget = Math.max(budget * 4, 40);
|
|
4464
|
+
const truncatedContent = section.content.slice(0, charBudget) + "\n\n*(truncated)*";
|
|
4465
|
+
result.push({
|
|
4466
|
+
...section,
|
|
4467
|
+
content: truncatedContent,
|
|
4468
|
+
approximateTokens: estimateTokens(truncatedContent)
|
|
4469
|
+
});
|
|
4470
|
+
break;
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
return result;
|
|
4474
|
+
}
|
|
4475
|
+
|
|
4476
|
+
// src/training/schema-synthesizer.ts
|
|
4477
|
+
function synthesizeFromSchemas(schemas, config) {
|
|
4478
|
+
const maxPerCommand = config.maxPairsPerCommand ?? 5;
|
|
4479
|
+
const minQuality = config.minQuality ?? 0.5;
|
|
4480
|
+
const shuffleRoles = config.shuffleRoles ?? true;
|
|
4481
|
+
const languages = config.languages ?? ["en"];
|
|
4482
|
+
const pairs = [];
|
|
4483
|
+
let idCounter = 0;
|
|
4484
|
+
for (const schema of schemas) {
|
|
4485
|
+
const combos = generateRoleCombinations2(schema);
|
|
4486
|
+
for (const language of languages) {
|
|
4487
|
+
let commandPairCount = 0;
|
|
4488
|
+
for (const combo of combos) {
|
|
4489
|
+
if (commandPairCount >= maxPerCommand) break;
|
|
4490
|
+
const pair = buildTrainingPair(
|
|
4491
|
+
schema,
|
|
4492
|
+
combo,
|
|
4493
|
+
language,
|
|
4494
|
+
config.domain,
|
|
4495
|
+
`${config.domain}-synth-${idCounter++}`
|
|
4496
|
+
);
|
|
4497
|
+
if (pair.quality >= minQuality) {
|
|
4498
|
+
pairs.push(pair);
|
|
4499
|
+
commandPairCount++;
|
|
4500
|
+
}
|
|
4501
|
+
if (shuffleRoles && combo.length >= 2 && commandPairCount < maxPerCommand) {
|
|
4502
|
+
const shuffled = [...combo].reverse();
|
|
4503
|
+
const shuffledPair = buildTrainingPair(
|
|
4504
|
+
schema,
|
|
4505
|
+
shuffled,
|
|
4506
|
+
language,
|
|
4507
|
+
config.domain,
|
|
4508
|
+
`${config.domain}-synth-${idCounter++}`
|
|
4509
|
+
);
|
|
4510
|
+
if (shuffledPair.quality >= minQuality) {
|
|
4511
|
+
pairs.push(shuffledPair);
|
|
4512
|
+
commandPairCount++;
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
}
|
|
4516
|
+
}
|
|
4517
|
+
}
|
|
4518
|
+
const metadata = {
|
|
4519
|
+
domain: config.domain,
|
|
4520
|
+
commandCount: schemas.length,
|
|
4521
|
+
pairCount: pairs.length,
|
|
4522
|
+
bySource: { synthetic: pairs.length },
|
|
4523
|
+
languages
|
|
4524
|
+
};
|
|
4525
|
+
return { pairs, metadata };
|
|
4526
|
+
}
|
|
4527
|
+
function generateRoleCombinations2(schema) {
|
|
4528
|
+
const required = schema.roles.filter((r) => r.required);
|
|
4529
|
+
const optional = schema.roles.filter((r) => !r.required);
|
|
4530
|
+
const combos = [];
|
|
4531
|
+
if (required.length > 0) {
|
|
4532
|
+
combos.push(required);
|
|
4533
|
+
}
|
|
4534
|
+
for (const opt of optional) {
|
|
4535
|
+
combos.push([...required, opt]);
|
|
4536
|
+
}
|
|
4537
|
+
if (optional.length >= 2) {
|
|
4538
|
+
combos.push([...required, ...optional]);
|
|
4539
|
+
}
|
|
4540
|
+
if (combos.length === 0) {
|
|
4541
|
+
combos.push(schema.roles.slice());
|
|
4542
|
+
}
|
|
4543
|
+
return combos;
|
|
4544
|
+
}
|
|
4545
|
+
function buildTrainingPair(schema, roles, language, domain, id) {
|
|
4546
|
+
const sampledRoles = /* @__PURE__ */ new Map();
|
|
4547
|
+
for (const role of roles) {
|
|
4548
|
+
sampledRoles.set(role.role, sampleValue2(role));
|
|
4549
|
+
}
|
|
4550
|
+
const nodeRoles = /* @__PURE__ */ new Map();
|
|
4551
|
+
for (const [name, sample] of sampledRoles) {
|
|
4552
|
+
nodeRoles.set(name, toSemanticValue2(sample));
|
|
4553
|
+
}
|
|
4554
|
+
const node = createCommandNode(schema.action, nodeRoles);
|
|
4555
|
+
const explicit = renderExplicit(node);
|
|
4556
|
+
const jsonRoles = {};
|
|
4557
|
+
for (const [name, sample] of sampledRoles) {
|
|
4558
|
+
jsonRoles[name] = sample;
|
|
4559
|
+
}
|
|
4560
|
+
const json = { action: schema.action, roles: jsonRoles };
|
|
4561
|
+
const natural = buildNaturalForm(schema, sampledRoles, language);
|
|
4562
|
+
const requiredCount = schema.roles.filter((r) => r.required).length;
|
|
4563
|
+
const filledRequired = roles.filter((r) => r.required).length;
|
|
4564
|
+
const quality = requiredCount > 0 ? filledRequired / requiredCount : 0.8;
|
|
4565
|
+
return {
|
|
4566
|
+
id,
|
|
4567
|
+
natural,
|
|
4568
|
+
language,
|
|
4569
|
+
explicit,
|
|
4570
|
+
json,
|
|
4571
|
+
domain,
|
|
4572
|
+
action: schema.action,
|
|
4573
|
+
source: "synthetic",
|
|
4574
|
+
quality
|
|
4575
|
+
};
|
|
4576
|
+
}
|
|
4577
|
+
function buildNaturalForm(schema, roles, language) {
|
|
4578
|
+
const parts = [schema.action];
|
|
4579
|
+
for (const roleSpec of schema.roles) {
|
|
4580
|
+
const sample = roles.get(roleSpec.role);
|
|
4581
|
+
if (!sample) continue;
|
|
4582
|
+
const marker = roleSpec.markerOverride?.[language];
|
|
4583
|
+
if (marker) {
|
|
4584
|
+
parts.push(marker);
|
|
4585
|
+
}
|
|
4586
|
+
parts.push(String(sample.value));
|
|
4587
|
+
}
|
|
4588
|
+
return parts.join(" ");
|
|
4589
|
+
}
|
|
4590
|
+
function sampleValue2(role) {
|
|
4591
|
+
const primaryType = role.expectedTypes[0] || "expression";
|
|
4592
|
+
switch (primaryType) {
|
|
4593
|
+
case "selector":
|
|
4594
|
+
return sampleSelector2(role.role);
|
|
4595
|
+
case "literal":
|
|
4596
|
+
return sampleLiteral2(role.role);
|
|
4597
|
+
case "reference":
|
|
4598
|
+
return { type: "reference", value: "me" };
|
|
4599
|
+
case "expression":
|
|
4600
|
+
return sampleExpression2(role.role);
|
|
4601
|
+
case "flag":
|
|
4602
|
+
return { type: "flag", value: true };
|
|
4603
|
+
default:
|
|
4604
|
+
return { type: "literal", value: "example" };
|
|
4605
|
+
}
|
|
4606
|
+
}
|
|
4607
|
+
function sampleSelector2(roleName) {
|
|
4608
|
+
const selectors = {
|
|
4609
|
+
patient: ".active",
|
|
4610
|
+
destination: "#output",
|
|
4611
|
+
source: "#input",
|
|
4612
|
+
target: "#target"
|
|
4613
|
+
};
|
|
4614
|
+
return { type: "selector", value: selectors[roleName] || `#${roleName}` };
|
|
4615
|
+
}
|
|
4616
|
+
function sampleLiteral2(roleName) {
|
|
4617
|
+
const literals = {
|
|
4618
|
+
duration: "30s",
|
|
4619
|
+
interval: "5s",
|
|
4620
|
+
delay: "500ms",
|
|
4621
|
+
timeout: "10s",
|
|
4622
|
+
quantity: 5,
|
|
4623
|
+
limit: 10,
|
|
4624
|
+
style: "json",
|
|
4625
|
+
manner: "rolling",
|
|
4626
|
+
format: "html"
|
|
4627
|
+
};
|
|
4628
|
+
return { type: "literal", value: literals[roleName] || roleName };
|
|
4629
|
+
}
|
|
4630
|
+
function sampleExpression2(roleName) {
|
|
4631
|
+
const expressions = {
|
|
4632
|
+
source: "/api/data",
|
|
4633
|
+
destination: "#output",
|
|
4634
|
+
patient: ".active",
|
|
4635
|
+
instrument: "toUpperCase",
|
|
4636
|
+
condition: "age > 18",
|
|
4637
|
+
style: "json",
|
|
4638
|
+
duration: "5s",
|
|
4639
|
+
url: "/api/users"
|
|
4640
|
+
};
|
|
4641
|
+
return { type: "expression", value: expressions[roleName] || `/api/${roleName}` };
|
|
4642
|
+
}
|
|
4643
|
+
function toSemanticValue2(sample) {
|
|
4644
|
+
switch (sample.type) {
|
|
4645
|
+
case "selector":
|
|
2127
4646
|
return {
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
metadata: {
|
|
2132
|
-
parser: "semantic",
|
|
2133
|
-
confidence: result.confidence
|
|
2134
|
-
}
|
|
4647
|
+
type: "selector",
|
|
4648
|
+
value: String(sample.value),
|
|
4649
|
+
selectorKind: detectSelectorKind3(String(sample.value))
|
|
2135
4650
|
};
|
|
2136
|
-
|
|
4651
|
+
case "literal":
|
|
2137
4652
|
return {
|
|
2138
|
-
|
|
2139
|
-
|
|
4653
|
+
type: "literal",
|
|
4654
|
+
value: sample.value,
|
|
4655
|
+
dataType: typeof sample.value === "number" ? "number" : typeof sample.value === "boolean" ? "boolean" : "string"
|
|
2140
4656
|
};
|
|
4657
|
+
case "reference":
|
|
4658
|
+
return { type: "reference", value: String(sample.value) };
|
|
4659
|
+
case "expression":
|
|
4660
|
+
return { type: "expression", value: String(sample.value), raw: String(sample.value) };
|
|
4661
|
+
case "flag":
|
|
4662
|
+
return { type: "flag", value: sample.value };
|
|
4663
|
+
default:
|
|
4664
|
+
return { type: "literal", value: String(sample.value), dataType: "string" };
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4667
|
+
function detectSelectorKind3(selector) {
|
|
4668
|
+
if (selector.startsWith("#")) return "id";
|
|
4669
|
+
if (selector.startsWith(".")) return "class";
|
|
4670
|
+
if (selector.startsWith("[")) return "attribute";
|
|
4671
|
+
return "complex";
|
|
4672
|
+
}
|
|
4673
|
+
|
|
4674
|
+
// src/feedback/feedback-formatter.ts
|
|
4675
|
+
function buildFeedback(input, inputFormat, diagnostics, schemaLookup, action) {
|
|
4676
|
+
const hasErrors = diagnostics.some((d) => d.severity === "error");
|
|
4677
|
+
const feedbackDiagnostics = diagnostics.map((d) => toFeedbackDiagnostic(d));
|
|
4678
|
+
const hints = generateHints(feedbackDiagnostics, schemaLookup, action);
|
|
4679
|
+
let schemaInfo;
|
|
4680
|
+
let correctedExample;
|
|
4681
|
+
if (action && schemaLookup) {
|
|
4682
|
+
const schema = schemaLookup.getSchema(action);
|
|
4683
|
+
if (schema) {
|
|
4684
|
+
schemaInfo = extractSchemaInfo(schema);
|
|
4685
|
+
if (hasErrors) {
|
|
4686
|
+
correctedExample = generateCorrectedExample(schema);
|
|
4687
|
+
}
|
|
2141
4688
|
}
|
|
2142
4689
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
4690
|
+
return {
|
|
4691
|
+
accepted: !hasErrors,
|
|
4692
|
+
input: { format: inputFormat, text: input },
|
|
4693
|
+
diagnostics: feedbackDiagnostics,
|
|
4694
|
+
hints,
|
|
4695
|
+
...schemaInfo && { schema: schemaInfo },
|
|
4696
|
+
...correctedExample && { correctedExample }
|
|
4697
|
+
};
|
|
4698
|
+
}
|
|
4699
|
+
function toFeedbackDiagnostic(d) {
|
|
4700
|
+
const fixType = classifyFixType(d);
|
|
4701
|
+
return {
|
|
4702
|
+
severity: d.severity,
|
|
4703
|
+
code: d.code || "unknown",
|
|
4704
|
+
message: d.message,
|
|
4705
|
+
...d.suggestions?.[0] && { suggestion: d.suggestions[0] },
|
|
4706
|
+
...fixType && { fixType }
|
|
4707
|
+
};
|
|
4708
|
+
}
|
|
4709
|
+
function classifyFixType(d) {
|
|
4710
|
+
const code = (d.code || "").toLowerCase();
|
|
4711
|
+
const msg = d.message.toLowerCase();
|
|
4712
|
+
if (code.includes("missing-role") || code.includes("missing_role") || msg.includes("required role") || msg.includes("missing role")) {
|
|
4713
|
+
return "missing_role";
|
|
2145
4714
|
}
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
4715
|
+
if (code.includes("invalid-type") || code.includes("invalid_type") || code.includes("type-mismatch") || msg.includes("invalid type") || msg.includes("expected type")) {
|
|
4716
|
+
return "invalid_type";
|
|
4717
|
+
}
|
|
4718
|
+
if (code.includes("unknown-command") || code.includes("unknown_command") || msg.includes("unknown command") || msg.includes("not recognized") && msg.includes("command")) {
|
|
4719
|
+
return "unknown_command";
|
|
4720
|
+
}
|
|
4721
|
+
if (code.includes("unknown-role") || code.includes("unknown_role") || msg.includes("unknown role") || msg.includes("not recognized") && msg.includes("role")) {
|
|
4722
|
+
return "unknown_role";
|
|
4723
|
+
}
|
|
4724
|
+
if (code.includes("parse") || code.includes("syntax") || msg.includes("syntax error") || msg.includes("parse error") || msg.includes("unexpected")) {
|
|
4725
|
+
return "syntax_error";
|
|
4726
|
+
}
|
|
4727
|
+
return void 0;
|
|
4728
|
+
}
|
|
4729
|
+
function generateHints(diagnostics, schemaLookup, action) {
|
|
4730
|
+
const hints = [];
|
|
4731
|
+
const schema = action && schemaLookup ? schemaLookup.getSchema(action) : void 0;
|
|
4732
|
+
for (const d of diagnostics) {
|
|
4733
|
+
if (d.severity !== "error") continue;
|
|
4734
|
+
switch (d.fixType) {
|
|
4735
|
+
case "missing_role": {
|
|
4736
|
+
if (schema) {
|
|
4737
|
+
const required = schema.roles.filter((r) => r.required).map((r) => `'${r.role}' (${r.description}, type: ${r.expectedTypes.join("|")})`).join(", ");
|
|
4738
|
+
hints.push(`Required roles for '${action}': ${required}`);
|
|
4739
|
+
} else {
|
|
4740
|
+
hints.push("Check that all required roles are present.");
|
|
4741
|
+
}
|
|
4742
|
+
break;
|
|
4743
|
+
}
|
|
4744
|
+
case "unknown_command": {
|
|
4745
|
+
hints.push(
|
|
4746
|
+
`Command not recognized. Ensure you're using a valid command from the domain schema.`
|
|
4747
|
+
);
|
|
4748
|
+
break;
|
|
4749
|
+
}
|
|
4750
|
+
case "unknown_role": {
|
|
4751
|
+
if (schema) {
|
|
4752
|
+
const validRoles = schema.roles.map((r) => `'${r.role}'`).join(", ");
|
|
4753
|
+
hints.push(`Valid roles for '${action}': ${validRoles}`);
|
|
4754
|
+
} else {
|
|
4755
|
+
hints.push("Use only roles defined in the command schema.");
|
|
4756
|
+
}
|
|
4757
|
+
break;
|
|
4758
|
+
}
|
|
4759
|
+
case "invalid_type": {
|
|
4760
|
+
hints.push(
|
|
4761
|
+
"Check value types: selectors start with #.[@*, strings use quotes, numbers are plain digits."
|
|
4762
|
+
);
|
|
4763
|
+
break;
|
|
4764
|
+
}
|
|
4765
|
+
case "syntax_error": {
|
|
4766
|
+
hints.push("Ensure bracket syntax: [command role:value ...]. No spaces around the colon.");
|
|
4767
|
+
break;
|
|
4768
|
+
}
|
|
4769
|
+
default: {
|
|
4770
|
+
if (d.suggestion) {
|
|
4771
|
+
hints.push(d.suggestion);
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
2153
4774
|
}
|
|
2154
|
-
translations[lang.code] = keywords;
|
|
2155
4775
|
}
|
|
2156
|
-
return new
|
|
4776
|
+
return [...new Set(hints)];
|
|
2157
4777
|
}
|
|
2158
|
-
function
|
|
2159
|
-
const
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
4778
|
+
function extractSchemaInfo(schema) {
|
|
4779
|
+
const requiredRoles = [];
|
|
4780
|
+
const optionalRoles = [];
|
|
4781
|
+
const roleDescriptions = {};
|
|
4782
|
+
for (const role of schema.roles) {
|
|
4783
|
+
if (role.required) {
|
|
4784
|
+
requiredRoles.push(role.role);
|
|
4785
|
+
} else {
|
|
4786
|
+
optionalRoles.push(role.role);
|
|
2163
4787
|
}
|
|
4788
|
+
roleDescriptions[role.role] = role.description;
|
|
2164
4789
|
}
|
|
2165
|
-
return
|
|
4790
|
+
return {
|
|
4791
|
+
action: schema.action,
|
|
4792
|
+
requiredRoles,
|
|
4793
|
+
optionalRoles,
|
|
4794
|
+
roleDescriptions
|
|
4795
|
+
};
|
|
2166
4796
|
}
|
|
2167
|
-
function
|
|
2168
|
-
const
|
|
2169
|
-
const
|
|
2170
|
-
const
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
4797
|
+
function generateCorrectedExample(schema) {
|
|
4798
|
+
const roles = /* @__PURE__ */ new Map();
|
|
4799
|
+
const jsonRoles = {};
|
|
4800
|
+
for (const role of schema.roles) {
|
|
4801
|
+
if (!role.required) continue;
|
|
4802
|
+
const sample = sampleDefaultValue(role.role, role.expectedTypes[0] || "expression");
|
|
4803
|
+
roles.set(role.role, toSemanticValue3(sample));
|
|
4804
|
+
jsonRoles[role.role] = sample;
|
|
4805
|
+
}
|
|
4806
|
+
const node = createCommandNode(schema.action, roles);
|
|
4807
|
+
const explicit = renderExplicit(node);
|
|
4808
|
+
const json = { action: schema.action, roles: jsonRoles };
|
|
4809
|
+
return { explicit, json };
|
|
4810
|
+
}
|
|
4811
|
+
function sampleDefaultValue(roleName, expectedType) {
|
|
4812
|
+
switch (expectedType) {
|
|
4813
|
+
case "selector":
|
|
4814
|
+
return { type: "selector", value: `#${roleName}` };
|
|
4815
|
+
case "expression":
|
|
4816
|
+
return { type: "expression", value: `<${roleName}-value>` };
|
|
4817
|
+
case "literal":
|
|
4818
|
+
return { type: "literal", value: `<${roleName}-value>` };
|
|
4819
|
+
case "reference":
|
|
4820
|
+
return { type: "reference", value: "me" };
|
|
4821
|
+
default:
|
|
4822
|
+
return { type: "expression", value: `<${roleName}-value>` };
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
function toSemanticValue3(sample) {
|
|
4826
|
+
switch (sample.type) {
|
|
4827
|
+
case "selector":
|
|
4828
|
+
return { type: "selector", value: sample.value, selectorKind: "id" };
|
|
4829
|
+
case "expression":
|
|
4830
|
+
return { type: "expression", value: sample.value, raw: sample.value };
|
|
4831
|
+
case "literal":
|
|
4832
|
+
return { type: "literal", value: sample.value, dataType: "string" };
|
|
4833
|
+
case "reference":
|
|
4834
|
+
return { type: "reference", value: sample.value };
|
|
4835
|
+
default:
|
|
4836
|
+
return { type: "literal", value: sample.value, dataType: "string" };
|
|
4837
|
+
}
|
|
2176
4838
|
}
|
|
2177
4839
|
|
|
2178
4840
|
// src/api/domain-registry.ts
|
|
@@ -2192,6 +4854,17 @@ var DomainRegistry = class {
|
|
|
2192
4854
|
}
|
|
2193
4855
|
this.descriptors.set(descriptor.name, descriptor);
|
|
2194
4856
|
}
|
|
4857
|
+
/**
|
|
4858
|
+
* Attach schemas to an already-registered domain.
|
|
4859
|
+
* Useful for lazy loading schemas after initial registration.
|
|
4860
|
+
*/
|
|
4861
|
+
setSchemas(name, schemas) {
|
|
4862
|
+
const existing = this.descriptors.get(name);
|
|
4863
|
+
if (!existing) {
|
|
4864
|
+
throw new Error(`Domain not registered: ${name}`);
|
|
4865
|
+
}
|
|
4866
|
+
this.descriptors.set(name, { ...existing, schemas });
|
|
4867
|
+
}
|
|
2195
4868
|
/**
|
|
2196
4869
|
* Get all registered domain names.
|
|
2197
4870
|
*/
|
|
@@ -2261,6 +4934,71 @@ var DomainRegistry = class {
|
|
|
2261
4934
|
return this.getDSL(descriptor);
|
|
2262
4935
|
}
|
|
2263
4936
|
// ---------------------------------------------------------------------------
|
|
4937
|
+
// LLM ↔ LSE Convenience Methods
|
|
4938
|
+
// ---------------------------------------------------------------------------
|
|
4939
|
+
/**
|
|
4940
|
+
* Generate an LLM system prompt for a domain from its schemas.
|
|
4941
|
+
* Returns null if the domain is not registered or has no schemas.
|
|
4942
|
+
*/
|
|
4943
|
+
generatePrompt(domain, config) {
|
|
4944
|
+
const descriptor = this.descriptors.get(domain);
|
|
4945
|
+
if (!descriptor?.schemas || descriptor.schemas.length === 0) return null;
|
|
4946
|
+
return generatePrompt({
|
|
4947
|
+
domain,
|
|
4948
|
+
description: descriptor.description,
|
|
4949
|
+
schemas: descriptor.schemas,
|
|
4950
|
+
...config
|
|
4951
|
+
});
|
|
4952
|
+
}
|
|
4953
|
+
/**
|
|
4954
|
+
* Synthesize training data for a domain from its schemas.
|
|
4955
|
+
* Returns null if the domain is not registered or has no schemas.
|
|
4956
|
+
*/
|
|
4957
|
+
generateTrainingData(domain, config) {
|
|
4958
|
+
const descriptor = this.descriptors.get(domain);
|
|
4959
|
+
if (!descriptor?.schemas || descriptor.schemas.length === 0) return null;
|
|
4960
|
+
return synthesizeFromSchemas(descriptor.schemas, {
|
|
4961
|
+
domain,
|
|
4962
|
+
...config
|
|
4963
|
+
});
|
|
4964
|
+
}
|
|
4965
|
+
/**
|
|
4966
|
+
* Build structured feedback for a failed LSE input in a domain.
|
|
4967
|
+
* Returns null if the domain is not registered or has no schemas.
|
|
4968
|
+
*/
|
|
4969
|
+
buildFeedback(domain, input, inputFormat, diagnostics) {
|
|
4970
|
+
const descriptor = this.descriptors.get(domain);
|
|
4971
|
+
if (!descriptor?.schemas || descriptor.schemas.length === 0) return null;
|
|
4972
|
+
const schemaLookup = this.createSchemaLookup(descriptor.schemas);
|
|
4973
|
+
let action;
|
|
4974
|
+
try {
|
|
4975
|
+
if (inputFormat === "explicit") {
|
|
4976
|
+
const node = parseExplicit(input, { schemaLookup });
|
|
4977
|
+
action = node.action;
|
|
4978
|
+
}
|
|
4979
|
+
} catch {
|
|
4980
|
+
}
|
|
4981
|
+
return buildFeedback(input, inputFormat, diagnostics, schemaLookup, action);
|
|
4982
|
+
}
|
|
4983
|
+
/**
|
|
4984
|
+
* Get schemas for a domain (if available).
|
|
4985
|
+
*/
|
|
4986
|
+
getSchemas(domain) {
|
|
4987
|
+
const descriptor = this.descriptors.get(domain);
|
|
4988
|
+
if (!descriptor?.schemas) return null;
|
|
4989
|
+
return descriptor.schemas;
|
|
4990
|
+
}
|
|
4991
|
+
/**
|
|
4992
|
+
* Create a SchemaLookup from a domain's schemas.
|
|
4993
|
+
*/
|
|
4994
|
+
createSchemaLookup(schemas) {
|
|
4995
|
+
const map = /* @__PURE__ */ new Map();
|
|
4996
|
+
for (const s of schemas) {
|
|
4997
|
+
map.set(s.action, s);
|
|
4998
|
+
}
|
|
4999
|
+
return { getSchema: (action) => map.get(action) };
|
|
5000
|
+
}
|
|
5001
|
+
// ---------------------------------------------------------------------------
|
|
2264
5002
|
// Private: DSL/renderer lifecycle
|
|
2265
5003
|
// ---------------------------------------------------------------------------
|
|
2266
5004
|
async getDSL(descriptor) {
|
|
@@ -2360,7 +5098,7 @@ function generateToolDefinitions(desc) {
|
|
|
2360
5098
|
case "parse":
|
|
2361
5099
|
tools.push({
|
|
2362
5100
|
name: `parse_${desc.name}`,
|
|
2363
|
-
description: `Parse a ${desc.description} input into a semantic representation. Supports: ${langList}.`,
|
|
5101
|
+
description: `Parse a ${desc.description} input into a semantic representation (action, roles, confidence). Supports: ${langList}.`,
|
|
2364
5102
|
inputSchema: {
|
|
2365
5103
|
type: "object",
|
|
2366
5104
|
properties: {
|
|
@@ -2381,7 +5119,7 @@ function generateToolDefinitions(desc) {
|
|
|
2381
5119
|
case "compile":
|
|
2382
5120
|
tools.push({
|
|
2383
5121
|
name: `compile_${desc.name}`,
|
|
2384
|
-
description: `Compile a ${desc.description} input to target code. Supports: ${langList}.`,
|
|
5122
|
+
description: `Compile a ${desc.description} input to ${desc.outputDescription ?? "target code"}. Supports: ${langList}.`,
|
|
2385
5123
|
inputSchema: {
|
|
2386
5124
|
type: "object",
|
|
2387
5125
|
properties: {
|
|
@@ -2402,7 +5140,7 @@ function generateToolDefinitions(desc) {
|
|
|
2402
5140
|
case "validate":
|
|
2403
5141
|
tools.push({
|
|
2404
5142
|
name: `validate_${desc.name}`,
|
|
2405
|
-
description: `
|
|
5143
|
+
description: `Check ${desc.description} syntax for errors. Returns {valid, errors[]}. Supports: ${langList}.`,
|
|
2406
5144
|
inputSchema: {
|
|
2407
5145
|
type: "object",
|
|
2408
5146
|
properties: {
|
|
@@ -2423,7 +5161,7 @@ function generateToolDefinitions(desc) {
|
|
|
2423
5161
|
case "translate":
|
|
2424
5162
|
tools.push({
|
|
2425
5163
|
name: `translate_${desc.name}`,
|
|
2426
|
-
description: `Translate ${desc.description} input between natural languages
|
|
5164
|
+
description: `Translate ${desc.description} input between natural languages (requires 'from' and 'to' codes). Supports: ${langList}.`,
|
|
2427
5165
|
inputSchema: {
|
|
2428
5166
|
type: "object",
|
|
2429
5167
|
properties: {
|
|
@@ -2489,9 +5227,9 @@ var CrossDomainDispatcher = class {
|
|
|
2489
5227
|
if (domainNames.length === 0) return null;
|
|
2490
5228
|
const candidates = [];
|
|
2491
5229
|
for (const name of domainNames) {
|
|
2492
|
-
const dsl = await this.registry.getDSLForDomain(name);
|
|
2493
|
-
if (!dsl) continue;
|
|
2494
5230
|
try {
|
|
5231
|
+
const dsl = await this.registry.getDSLForDomain(name);
|
|
5232
|
+
if (!dsl) continue;
|
|
2495
5233
|
const { node, confidence } = dsl.parseWithConfidence(input, language);
|
|
2496
5234
|
if (confidence >= this.minConfidence) {
|
|
2497
5235
|
candidates.push({ domain: name, node, confidence, dsl });
|
|
@@ -2500,10 +5238,16 @@ var CrossDomainDispatcher = class {
|
|
|
2500
5238
|
}
|
|
2501
5239
|
}
|
|
2502
5240
|
if (candidates.length === 0) return null;
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
5241
|
+
if (this.priority.length > 0) {
|
|
5242
|
+
candidates.sort((a, b) => {
|
|
5243
|
+
const pa = this.getPriorityIndex(a.domain);
|
|
5244
|
+
const pb = this.getPriorityIndex(b.domain);
|
|
5245
|
+
if (pa !== pb) return pa - pb;
|
|
5246
|
+
return b.confidence - a.confidence;
|
|
5247
|
+
});
|
|
5248
|
+
} else {
|
|
5249
|
+
candidates.sort((a, b) => b.confidence - a.confidence);
|
|
5250
|
+
}
|
|
2507
5251
|
return candidates[0];
|
|
2508
5252
|
}
|
|
2509
5253
|
/**
|
|
@@ -2547,6 +5291,40 @@ var CrossDomainDispatcher = class {
|
|
|
2547
5291
|
const result = detected.dsl.compile(input, language);
|
|
2548
5292
|
return { ...result, domain: detected.domain };
|
|
2549
5293
|
}
|
|
5294
|
+
/**
|
|
5295
|
+
* Compile multi-line input where each line may belong to a different domain.
|
|
5296
|
+
* Detects the domain for each line and compiles it, returning per-line results.
|
|
5297
|
+
* Empty lines and comment lines are skipped.
|
|
5298
|
+
*/
|
|
5299
|
+
async compileComposite(input, language = "en") {
|
|
5300
|
+
const lines = input.split("\n");
|
|
5301
|
+
const statements = [];
|
|
5302
|
+
const errors = [];
|
|
5303
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5304
|
+
const line = lines[i];
|
|
5305
|
+
if (isSkippableLine(line)) continue;
|
|
5306
|
+
const trimmed = line.trim();
|
|
5307
|
+
const detected = await this.detect(trimmed, language);
|
|
5308
|
+
if (detected) {
|
|
5309
|
+
const result = detected.dsl.compile(trimmed, language);
|
|
5310
|
+
statements.push({
|
|
5311
|
+
line: i + 1,
|
|
5312
|
+
input: trimmed,
|
|
5313
|
+
domain: detected.domain,
|
|
5314
|
+
ok: result.ok,
|
|
5315
|
+
code: result.code,
|
|
5316
|
+
errors: result.errors
|
|
5317
|
+
});
|
|
5318
|
+
} else {
|
|
5319
|
+
errors.push({
|
|
5320
|
+
line: i + 1,
|
|
5321
|
+
input: trimmed,
|
|
5322
|
+
message: "No domain matched this input"
|
|
5323
|
+
});
|
|
5324
|
+
}
|
|
5325
|
+
}
|
|
5326
|
+
return { statements, errors };
|
|
5327
|
+
}
|
|
2550
5328
|
/**
|
|
2551
5329
|
* Validate input against all domains, returning the best match.
|
|
2552
5330
|
*/
|
|
@@ -2828,6 +5606,30 @@ ${body}
|
|
|
2828
5606
|
}
|
|
2829
5607
|
};
|
|
2830
5608
|
|
|
5609
|
+
// src/aot/registry-bridge.ts
|
|
5610
|
+
async function registryToAOTBackends(registry) {
|
|
5611
|
+
const backends = [];
|
|
5612
|
+
for (const name of registry.getDomainNames()) {
|
|
5613
|
+
const descriptor = registry.getDescriptor(name);
|
|
5614
|
+
if (!descriptor?.scanConfig) continue;
|
|
5615
|
+
const dsl = await registry.getDSLForDomain(name);
|
|
5616
|
+
if (!dsl) continue;
|
|
5617
|
+
const scanConfig = {
|
|
5618
|
+
domain: name,
|
|
5619
|
+
attributes: [...descriptor.scanConfig.attributes],
|
|
5620
|
+
...descriptor.scanConfig.scriptTypes ? { scriptTypes: [...descriptor.scanConfig.scriptTypes] } : {},
|
|
5621
|
+
defaultLanguage: descriptor.scanConfig.defaultLanguage ?? "en"
|
|
5622
|
+
};
|
|
5623
|
+
const codeGenerator = {
|
|
5624
|
+
generate(node) {
|
|
5625
|
+
return JSON.stringify({ action: node.action, roles: Object.fromEntries(node.roles) });
|
|
5626
|
+
}
|
|
5627
|
+
};
|
|
5628
|
+
backends.push({ domain: name, dsl, codeGenerator, scanConfig });
|
|
5629
|
+
}
|
|
5630
|
+
return backends;
|
|
5631
|
+
}
|
|
5632
|
+
|
|
2831
5633
|
// src/schema/command-schema.ts
|
|
2832
5634
|
function defineCommand(schema) {
|
|
2833
5635
|
return {
|
|
@@ -4119,6 +6921,295 @@ function normalized(stem, confidence, metadata) {
|
|
|
4119
6921
|
return { stem, confidence };
|
|
4120
6922
|
}
|
|
4121
6923
|
|
|
6924
|
+
// src/core/tokenization/morphology/base-normalizer.ts
|
|
6925
|
+
var BaseMorphologicalNormalizer = class {
|
|
6926
|
+
constructor(config) {
|
|
6927
|
+
this.language = config.language;
|
|
6928
|
+
this.config = {
|
|
6929
|
+
minWordLength: 3,
|
|
6930
|
+
minStemLength: 2,
|
|
6931
|
+
...config
|
|
6932
|
+
};
|
|
6933
|
+
}
|
|
6934
|
+
/**
|
|
6935
|
+
* Standard normalization pipeline. Override for custom behavior.
|
|
6936
|
+
*/
|
|
6937
|
+
normalize(word) {
|
|
6938
|
+
const lower = word.toLowerCase();
|
|
6939
|
+
if (this.isAlreadyNormalized(lower)) {
|
|
6940
|
+
return noChange(word);
|
|
6941
|
+
}
|
|
6942
|
+
if (this.config.reflexiveSuffixes) {
|
|
6943
|
+
const reflexive = this.tryReflexiveNormalization(lower);
|
|
6944
|
+
if (reflexive) return reflexive;
|
|
6945
|
+
}
|
|
6946
|
+
if (this.config.endings) {
|
|
6947
|
+
const conjugation = this.tryConjugationEndings(lower);
|
|
6948
|
+
if (conjugation) return conjugation;
|
|
6949
|
+
}
|
|
6950
|
+
if (this.config.suffixRules) {
|
|
6951
|
+
const suffix = this.trySuffixRules(lower);
|
|
6952
|
+
if (suffix) return suffix;
|
|
6953
|
+
}
|
|
6954
|
+
if (this.config.prefixRules) {
|
|
6955
|
+
const prefix = this.tryPrefixRules(lower);
|
|
6956
|
+
if (prefix) return prefix;
|
|
6957
|
+
}
|
|
6958
|
+
return noChange(word);
|
|
6959
|
+
}
|
|
6960
|
+
/**
|
|
6961
|
+
* Check if word is already in dictionary form (e.g., ends in -ar/-er/-ir).
|
|
6962
|
+
* Override for language-specific checks.
|
|
6963
|
+
*/
|
|
6964
|
+
isAlreadyNormalized(word) {
|
|
6965
|
+
if (this.config.infinitiveEndings) {
|
|
6966
|
+
return this.config.infinitiveEndings.some((e) => word.endsWith(e));
|
|
6967
|
+
}
|
|
6968
|
+
return false;
|
|
6969
|
+
}
|
|
6970
|
+
/**
|
|
6971
|
+
* Try to strip reflexive suffixes and normalize the remainder.
|
|
6972
|
+
* Common in Romance languages (Spanish, Portuguese, French).
|
|
6973
|
+
*/
|
|
6974
|
+
tryReflexiveNormalization(word) {
|
|
6975
|
+
const suffixes = this.config.reflexiveSuffixes;
|
|
6976
|
+
if (!suffixes) return null;
|
|
6977
|
+
for (const suffix of suffixes) {
|
|
6978
|
+
if (!word.endsWith(suffix)) continue;
|
|
6979
|
+
const remainder = word.slice(0, -suffix.length);
|
|
6980
|
+
if (this.isAlreadyNormalized(remainder)) {
|
|
6981
|
+
return normalized(remainder, 0.88, {
|
|
6982
|
+
removedSuffixes: [suffix],
|
|
6983
|
+
conjugationType: "reflexive"
|
|
6984
|
+
});
|
|
6985
|
+
}
|
|
6986
|
+
const inner = this.tryConjugationEndings(remainder) || this.trySuffixRules(remainder);
|
|
6987
|
+
if (inner && inner.stem !== remainder) {
|
|
6988
|
+
return normalized(inner.stem, inner.confidence * 0.95, {
|
|
6989
|
+
removedSuffixes: [suffix, ...inner.metadata?.removedSuffixes || []],
|
|
6990
|
+
conjugationType: "reflexive"
|
|
6991
|
+
});
|
|
6992
|
+
}
|
|
6993
|
+
}
|
|
6994
|
+
return null;
|
|
6995
|
+
}
|
|
6996
|
+
/**
|
|
6997
|
+
* Try conjugation endings (verb class endings like -ar/-er/-ir patterns).
|
|
6998
|
+
* Endings must be pre-sorted longest-first.
|
|
6999
|
+
*/
|
|
7000
|
+
tryConjugationEndings(word) {
|
|
7001
|
+
const endings = this.config.endings;
|
|
7002
|
+
if (!endings) return null;
|
|
7003
|
+
const minStem = this.config.minStemLength ?? 2;
|
|
7004
|
+
for (const rule of endings) {
|
|
7005
|
+
if (!word.endsWith(rule.ending)) continue;
|
|
7006
|
+
const stemBase = word.slice(0, -rule.ending.length);
|
|
7007
|
+
if (stemBase.length < minStem) continue;
|
|
7008
|
+
const infinitive = stemBase + rule.stem;
|
|
7009
|
+
return normalized(infinitive, rule.confidence, {
|
|
7010
|
+
removedSuffixes: [rule.ending],
|
|
7011
|
+
conjugationType: rule.type
|
|
7012
|
+
});
|
|
7013
|
+
}
|
|
7014
|
+
return null;
|
|
7015
|
+
}
|
|
7016
|
+
/**
|
|
7017
|
+
* Try SuffixRule-style normalization.
|
|
7018
|
+
* Rules must be pre-sorted longest-first.
|
|
7019
|
+
*/
|
|
7020
|
+
trySuffixRules(word) {
|
|
7021
|
+
const rules = this.config.suffixRules;
|
|
7022
|
+
if (!rules) return null;
|
|
7023
|
+
const defaultMinStem = this.config.minStemLength ?? 2;
|
|
7024
|
+
for (const rule of rules) {
|
|
7025
|
+
if (!word.endsWith(rule.pattern)) continue;
|
|
7026
|
+
const stem = word.slice(0, -rule.pattern.length);
|
|
7027
|
+
const minStem = rule.minStemLength ?? defaultMinStem;
|
|
7028
|
+
if (stem.length < minStem) continue;
|
|
7029
|
+
const result = stem + (rule.replacement || "");
|
|
7030
|
+
return normalized(result, rule.confidence, {
|
|
7031
|
+
removedSuffixes: [rule.pattern],
|
|
7032
|
+
...rule.conjugationType && { conjugationType: rule.conjugationType }
|
|
7033
|
+
});
|
|
7034
|
+
}
|
|
7035
|
+
return null;
|
|
7036
|
+
}
|
|
7037
|
+
/**
|
|
7038
|
+
* Try PrefixRule-style normalization.
|
|
7039
|
+
*/
|
|
7040
|
+
tryPrefixRules(word) {
|
|
7041
|
+
const rules = this.config.prefixRules;
|
|
7042
|
+
if (!rules) return null;
|
|
7043
|
+
for (const rule of rules) {
|
|
7044
|
+
if (!word.startsWith(rule.pattern)) continue;
|
|
7045
|
+
const remainder = word.slice(rule.pattern.length);
|
|
7046
|
+
const minRemaining = rule.minRemaining ?? this.config.minStemLength ?? 2;
|
|
7047
|
+
if (remainder.length < minRemaining) continue;
|
|
7048
|
+
return normalized(remainder, 1 - rule.confidencePenalty, {
|
|
7049
|
+
removedPrefixes: [rule.pattern]
|
|
7050
|
+
});
|
|
7051
|
+
}
|
|
7052
|
+
return null;
|
|
7053
|
+
}
|
|
7054
|
+
};
|
|
7055
|
+
|
|
7056
|
+
// src/training/jsonl-writer.ts
|
|
7057
|
+
function toJSONL(pairs) {
|
|
7058
|
+
return pairs.map((pair) => JSON.stringify(toJSONLRow(pair))).join("\n");
|
|
7059
|
+
}
|
|
7060
|
+
function toJSONLRow(pair) {
|
|
7061
|
+
return {
|
|
7062
|
+
id: pair.id,
|
|
7063
|
+
prompt: pair.natural,
|
|
7064
|
+
completion: pair.explicit,
|
|
7065
|
+
metadata: {
|
|
7066
|
+
domain: pair.domain,
|
|
7067
|
+
action: pair.action,
|
|
7068
|
+
language: pair.language,
|
|
7069
|
+
source: pair.source,
|
|
7070
|
+
quality: pair.quality,
|
|
7071
|
+
...pair.confidence !== void 0 && { confidence: pair.confidence }
|
|
7072
|
+
}
|
|
7073
|
+
};
|
|
7074
|
+
}
|
|
7075
|
+
function parseJSONL(text) {
|
|
7076
|
+
return text.split("\n").filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
|
|
7077
|
+
}
|
|
7078
|
+
|
|
7079
|
+
// src/feedback/confidence-gate.ts
|
|
7080
|
+
function needsDisambiguation(confidence, low = 0.5, high = 0.7) {
|
|
7081
|
+
return confidence >= low && confidence < high;
|
|
7082
|
+
}
|
|
7083
|
+
function buildDisambiguation(input, candidates) {
|
|
7084
|
+
const sortedCandidates = [...candidates].sort((a, b) => b.confidence - a.confidence);
|
|
7085
|
+
const disambiguationCandidates = sortedCandidates.map((c, i) => ({
|
|
7086
|
+
action: c.action,
|
|
7087
|
+
confidence: c.confidence,
|
|
7088
|
+
explicit: renderExplicit(c.node),
|
|
7089
|
+
description: c.description || `${c.action} (option ${String.fromCharCode(97 + i)})`
|
|
7090
|
+
}));
|
|
7091
|
+
const optionLines = disambiguationCandidates.map(
|
|
7092
|
+
(c, i) => ` (${String.fromCharCode(97 + i)}) ${c.explicit} \u2014 ${c.description} (conf: ${c.confidence.toFixed(2)})`
|
|
7093
|
+
);
|
|
7094
|
+
const question = [
|
|
7095
|
+
`Your input parsed with confidence ${sortedCandidates[0]?.confidence.toFixed(2) ?? "?"}.`,
|
|
7096
|
+
...optionLines,
|
|
7097
|
+
"Which did you mean?"
|
|
7098
|
+
].join("\n");
|
|
7099
|
+
return {
|
|
7100
|
+
input,
|
|
7101
|
+
candidates: disambiguationCandidates,
|
|
7102
|
+
question
|
|
7103
|
+
};
|
|
7104
|
+
}
|
|
7105
|
+
|
|
7106
|
+
// src/feedback/pattern-tracker.ts
|
|
7107
|
+
var PatternTracker = class {
|
|
7108
|
+
constructor(maxEvents = 1e3) {
|
|
7109
|
+
this.events = [];
|
|
7110
|
+
this.maxEvents = maxEvents;
|
|
7111
|
+
}
|
|
7112
|
+
/**
|
|
7113
|
+
* Record a pattern event.
|
|
7114
|
+
*/
|
|
7115
|
+
record(event) {
|
|
7116
|
+
this.events.push(event);
|
|
7117
|
+
if (this.events.length > this.maxEvents) {
|
|
7118
|
+
this.events = this.events.slice(this.events.length - this.maxEvents);
|
|
7119
|
+
}
|
|
7120
|
+
}
|
|
7121
|
+
/**
|
|
7122
|
+
* Get hit rate by command action.
|
|
7123
|
+
*/
|
|
7124
|
+
getHitRateByCommand() {
|
|
7125
|
+
return this.computeHitRate((e) => e.action);
|
|
7126
|
+
}
|
|
7127
|
+
/**
|
|
7128
|
+
* Get hit rate by language.
|
|
7129
|
+
*/
|
|
7130
|
+
getHitRateByLanguage() {
|
|
7131
|
+
return this.computeHitRate((e) => e.language);
|
|
7132
|
+
}
|
|
7133
|
+
/**
|
|
7134
|
+
* Get the most frequent failure diagnostic codes.
|
|
7135
|
+
*/
|
|
7136
|
+
getTopFailures(limit = 10) {
|
|
7137
|
+
const codeMap = /* @__PURE__ */ new Map();
|
|
7138
|
+
for (const event of this.events) {
|
|
7139
|
+
if (event.outcome !== "rejected" || !event.diagnosticCodes) continue;
|
|
7140
|
+
for (const code of event.diagnosticCodes) {
|
|
7141
|
+
const entry = codeMap.get(code) || { count: 0, actions: /* @__PURE__ */ new Set() };
|
|
7142
|
+
entry.count++;
|
|
7143
|
+
entry.actions.add(event.action);
|
|
7144
|
+
codeMap.set(code, entry);
|
|
7145
|
+
}
|
|
7146
|
+
}
|
|
7147
|
+
return Array.from(codeMap.entries()).map(([code, { count, actions }]) => ({
|
|
7148
|
+
code,
|
|
7149
|
+
count,
|
|
7150
|
+
actions: Array.from(actions)
|
|
7151
|
+
})).sort((a, b) => b.count - a.count).slice(0, limit);
|
|
7152
|
+
}
|
|
7153
|
+
/**
|
|
7154
|
+
* Get a complete summary of tracking data.
|
|
7155
|
+
*/
|
|
7156
|
+
getSummary() {
|
|
7157
|
+
const byOutcome = {};
|
|
7158
|
+
for (const event of this.events) {
|
|
7159
|
+
byOutcome[event.outcome] = (byOutcome[event.outcome] || 0) + 1;
|
|
7160
|
+
}
|
|
7161
|
+
return {
|
|
7162
|
+
totalEvents: this.events.length,
|
|
7163
|
+
byOutcome,
|
|
7164
|
+
byCommand: this.getHitRateByCommand(),
|
|
7165
|
+
byLanguage: this.getHitRateByLanguage(),
|
|
7166
|
+
topFailures: this.getTopFailures()
|
|
7167
|
+
};
|
|
7168
|
+
}
|
|
7169
|
+
/**
|
|
7170
|
+
* Export all events as JSONL.
|
|
7171
|
+
*/
|
|
7172
|
+
exportJSONL() {
|
|
7173
|
+
return this.events.map((e) => JSON.stringify(e)).join("\n");
|
|
7174
|
+
}
|
|
7175
|
+
/**
|
|
7176
|
+
* Get the number of recorded events.
|
|
7177
|
+
*/
|
|
7178
|
+
get size() {
|
|
7179
|
+
return this.events.length;
|
|
7180
|
+
}
|
|
7181
|
+
/**
|
|
7182
|
+
* Clear all events.
|
|
7183
|
+
*/
|
|
7184
|
+
clear() {
|
|
7185
|
+
this.events = [];
|
|
7186
|
+
}
|
|
7187
|
+
// ---------------------------------------------------------------------------
|
|
7188
|
+
// Private
|
|
7189
|
+
// ---------------------------------------------------------------------------
|
|
7190
|
+
computeHitRate(keyFn) {
|
|
7191
|
+
const map = /* @__PURE__ */ new Map();
|
|
7192
|
+
for (const event of this.events) {
|
|
7193
|
+
const key = keyFn(event);
|
|
7194
|
+
const entry = map.get(key) || { attempts: 0, successes: 0 };
|
|
7195
|
+
entry.attempts++;
|
|
7196
|
+
if (event.outcome === "accepted") {
|
|
7197
|
+
entry.successes++;
|
|
7198
|
+
}
|
|
7199
|
+
map.set(key, entry);
|
|
7200
|
+
}
|
|
7201
|
+
const result = {};
|
|
7202
|
+
for (const [key, { attempts, successes }] of map) {
|
|
7203
|
+
result[key] = {
|
|
7204
|
+
attempts,
|
|
7205
|
+
successes,
|
|
7206
|
+
rate: attempts > 0 ? successes / attempts : 0
|
|
7207
|
+
};
|
|
7208
|
+
}
|
|
7209
|
+
return result;
|
|
7210
|
+
}
|
|
7211
|
+
};
|
|
7212
|
+
|
|
4122
7213
|
// src/parsing/multi-statement.ts
|
|
4123
7214
|
function createMultiStatementParser(dsl, config) {
|
|
4124
7215
|
return new MultiStatementParserImpl(dsl, config);
|
|
@@ -4359,10 +7450,12 @@ function accumulateIndented(statements, config) {
|
|
|
4359
7450
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4360
7451
|
0 && (module.exports = {
|
|
4361
7452
|
AOTOrchestrator,
|
|
7453
|
+
BaseMorphologicalNormalizer,
|
|
4362
7454
|
BaseTokenizer,
|
|
4363
7455
|
CrossDomainDispatcher,
|
|
4364
7456
|
DEFAULT_OPERATORS,
|
|
4365
7457
|
DEFAULT_PUNCTUATION,
|
|
7458
|
+
DEFAULT_REFERENCES,
|
|
4366
7459
|
DomainAwareScanner,
|
|
4367
7460
|
DomainRegistry,
|
|
4368
7461
|
GrammarTransformer,
|
|
@@ -4374,24 +7467,31 @@ function accumulateIndented(statements, config) {
|
|
|
4374
7467
|
NumberExtractor,
|
|
4375
7468
|
OperatorExtractor,
|
|
4376
7469
|
PatternMatcher,
|
|
7470
|
+
PatternTracker,
|
|
4377
7471
|
PunctuationExtractor,
|
|
4378
7472
|
StringLiteralExtractor,
|
|
4379
7473
|
TokenStreamImpl,
|
|
4380
7474
|
UnicodeIdentifierExtractor,
|
|
4381
7475
|
WhitespaceExtractor,
|
|
4382
7476
|
accumulateBlocks,
|
|
7477
|
+
buildDisambiguation,
|
|
7478
|
+
buildFeedback,
|
|
4383
7479
|
buildPhrase,
|
|
4384
7480
|
buildTablesFromProfiles,
|
|
4385
7481
|
combineClassifiers,
|
|
7482
|
+
convertValue,
|
|
7483
|
+
createAsyncNode,
|
|
4386
7484
|
createCommandNode,
|
|
4387
7485
|
createCompoundNode,
|
|
4388
7486
|
createConditionalNode,
|
|
4389
7487
|
createDiagnosticCollector,
|
|
4390
7488
|
createEventHandlerNode,
|
|
4391
7489
|
createExpression,
|
|
7490
|
+
createFlag,
|
|
4392
7491
|
createLatinCharClassifiers,
|
|
4393
7492
|
createLiteral,
|
|
4394
7493
|
createLoopNode,
|
|
7494
|
+
createMatchNode,
|
|
4395
7495
|
createMultiStatementParser,
|
|
4396
7496
|
createMultilingualDSL,
|
|
4397
7497
|
createPosition,
|
|
@@ -4402,6 +7502,7 @@ function accumulateIndented(statements, config) {
|
|
|
4402
7502
|
createSimpleTokenizer,
|
|
4403
7503
|
createToken,
|
|
4404
7504
|
createTokenizerContext,
|
|
7505
|
+
createTryNode,
|
|
4405
7506
|
createUnicodeRangeClassifier,
|
|
4406
7507
|
defineCommand,
|
|
4407
7508
|
defineRole,
|
|
@@ -4413,9 +7514,15 @@ function accumulateIndented(statements, config) {
|
|
|
4413
7514
|
extractUrl,
|
|
4414
7515
|
extractValue,
|
|
4415
7516
|
filterBySeverity,
|
|
7517
|
+
fromEnvelopeJSON,
|
|
4416
7518
|
fromError,
|
|
7519
|
+
fromInterchangeNode,
|
|
7520
|
+
fromProtocolJSON,
|
|
7521
|
+
generateExamples,
|
|
4417
7522
|
generatePattern,
|
|
4418
7523
|
generatePatternVariants,
|
|
7524
|
+
generatePrompt,
|
|
7525
|
+
generateProtocolReference,
|
|
4419
7526
|
getAllPossessiveKeywords,
|
|
4420
7527
|
getDefaultExtractors,
|
|
4421
7528
|
getPossessiveReference,
|
|
@@ -4426,8 +7533,12 @@ function accumulateIndented(statements, config) {
|
|
|
4426
7533
|
isCSSPropertyRef,
|
|
4427
7534
|
isCSSSelector,
|
|
4428
7535
|
isClassName,
|
|
7536
|
+
isCompoundSyntax,
|
|
4429
7537
|
isContextAwareExtractor,
|
|
4430
7538
|
isDigit,
|
|
7539
|
+
isDocumentSyntax,
|
|
7540
|
+
isEnvelope,
|
|
7541
|
+
isExplicitSyntax,
|
|
4431
7542
|
isIdSelector,
|
|
4432
7543
|
isNumericValue,
|
|
4433
7544
|
isPossessiveKeyword,
|
|
@@ -4437,17 +7548,38 @@ function accumulateIndented(statements, config) {
|
|
|
4437
7548
|
isSelectorStart,
|
|
4438
7549
|
isTypeCompatible,
|
|
4439
7550
|
isUrlStart,
|
|
7551
|
+
isValidReference,
|
|
4440
7552
|
isVariableRef,
|
|
4441
7553
|
isWhitespace,
|
|
4442
7554
|
joinTokens,
|
|
7555
|
+
jsonToSemanticNode,
|
|
4443
7556
|
lookupKeyword,
|
|
4444
7557
|
lookupMarker,
|
|
4445
7558
|
matchBest,
|
|
4446
7559
|
matchPattern,
|
|
7560
|
+
needsDisambiguation,
|
|
4447
7561
|
noChange,
|
|
4448
7562
|
normalized,
|
|
7563
|
+
parseCompound,
|
|
7564
|
+
parseDocument,
|
|
7565
|
+
parseExplicit,
|
|
7566
|
+
parseJSONL,
|
|
4449
7567
|
patternMatcher,
|
|
7568
|
+
registryToAOTBackends,
|
|
7569
|
+
renderDocument,
|
|
7570
|
+
renderExplicit,
|
|
7571
|
+
renderExpr,
|
|
4450
7572
|
reorderRoles,
|
|
7573
|
+
semanticNodeToJSON,
|
|
7574
|
+
semanticNodeToRuntimeAST,
|
|
7575
|
+
semanticValueToAST,
|
|
7576
|
+
synthesizeFromSchemas,
|
|
7577
|
+
toEnvelopeJSON,
|
|
7578
|
+
toJSONL,
|
|
7579
|
+
toJSONLRow,
|
|
7580
|
+
toProtocolJSON,
|
|
7581
|
+
validateProtocolJSON,
|
|
7582
|
+
validateSemanticJSON,
|
|
4451
7583
|
validateValueType,
|
|
4452
7584
|
withDefaultExtractors
|
|
4453
7585
|
});
|