@lokascript/framework 4.7.0 → 5.0.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/CHANGELOG.md +76 -1
- package/dist/api/create-dsl.d.ts +14 -12
- package/dist/api/create-dsl.d.ts.map +1 -1
- package/dist/api/index.js +888 -1683
- package/dist/api/index.js.map +1 -1
- package/dist/core/index.d.ts +0 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +560 -1133
- package/dist/core/index.js.map +1 -1
- package/dist/core/matching/index.d.ts +2 -0
- package/dist/core/matching/index.d.ts.map +1 -1
- package/dist/core/matching/possessive-keywords.d.ts.map +1 -0
- package/dist/core/matching/stock-confidence.d.ts +3 -4
- package/dist/core/matching/stock-confidence.d.ts.map +1 -1
- package/dist/core/matching/stock-reader.d.ts +8 -1
- package/dist/core/matching/stock-reader.d.ts.map +1 -1
- package/dist/core/matching/stock-reading.d.ts +4 -4
- package/dist/core/{pattern-matching/utils → matching}/type-validation.d.ts +1 -1
- package/dist/core/matching/type-validation.d.ts.map +1 -0
- package/dist/core/types.d.ts +5 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/generation/index.d.ts +1 -1
- package/dist/generation/index.d.ts.map +1 -1
- package/dist/generation/index.js +32 -171
- package/dist/generation/index.js.map +1 -1
- package/dist/generation/profile.d.ts +30 -0
- package/dist/generation/profile.d.ts.map +1 -0
- package/dist/generation/renderer.d.ts +1 -1
- package/dist/generation/renderer.d.ts.map +1 -1
- package/dist/generation/schema-patterns.d.ts +3 -3
- package/dist/index.cjs +969 -1674
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +969 -1668
- package/dist/index.js.map +1 -1
- package/dist/ir/index.js.map +1 -1
- package/dist/multilingual/builders.d.ts +3 -3
- package/dist/multilingual/builders.d.ts.map +1 -1
- package/dist/multilingual/index.js.map +1 -1
- package/dist/parsing/index.d.ts +0 -1
- package/dist/parsing/index.d.ts.map +1 -1
- package/dist/parsing/index.js +0 -1145
- package/dist/parsing/index.js.map +1 -1
- package/dist/testing/gate/index.js +44 -12
- package/dist/testing/gate/index.js.map +1 -1
- package/dist/testing/gate/lint/types.d.ts +1 -1
- package/dist/testing/gate/lint/types.d.ts.map +1 -1
- package/dist/testing/gate/round-trip.d.ts +24 -3
- package/dist/testing/gate/round-trip.d.ts.map +1 -1
- package/package.json +2 -7
- package/src/__test__/test-utils.ts +1 -1
- package/src/api/create-dsl-engine.test.ts +123 -34
- package/src/api/create-dsl.test.ts +7 -0
- package/src/api/create-dsl.ts +56 -39
- package/src/core/index.ts +0 -1
- package/src/core/matching/index.ts +2 -0
- package/src/core/matching/stock-confidence.ts +3 -4
- package/src/core/matching/stock-reader.test.ts +87 -3
- package/src/core/matching/stock-reader.ts +20 -1
- package/src/core/matching/stock-reading.ts +6 -6
- package/src/core/{pattern-matching/utils → matching}/type-validation.test.ts +2 -2
- package/src/core/{pattern-matching/utils → matching}/type-validation.ts +1 -1
- package/src/core/types.ts +5 -0
- package/src/generation/domain-renderer.test.ts +1 -1
- package/src/generation/index.ts +1 -1
- package/src/generation/profile.ts +41 -0
- package/src/generation/renderer.test.ts +1 -1
- package/src/generation/renderer.ts +1 -1
- package/src/generation/schema-patterns.test.ts +41 -1
- package/src/generation/schema-patterns.ts +15 -3
- package/src/multilingual/bridge.test.ts +31 -23
- package/src/multilingual/builders.ts +3 -3
- package/src/parsing/index.ts +0 -1
- package/src/testing/gate/lint/types.ts +1 -1
- package/src/testing/gate/round-trip.test.ts +99 -9
- package/src/testing/gate/round-trip.ts +76 -11
- package/dist/core/pattern-matching/index.d.ts +0 -6
- package/dist/core/pattern-matching/index.d.ts.map +0 -1
- package/dist/core/pattern-matching/index.js +0 -1211
- package/dist/core/pattern-matching/index.js.map +0 -1
- package/dist/core/pattern-matching/pattern-matcher.d.ts +0 -122
- package/dist/core/pattern-matching/pattern-matcher.d.ts.map +0 -1
- package/dist/core/pattern-matching/utils/index.d.ts +0 -6
- package/dist/core/pattern-matching/utils/index.d.ts.map +0 -1
- package/dist/core/pattern-matching/utils/possessive-keywords.d.ts.map +0 -1
- package/dist/core/pattern-matching/utils/type-validation.d.ts.map +0 -1
- package/dist/generation/pattern-generator.d.ts +0 -55
- package/dist/generation/pattern-generator.d.ts.map +0 -1
- package/src/core/pattern-matching/index.ts +0 -6
- package/src/core/pattern-matching/pattern-matcher.test.ts +0 -1031
- package/src/core/pattern-matching/pattern-matcher.ts +0 -765
- package/src/core/pattern-matching/utils/index.ts +0 -6
- package/src/generation/pattern-generator.test.ts +0 -532
- package/src/generation/pattern-generator.ts +0 -327
- /package/dist/core/{pattern-matching/utils → matching}/possessive-keywords.d.ts +0 -0
- /package/src/core/{pattern-matching/utils → matching}/possessive-keywords.ts +0 -0
package/dist/index.cjs
CHANGED
|
@@ -41,7 +41,6 @@ __export(index_exports, {
|
|
|
41
41
|
NullProfileProvider: () => NullProfileProvider,
|
|
42
42
|
NumberExtractor: () => NumberExtractor,
|
|
43
43
|
OperatorExtractor: () => OperatorExtractor,
|
|
44
|
-
PatternMatcher: () => PatternMatcher,
|
|
45
44
|
PatternTracker: () => PatternTracker,
|
|
46
45
|
PunctuationExtractor: () => PunctuationExtractor,
|
|
47
46
|
StockValueReader: () => StockValueReader,
|
|
@@ -105,8 +104,6 @@ __export(index_exports, {
|
|
|
105
104
|
fromInterchangeNode: () => fromInterchangeNode,
|
|
106
105
|
fromProtocolJSON: () => import_intent5.fromProtocolJSON,
|
|
107
106
|
generateExamples: () => generateExamples,
|
|
108
|
-
generatePattern: () => generatePattern,
|
|
109
|
-
generatePatternVariants: () => generatePatternVariants,
|
|
110
107
|
generatePrompt: () => generatePrompt,
|
|
111
108
|
generateProtocolReference: () => generateProtocolReference,
|
|
112
109
|
getAllPossessiveKeywords: () => getAllPossessiveKeywords,
|
|
@@ -145,8 +142,6 @@ __export(index_exports, {
|
|
|
145
142
|
legacyMarkerAlternatives: () => legacyMarkerAlternatives,
|
|
146
143
|
lookupKeyword: () => lookupKeyword,
|
|
147
144
|
lookupMarker: () => lookupMarker,
|
|
148
|
-
matchBest: () => matchBest,
|
|
149
|
-
matchPattern: () => matchPattern,
|
|
150
145
|
needsDisambiguation: () => needsDisambiguation,
|
|
151
146
|
noChange: () => noChange,
|
|
152
147
|
normalized: () => normalized,
|
|
@@ -154,7 +149,6 @@ __export(index_exports, {
|
|
|
154
149
|
parseDocument: () => import_intent3.parseDocument,
|
|
155
150
|
parseExplicit: () => import_intent3.parseExplicit,
|
|
156
151
|
parseJSONL: () => parseJSONL,
|
|
157
|
-
patternMatcher: () => patternMatcher,
|
|
158
152
|
readGreedy: () => readGreedy,
|
|
159
153
|
readRoleValue: () => readRoleValue,
|
|
160
154
|
registryToAOTBackends: () => registryToAOTBackends,
|
|
@@ -189,256 +183,670 @@ module.exports = __toCommonJS(index_exports);
|
|
|
189
183
|
// src/core/types.ts
|
|
190
184
|
var import_intent = require("@lokascript/intent");
|
|
191
185
|
|
|
192
|
-
// src/core/
|
|
193
|
-
var
|
|
194
|
-
constructor(
|
|
195
|
-
this.
|
|
196
|
-
this.
|
|
186
|
+
// src/core/tokenization/token-utils.ts
|
|
187
|
+
var TokenStreamImpl = class {
|
|
188
|
+
constructor(tokens, language) {
|
|
189
|
+
this.pos = 0;
|
|
190
|
+
this.tokens = tokens;
|
|
191
|
+
this.language = language;
|
|
197
192
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return
|
|
193
|
+
peek(offset = 0) {
|
|
194
|
+
const index = this.pos + offset;
|
|
195
|
+
if (index < 0 || index >= this.tokens.length) {
|
|
196
|
+
return null;
|
|
202
197
|
}
|
|
203
|
-
return
|
|
198
|
+
return this.tokens[index];
|
|
204
199
|
}
|
|
205
|
-
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
209
|
-
switch (level) {
|
|
210
|
-
case "debug":
|
|
211
|
-
console.debug(`${timestamp} ${prefix} DEBUG:`, message, ...args);
|
|
212
|
-
break;
|
|
213
|
-
case "info":
|
|
214
|
-
console.info(`${timestamp} ${prefix} INFO:`, message, ...args);
|
|
215
|
-
break;
|
|
216
|
-
case "warn":
|
|
217
|
-
console.warn(`${timestamp} ${prefix} WARN:`, message, ...args);
|
|
218
|
-
break;
|
|
219
|
-
case "error":
|
|
220
|
-
console.error(`${timestamp} ${prefix} ERROR:`, message, ...args);
|
|
221
|
-
break;
|
|
200
|
+
advance() {
|
|
201
|
+
if (this.isAtEnd()) {
|
|
202
|
+
throw new Error("Unexpected end of token stream");
|
|
222
203
|
}
|
|
204
|
+
return this.tokens[this.pos++];
|
|
223
205
|
}
|
|
224
|
-
|
|
225
|
-
this.
|
|
206
|
+
isAtEnd() {
|
|
207
|
+
return this.pos >= this.tokens.length;
|
|
226
208
|
}
|
|
227
|
-
|
|
228
|
-
this.
|
|
209
|
+
mark() {
|
|
210
|
+
return { position: this.pos };
|
|
229
211
|
}
|
|
230
|
-
|
|
231
|
-
this.
|
|
212
|
+
reset(mark) {
|
|
213
|
+
this.pos = mark.position;
|
|
232
214
|
}
|
|
233
|
-
|
|
234
|
-
this.
|
|
215
|
+
position() {
|
|
216
|
+
return this.pos;
|
|
235
217
|
}
|
|
236
218
|
/**
|
|
237
|
-
*
|
|
219
|
+
* Get remaining tokens as an array.
|
|
238
220
|
*/
|
|
239
|
-
|
|
240
|
-
return this.
|
|
221
|
+
remaining() {
|
|
222
|
+
return this.tokens.slice(this.pos);
|
|
241
223
|
}
|
|
242
224
|
/**
|
|
243
|
-
*
|
|
225
|
+
* Consume tokens while predicate is true.
|
|
244
226
|
*/
|
|
245
|
-
|
|
246
|
-
|
|
227
|
+
takeWhile(predicate) {
|
|
228
|
+
const result = [];
|
|
229
|
+
while (!this.isAtEnd() && predicate(this.peek())) {
|
|
230
|
+
result.push(this.advance());
|
|
231
|
+
}
|
|
232
|
+
return result;
|
|
247
233
|
}
|
|
248
234
|
/**
|
|
249
|
-
*
|
|
235
|
+
* Skip tokens while predicate is true.
|
|
250
236
|
*/
|
|
251
|
-
|
|
252
|
-
this.
|
|
237
|
+
skipWhile(predicate) {
|
|
238
|
+
while (!this.isAtEnd() && predicate(this.peek())) {
|
|
239
|
+
this.advance();
|
|
240
|
+
}
|
|
253
241
|
}
|
|
254
242
|
};
|
|
255
|
-
function
|
|
256
|
-
return
|
|
243
|
+
function createPosition(start, end) {
|
|
244
|
+
return { start, end };
|
|
257
245
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
246
|
+
function createToken(valueOrParams, kind, position, normalizedOrOptions) {
|
|
247
|
+
if (typeof valueOrParams === "object") {
|
|
248
|
+
const { value: value2, kind: kind2, position: position2, normalized: normalized2, stem, stemConfidence, metadata } = valueOrParams;
|
|
249
|
+
return {
|
|
250
|
+
value: value2,
|
|
251
|
+
kind: kind2,
|
|
252
|
+
position: position2,
|
|
253
|
+
...normalized2 !== void 0 && { normalized: normalized2 },
|
|
254
|
+
...stem !== void 0 && { stem },
|
|
255
|
+
...stemConfidence !== void 0 && { stemConfidence },
|
|
256
|
+
...metadata !== void 0 && { metadata }
|
|
257
|
+
};
|
|
266
258
|
}
|
|
267
|
-
|
|
268
|
-
|
|
259
|
+
const value = valueOrParams;
|
|
260
|
+
if (!kind || !position) {
|
|
261
|
+
throw new Error("createToken requires kind and position parameters");
|
|
269
262
|
}
|
|
270
|
-
if (
|
|
271
|
-
return
|
|
263
|
+
if (typeof normalizedOrOptions === "string") {
|
|
264
|
+
return { value, kind, position, normalized: normalizedOrOptions };
|
|
272
265
|
}
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
266
|
+
if (normalizedOrOptions) {
|
|
267
|
+
const { normalized: normalized2, stem, stemConfidence, metadata } = normalizedOrOptions;
|
|
268
|
+
return {
|
|
269
|
+
value,
|
|
270
|
+
kind,
|
|
271
|
+
position,
|
|
272
|
+
...normalized2 !== void 0 && { normalized: normalized2 },
|
|
273
|
+
...stem !== void 0 && { stem },
|
|
274
|
+
...stemConfidence !== void 0 && { stemConfidence },
|
|
275
|
+
...metadata !== void 0 && { metadata }
|
|
276
|
+
};
|
|
278
277
|
}
|
|
279
|
-
return
|
|
280
|
-
}
|
|
281
|
-
function isCSSSelector(value) {
|
|
282
|
-
return value.startsWith(".") || value.startsWith("#") || value.startsWith("<");
|
|
283
|
-
}
|
|
284
|
-
function isClassName(value) {
|
|
285
|
-
return value.startsWith(".");
|
|
286
|
-
}
|
|
287
|
-
function isIdSelector(value) {
|
|
288
|
-
return value.startsWith("#");
|
|
289
|
-
}
|
|
290
|
-
function isCSSPropertyRef(value) {
|
|
291
|
-
return value.startsWith("*");
|
|
278
|
+
return { value, kind, position };
|
|
292
279
|
}
|
|
293
|
-
function
|
|
294
|
-
|
|
295
|
-
if (durationMatch) {
|
|
296
|
-
return true;
|
|
297
|
-
}
|
|
298
|
-
const num = parseFloat(value);
|
|
299
|
-
return !isNaN(num) && isFinite(num);
|
|
280
|
+
function isWhitespace(char) {
|
|
281
|
+
return /\s/.test(char);
|
|
300
282
|
}
|
|
301
|
-
function
|
|
302
|
-
return
|
|
283
|
+
function isSelectorStart(char) {
|
|
284
|
+
return char === "#" || char === "." || char === "[" || char === "@" || char === "*" || char === "<";
|
|
303
285
|
}
|
|
304
|
-
function
|
|
305
|
-
return
|
|
286
|
+
function isQuote(char) {
|
|
287
|
+
return char === '"' || char === "'" || char === "`" || char === "\u300C" || char === "\u300D";
|
|
306
288
|
}
|
|
307
|
-
function
|
|
308
|
-
|
|
309
|
-
return builtIns.has(value.toLowerCase());
|
|
289
|
+
function isDigit(char) {
|
|
290
|
+
return /\d/.test(char);
|
|
310
291
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
function getPossessiveReference(profile, keyword) {
|
|
314
|
-
return profile?.possessive?.keywords?.[keyword];
|
|
292
|
+
function stripOptionalDiacritics(word) {
|
|
293
|
+
return word.replace(/[\u064b-\u0652\u0670]/g, "");
|
|
315
294
|
}
|
|
316
|
-
function
|
|
317
|
-
return
|
|
295
|
+
function isAsciiLetter(char) {
|
|
296
|
+
return /[a-zA-Z]/.test(char);
|
|
318
297
|
}
|
|
319
|
-
function
|
|
320
|
-
return
|
|
298
|
+
function isAsciiIdentifierChar(char) {
|
|
299
|
+
return /[a-zA-Z0-9_-]/.test(char);
|
|
321
300
|
}
|
|
322
301
|
|
|
323
|
-
// src/core/matching/
|
|
324
|
-
function
|
|
325
|
-
return
|
|
302
|
+
// src/core/matching/match-skeleton.ts
|
|
303
|
+
function groupHasRole(group) {
|
|
304
|
+
return group.tokens.some((t) => t.type === "role" || t.type === "group" && groupHasRole(t));
|
|
326
305
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
306
|
+
var MatchSkeleton = class {
|
|
307
|
+
/**
|
|
308
|
+
* @param makeReader - builds the grammar's reader, handed this matcher's view
|
|
309
|
+
* of the match in progress (one reader per matcher).
|
|
310
|
+
*/
|
|
311
|
+
constructor(makeReader, confidenceModel) {
|
|
312
|
+
/**
|
|
313
|
+
* The pattern tokens still to match after the one being matched, through
|
|
314
|
+
* every enclosing group: a value stops at any marker the pattern still owes.
|
|
315
|
+
*/
|
|
316
|
+
this.patternRest = [];
|
|
317
|
+
/**
|
|
318
|
+
* Whether the role slot being matched directly follows a literal the pattern
|
|
319
|
+
* matched: its marker (`[por {quantity}]`), or the command's verb.
|
|
320
|
+
*/
|
|
321
|
+
this.slotAfterLiteral = false;
|
|
322
|
+
/**
|
|
323
|
+
* Selective memoization cache (Phase 6.1).
|
|
324
|
+
* Caches pattern match results keyed by `tokenPosition:patternId`.
|
|
325
|
+
* Cleared before each top-level matchBest() call.
|
|
326
|
+
*/
|
|
327
|
+
this.matchCache = /* @__PURE__ */ new Map();
|
|
328
|
+
/**
|
|
329
|
+
* Stream index where each role's capture began, for the current
|
|
330
|
+
* `matchPattern` call. Rebuilt per call, written on the same success path as
|
|
331
|
+
* `captured` — see `PatternMatchResult.roleStarts`.
|
|
332
|
+
*/
|
|
333
|
+
this.roleStarts = /* @__PURE__ */ new Map();
|
|
334
|
+
/**
|
|
335
|
+
* Track stem matches for confidence calculation.
|
|
336
|
+
* This is set during matching and read during confidence calculation.
|
|
337
|
+
*/
|
|
338
|
+
this.stemMatchCount = 0;
|
|
339
|
+
this.totalKeywordMatches = 0;
|
|
340
|
+
this.confidenceModel = confidenceModel;
|
|
341
|
+
this.reader = makeReader({
|
|
342
|
+
patternRest: () => this.patternRest,
|
|
343
|
+
slotAfterLiteral: () => this.slotAfterLiteral,
|
|
344
|
+
wouldMatch: (pt, token) => this.patternTokenWouldMatch(pt, token)
|
|
345
|
+
});
|
|
333
346
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
347
|
+
/**
|
|
348
|
+
* Try to match a single pattern against the token stream.
|
|
349
|
+
* Returns the match result or null if no match.
|
|
350
|
+
*/
|
|
351
|
+
matchPattern(tokens, pattern) {
|
|
352
|
+
const mark = tokens.mark();
|
|
353
|
+
const captured = /* @__PURE__ */ new Map();
|
|
354
|
+
this.reader.beginPattern(pattern);
|
|
355
|
+
this.stemMatchCount = 0;
|
|
356
|
+
this.totalKeywordMatches = 0;
|
|
357
|
+
this.roleStarts = /* @__PURE__ */ new Map();
|
|
358
|
+
this.patternRest = [];
|
|
359
|
+
const success = this.matchTokenSequence(tokens, pattern.template.tokens, captured);
|
|
360
|
+
if (!success) {
|
|
361
|
+
tokens.reset(mark);
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
const confidence = this.confidenceModel.calculate({
|
|
365
|
+
pattern,
|
|
366
|
+
captured,
|
|
367
|
+
stemMatchCount: this.stemMatchCount,
|
|
368
|
+
totalKeywordMatches: this.totalKeywordMatches
|
|
369
|
+
});
|
|
370
|
+
this.applyExtractionRules(pattern, captured);
|
|
371
|
+
return {
|
|
372
|
+
pattern,
|
|
373
|
+
captured,
|
|
374
|
+
consumedTokens: tokens.position() - mark.position,
|
|
375
|
+
confidence,
|
|
376
|
+
roleStarts: this.roleStarts,
|
|
377
|
+
...this.reader.resultFields?.()
|
|
378
|
+
};
|
|
341
379
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
380
|
+
/**
|
|
381
|
+
* Try to match multiple patterns, return the best match.
|
|
382
|
+
*/
|
|
383
|
+
matchBest(tokens, patterns) {
|
|
384
|
+
this.matchCache.clear();
|
|
385
|
+
const matches = [];
|
|
386
|
+
const startPos = tokens.position();
|
|
387
|
+
for (const pattern of patterns) {
|
|
388
|
+
const mark = tokens.mark();
|
|
389
|
+
const cacheKey = `${startPos}:${pattern.id}`;
|
|
390
|
+
let result;
|
|
391
|
+
if (this.matchCache.has(cacheKey)) {
|
|
392
|
+
result = this.matchCache.get(cacheKey);
|
|
393
|
+
} else {
|
|
394
|
+
result = this.matchPattern(tokens, pattern);
|
|
395
|
+
this.matchCache.set(cacheKey, result);
|
|
396
|
+
}
|
|
397
|
+
if (result) {
|
|
398
|
+
matches.push(result);
|
|
347
399
|
}
|
|
400
|
+
tokens.reset(mark);
|
|
348
401
|
}
|
|
349
|
-
|
|
350
|
-
|
|
402
|
+
if (matches.length === 0) {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
matches.sort((a, b) => {
|
|
406
|
+
const priorityDiff = b.pattern.priority - a.pattern.priority;
|
|
407
|
+
if (priorityDiff !== 0) return priorityDiff;
|
|
408
|
+
return b.confidence - a.confidence;
|
|
409
|
+
});
|
|
410
|
+
const best = matches[0];
|
|
411
|
+
this.matchPattern(tokens, best.pattern);
|
|
412
|
+
return best;
|
|
351
413
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
414
|
+
/**
|
|
415
|
+
* Match a sequence of pattern tokens against the token stream.
|
|
416
|
+
*/
|
|
417
|
+
matchTokenSequence(tokens, patternTokens, captured, nextAfterSequence) {
|
|
418
|
+
this.reader.skipLeading?.(tokens, patternTokens);
|
|
419
|
+
for (let i = 0; i < patternTokens.length; i++) {
|
|
420
|
+
const patternToken = patternTokens[i];
|
|
421
|
+
this.reader.beforePatternToken?.(tokens, patternTokens, i, captured);
|
|
422
|
+
if (this.reader.maySkipSlot(
|
|
423
|
+
patternToken,
|
|
424
|
+
tokens.peek(),
|
|
425
|
+
patternTokens[i + 1] ?? nextAfterSequence
|
|
426
|
+
)) {
|
|
427
|
+
const skipMark = tokens.mark();
|
|
428
|
+
const speculative = new Map(captured);
|
|
429
|
+
if (this.matchTokenSequence(
|
|
430
|
+
tokens,
|
|
431
|
+
patternTokens.slice(i + 1),
|
|
432
|
+
speculative,
|
|
433
|
+
nextAfterSequence
|
|
434
|
+
) && this.reader.atClauseEnd(tokens)) {
|
|
435
|
+
captured.clear();
|
|
436
|
+
for (const [role, value] of speculative) captured.set(role, value);
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
tokens.reset(skipMark);
|
|
440
|
+
}
|
|
441
|
+
const outerRest = this.patternRest;
|
|
442
|
+
this.patternRest = [...patternTokens.slice(i + 1), ...outerRest];
|
|
443
|
+
this.slotAfterLiteral = patternToken.type === "role" && patternTokens[i - 1]?.type === "literal";
|
|
444
|
+
const matched = this.matchPatternToken(
|
|
445
|
+
tokens,
|
|
446
|
+
patternToken,
|
|
447
|
+
captured,
|
|
448
|
+
patternTokens[i + 1] ?? nextAfterSequence
|
|
449
|
+
);
|
|
450
|
+
this.patternRest = outerRest;
|
|
451
|
+
if (!matched) {
|
|
452
|
+
if (this.isOptional(patternToken)) {
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
return false;
|
|
357
456
|
}
|
|
358
457
|
}
|
|
359
|
-
captured.set(patternToken.role, methodCallValue);
|
|
360
458
|
return true;
|
|
361
459
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
460
|
+
/**
|
|
461
|
+
* Match a single pattern token against the current position in the stream.
|
|
462
|
+
*/
|
|
463
|
+
matchPatternToken(tokens, patternToken, captured, nextPatternToken) {
|
|
464
|
+
switch (patternToken.type) {
|
|
465
|
+
case "literal":
|
|
466
|
+
return this.matchLiteralToken(tokens, patternToken);
|
|
467
|
+
case "role": {
|
|
468
|
+
const roleStart = tokens.position();
|
|
469
|
+
const matchedRole = this.reader.readRole(tokens, patternToken, captured, nextPatternToken);
|
|
470
|
+
if (matchedRole && captured.has(patternToken.role)) {
|
|
471
|
+
this.roleStarts.set(patternToken.role, roleStart);
|
|
472
|
+
}
|
|
473
|
+
return matchedRole;
|
|
367
474
|
}
|
|
475
|
+
case "group":
|
|
476
|
+
return this.matchGroupToken(tokens, patternToken, captured, nextPatternToken);
|
|
477
|
+
default:
|
|
478
|
+
return false;
|
|
368
479
|
}
|
|
369
|
-
captured.set(patternToken.role, possessiveSelectorValue);
|
|
370
|
-
return true;
|
|
371
480
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
481
|
+
/**
|
|
482
|
+
* Match a literal pattern token (keyword or particle).
|
|
483
|
+
*/
|
|
484
|
+
matchLiteralToken(tokens, patternToken) {
|
|
485
|
+
const token = tokens.peek();
|
|
486
|
+
if (!token) return false;
|
|
487
|
+
const matchType = this.getMatchType(token, patternToken.value);
|
|
488
|
+
if (matchType !== "none") {
|
|
489
|
+
this.totalKeywordMatches++;
|
|
490
|
+
if (matchType === "stem") {
|
|
491
|
+
this.stemMatchCount++;
|
|
377
492
|
}
|
|
493
|
+
tokens.advance();
|
|
494
|
+
return true;
|
|
378
495
|
}
|
|
379
|
-
|
|
380
|
-
|
|
496
|
+
if (patternToken.alternatives) {
|
|
497
|
+
for (const alt of patternToken.alternatives) {
|
|
498
|
+
const altMatchType = this.getMatchType(token, alt);
|
|
499
|
+
if (altMatchType !== "none") {
|
|
500
|
+
this.totalKeywordMatches++;
|
|
501
|
+
if (altMatchType === "stem") {
|
|
502
|
+
this.stemMatchCount++;
|
|
503
|
+
}
|
|
504
|
+
tokens.advance();
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
for (const value of [patternToken.value, ...patternToken.alternatives ?? []]) {
|
|
510
|
+
for (const sep of ["-", "_"]) {
|
|
511
|
+
if (value.includes(sep) && this.tryMatchShatteredCompound(tokens, value, sep)) {
|
|
512
|
+
this.totalKeywordMatches++;
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return false;
|
|
381
518
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
519
|
+
/**
|
|
520
|
+
* Match a compound keyword (`titik-henti`, `по_умолчанию`) against a
|
|
521
|
+
* shattered token run (`titik` `-` `henti` / `по` `_` `умолчанию`): word
|
|
522
|
+
* segments must be identifier/keyword/particle tokens equal to the expected
|
|
523
|
+
* segments (case-insensitive; particle because prepositions like ru `по` /
|
|
524
|
+
* uk `за` lead some underscore compounds), separators lone `sep` tokens.
|
|
525
|
+
* Consumes the run on success, resets the stream on failure.
|
|
526
|
+
*/
|
|
527
|
+
tryMatchShatteredCompound(tokens, expected, sep) {
|
|
528
|
+
const segments = expected.split(sep);
|
|
529
|
+
if (segments.length < 2 || segments.some((s) => s.length === 0)) return false;
|
|
530
|
+
const mark = tokens.mark();
|
|
531
|
+
for (let i = 0; i < segments.length; i++) {
|
|
532
|
+
if (i > 0) {
|
|
533
|
+
const sepToken = tokens.peek();
|
|
534
|
+
if (!sepToken || sepToken.value !== sep) {
|
|
535
|
+
tokens.reset(mark);
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
tokens.advance();
|
|
539
|
+
}
|
|
540
|
+
const word = tokens.peek();
|
|
541
|
+
if (!word || word.kind !== "identifier" && word.kind !== "keyword" && word.kind !== "particle" || word.value.toLowerCase() !== segments[i].toLowerCase()) {
|
|
542
|
+
tokens.reset(mark);
|
|
543
|
+
return false;
|
|
387
544
|
}
|
|
545
|
+
tokens.advance();
|
|
388
546
|
}
|
|
389
|
-
captured.set(patternToken.role, selectorPropertyValue);
|
|
390
547
|
return true;
|
|
391
548
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
549
|
+
/**
|
|
550
|
+
* Match a group pattern token (optional sequence).
|
|
551
|
+
*/
|
|
552
|
+
matchGroupToken(tokens, patternToken, captured, nextPatternToken) {
|
|
553
|
+
const mark = tokens.mark();
|
|
554
|
+
const capturedBefore = new Map(captured);
|
|
555
|
+
let success = this.matchTokenSequence(tokens, patternToken.tokens, captured, nextPatternToken);
|
|
556
|
+
if (success && tokens.position() > mark.position && groupHasRole(patternToken) && [...captured].every(([role, value]) => capturedBefore.get(role) === value) && captured.size === capturedBefore.size) {
|
|
557
|
+
success = false;
|
|
558
|
+
}
|
|
559
|
+
if (success) {
|
|
560
|
+
const markerLiterals = [];
|
|
561
|
+
for (const inner of patternToken.tokens) {
|
|
562
|
+
if (inner.type === "group" && inner.renderRequired) {
|
|
563
|
+
for (const t of inner.tokens) {
|
|
564
|
+
if (t.type === "literal") markerLiterals.push(t.value, ...t.alternatives ?? []);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (markerLiterals.length > 0) {
|
|
569
|
+
const addedRole = [...captured.keys()].some((role) => !capturedBefore.has(role));
|
|
570
|
+
if (addedRole) {
|
|
571
|
+
const last = tokens.tokens[tokens.position() - 1];
|
|
572
|
+
const markerSeen = last !== void 0 && tokens.position() > mark.position && markerLiterals.some((lit) => this.getMatchType(last, lit) !== "none");
|
|
573
|
+
if (!markerSeen) success = false;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (!success) {
|
|
578
|
+
tokens.reset(mark);
|
|
579
|
+
for (const role of captured.keys()) {
|
|
580
|
+
if (!capturedBefore.has(role)) {
|
|
581
|
+
captured.delete(role);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
for (const [role, value] of capturedBefore) {
|
|
585
|
+
if (captured.get(role) !== value) {
|
|
586
|
+
captured.set(role, value);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
398
589
|
return patternToken.optional || false;
|
|
399
590
|
}
|
|
591
|
+
return true;
|
|
400
592
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
const baseRef = getPossessiveReference(profile, tokenLower);
|
|
412
|
-
if (!baseRef) return null;
|
|
413
|
-
const mark = tokens.mark();
|
|
414
|
-
tokens.advance();
|
|
415
|
-
const propertyToken = tokens.peek();
|
|
416
|
-
if (!propertyToken) {
|
|
417
|
-
tokens.reset(mark);
|
|
418
|
-
return null;
|
|
419
|
-
}
|
|
420
|
-
if (propertyToken.kind === "identifier" || propertyToken.kind === "keyword" && !isStructuralKeyword(propertyToken.value) || propertyToken.kind === "selector" && propertyToken.value.startsWith("*") || propertyToken.kind === "selector" && propertyToken.value.startsWith("@") || propertyToken.kind === "selector" && propertyToken.value.startsWith(".") && /^\.[a-zA-Z_]\w*/.test(propertyToken.value)) {
|
|
421
|
-
tokens.advance();
|
|
422
|
-
let propertyName = propertyToken.value;
|
|
423
|
-
if (propertyToken.kind === "selector" && propertyName.startsWith(".") && /^\.[a-zA-Z_]\w*/.test(propertyName)) {
|
|
424
|
-
propertyName = propertyName.substring(1);
|
|
425
|
-
}
|
|
426
|
-
let chainedProps = propertyName;
|
|
427
|
-
while (tokens.peek()?.kind === "selector" && tokens.peek().value.startsWith(".") && /^\.[a-zA-Z_]\w*/.test(tokens.peek().value)) {
|
|
428
|
-
chainedProps += tokens.peek().value;
|
|
429
|
-
tokens.advance();
|
|
593
|
+
/**
|
|
594
|
+
* Whether a pattern token (literal, or a group starting with a literal) would
|
|
595
|
+
* match the given stream token. Used to keep the event-head source-clause
|
|
596
|
+
* consumption from stealing a marker the pattern explicitly expects.
|
|
597
|
+
*/
|
|
598
|
+
patternTokenWouldMatch(pt, token) {
|
|
599
|
+
if (!pt) return false;
|
|
600
|
+
if (pt.type === "literal") {
|
|
601
|
+
if (this.getMatchType(token, pt.value) !== "none") return true;
|
|
602
|
+
return !!pt.alternatives?.some((a) => this.getMatchType(token, a) !== "none");
|
|
430
603
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
chainedProps += nextPeek.value;
|
|
434
|
-
tokens.advance();
|
|
604
|
+
if (pt.type === "group") {
|
|
605
|
+
return this.patternTokenWouldMatch(pt.tokens?.[0], token);
|
|
435
606
|
}
|
|
436
|
-
return
|
|
607
|
+
return false;
|
|
437
608
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
609
|
+
/**
|
|
610
|
+
* Get the type of match for a token against a value.
|
|
611
|
+
* Used for confidence calculation.
|
|
612
|
+
*/
|
|
613
|
+
getMatchType(token, value) {
|
|
614
|
+
if (token.value === value) return "exact";
|
|
615
|
+
if (token.normalized === value) return "normalized";
|
|
616
|
+
if (token.stem === value && token.stemConfidence !== void 0 && token.stemConfidence >= 0.7) {
|
|
617
|
+
return "stem";
|
|
618
|
+
}
|
|
619
|
+
if (token.kind === "keyword" && token.value.toLowerCase() === value.toLowerCase()) {
|
|
620
|
+
return "case-insensitive";
|
|
621
|
+
}
|
|
622
|
+
if (token.kind === "keyword") {
|
|
623
|
+
const strippedToken = stripOptionalDiacritics(token.value).toLowerCase();
|
|
624
|
+
if (strippedToken && strippedToken === stripOptionalDiacritics(value).toLowerCase()) {
|
|
625
|
+
return "case-insensitive";
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return "none";
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Apply extraction rules to fill in static values and defaults for missing
|
|
632
|
+
* roles, and re-shape captured roles that declare a `transform` (the
|
|
633
|
+
* ExtractionRule field was documented but unwired — go-qu-url-dest is the
|
|
634
|
+
* first consumer: it re-types the marker-bound capture to the expression
|
|
635
|
+
* shape the en reference produces).
|
|
636
|
+
*/
|
|
637
|
+
applyExtractionRules(pattern, captured) {
|
|
638
|
+
for (const [role, rule] of Object.entries(pattern.extraction)) {
|
|
639
|
+
const existing = captured.get(role);
|
|
640
|
+
if (existing) {
|
|
641
|
+
if (rule.transform) {
|
|
642
|
+
const raw = existing.type === "literal" ? String(existing.value) : String(existing.raw ?? "");
|
|
643
|
+
captured.set(role, rule.transform(raw));
|
|
644
|
+
}
|
|
645
|
+
} else if (rule.value !== void 0) {
|
|
646
|
+
captured.set(
|
|
647
|
+
role,
|
|
648
|
+
rule.valueIsExpression ? { type: "expression", raw: rule.value } : { type: "literal", value: rule.value }
|
|
649
|
+
);
|
|
650
|
+
} else if (rule.default) {
|
|
651
|
+
const implicitDefault = {
|
|
652
|
+
...rule.default,
|
|
653
|
+
implicit: true
|
|
654
|
+
};
|
|
655
|
+
captured.set(role, implicitDefault);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Check if a pattern token is optional.
|
|
661
|
+
*/
|
|
662
|
+
isOptional(patternToken) {
|
|
663
|
+
return patternToken.type !== "literal" && patternToken.optional === true;
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
// src/core/matching/type-validation.ts
|
|
668
|
+
function isTypeCompatible(actualType, expectedTypes) {
|
|
669
|
+
if (!expectedTypes || expectedTypes.length === 0) {
|
|
670
|
+
return true;
|
|
671
|
+
}
|
|
672
|
+
if (expectedTypes.includes(actualType)) {
|
|
673
|
+
return true;
|
|
674
|
+
}
|
|
675
|
+
if (expectedTypes.includes("expression")) {
|
|
676
|
+
return true;
|
|
677
|
+
}
|
|
678
|
+
if (actualType === "property-path") {
|
|
679
|
+
return expectedTypes.some((t) => ["selector", "reference", "expression"].includes(t));
|
|
680
|
+
}
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
function validateValueType(value, expectedTypes) {
|
|
684
|
+
if (!expectedTypes || expectedTypes.length === 0) {
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
return isTypeCompatible(value.type, expectedTypes);
|
|
688
|
+
}
|
|
689
|
+
function isCSSSelector(value) {
|
|
690
|
+
return value.startsWith(".") || value.startsWith("#") || value.startsWith("<");
|
|
691
|
+
}
|
|
692
|
+
function isClassName(value) {
|
|
693
|
+
return value.startsWith(".");
|
|
694
|
+
}
|
|
695
|
+
function isIdSelector(value) {
|
|
696
|
+
return value.startsWith("#");
|
|
697
|
+
}
|
|
698
|
+
function isCSSPropertyRef(value) {
|
|
699
|
+
return value.startsWith("*");
|
|
700
|
+
}
|
|
701
|
+
function isNumericValue(value) {
|
|
702
|
+
const durationMatch = value.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/);
|
|
703
|
+
if (durationMatch) {
|
|
704
|
+
return true;
|
|
705
|
+
}
|
|
706
|
+
const num = parseFloat(value);
|
|
707
|
+
return !isNaN(num) && isFinite(num);
|
|
708
|
+
}
|
|
709
|
+
function isPropertyName(value) {
|
|
710
|
+
return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value);
|
|
711
|
+
}
|
|
712
|
+
function isVariableRef(value) {
|
|
713
|
+
return value.startsWith(":");
|
|
714
|
+
}
|
|
715
|
+
function isBuiltInReference(value) {
|
|
716
|
+
const builtIns = /* @__PURE__ */ new Set(["me", "you", "it", "result", "event", "target", "body"]);
|
|
717
|
+
return builtIns.has(value.toLowerCase());
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// src/core/matching/possessive-keywords.ts
|
|
721
|
+
function getPossessiveReference(profile, keyword) {
|
|
722
|
+
return profile?.possessive?.keywords?.[keyword];
|
|
723
|
+
}
|
|
724
|
+
function isPossessiveKeyword(profile, keyword) {
|
|
725
|
+
return profile?.possessive?.keywords?.[keyword] !== void 0;
|
|
726
|
+
}
|
|
727
|
+
function getAllPossessiveKeywords(profile) {
|
|
728
|
+
return profile?.possessive?.keywords ?? {};
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// src/core/matching/stock-reading.ts
|
|
732
|
+
function isValidReference(_value) {
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
735
|
+
function safeToLowerCase(value) {
|
|
736
|
+
if (typeof value === "string") {
|
|
737
|
+
return value.toLowerCase();
|
|
738
|
+
}
|
|
739
|
+
if (value === null || value === void 0) {
|
|
740
|
+
return "";
|
|
741
|
+
}
|
|
742
|
+
return String(value).toLowerCase();
|
|
743
|
+
}
|
|
744
|
+
function readRoleValue(tokens, patternToken, captured, profile) {
|
|
745
|
+
skipNoiseWords(tokens);
|
|
746
|
+
const token = tokens.peek();
|
|
747
|
+
if (!token) {
|
|
748
|
+
return patternToken.optional || false;
|
|
749
|
+
}
|
|
750
|
+
const possessiveValue = tryMatchPossessiveExpression(tokens, profile);
|
|
751
|
+
if (possessiveValue) {
|
|
752
|
+
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
753
|
+
if (!isTypeCompatible(possessiveValue.type, patternToken.expectedTypes)) {
|
|
754
|
+
return patternToken.optional || false;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
captured.set(patternToken.role, possessiveValue);
|
|
758
|
+
return true;
|
|
759
|
+
}
|
|
760
|
+
const methodCallValue = tryMatchMethodCallExpression(tokens);
|
|
761
|
+
if (methodCallValue) {
|
|
762
|
+
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
763
|
+
if (!isTypeCompatible(methodCallValue.type, patternToken.expectedTypes)) {
|
|
764
|
+
return patternToken.optional || false;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
captured.set(patternToken.role, methodCallValue);
|
|
768
|
+
return true;
|
|
769
|
+
}
|
|
770
|
+
const possessiveSelectorValue = tryMatchPossessiveSelectorExpression(tokens);
|
|
771
|
+
if (possessiveSelectorValue) {
|
|
772
|
+
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
773
|
+
if (!isTypeCompatible(possessiveSelectorValue.type, patternToken.expectedTypes)) {
|
|
774
|
+
return patternToken.optional || false;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
captured.set(patternToken.role, possessiveSelectorValue);
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
780
|
+
const propertyAccessValue = tryMatchPropertyAccessExpression(tokens);
|
|
781
|
+
if (propertyAccessValue) {
|
|
782
|
+
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
783
|
+
if (!isTypeCompatible(propertyAccessValue.type, patternToken.expectedTypes)) {
|
|
784
|
+
return patternToken.optional || false;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
captured.set(patternToken.role, propertyAccessValue);
|
|
788
|
+
return true;
|
|
789
|
+
}
|
|
790
|
+
const selectorPropertyValue = tryMatchSelectorPropertyExpression(tokens);
|
|
791
|
+
if (selectorPropertyValue) {
|
|
792
|
+
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
793
|
+
if (!isTypeCompatible(selectorPropertyValue.type, patternToken.expectedTypes)) {
|
|
794
|
+
return patternToken.optional || false;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
captured.set(patternToken.role, selectorPropertyValue);
|
|
798
|
+
return true;
|
|
799
|
+
}
|
|
800
|
+
const value = tokenToSemanticValue(token);
|
|
801
|
+
if (!value) {
|
|
802
|
+
return patternToken.optional || false;
|
|
803
|
+
}
|
|
804
|
+
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
805
|
+
if (!isTypeCompatible(value.type, patternToken.expectedTypes)) {
|
|
806
|
+
return patternToken.optional || false;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
captured.set(patternToken.role, value);
|
|
810
|
+
tokens.advance();
|
|
811
|
+
return true;
|
|
812
|
+
}
|
|
813
|
+
function tryMatchPossessiveExpression(tokens, profile) {
|
|
814
|
+
const token = tokens.peek();
|
|
815
|
+
if (!token) return null;
|
|
816
|
+
if (!profile) return null;
|
|
817
|
+
const tokenValue = token.normalized || token.value;
|
|
818
|
+
const tokenLower = safeToLowerCase(tokenValue);
|
|
819
|
+
const baseRef = getPossessiveReference(profile, tokenLower);
|
|
820
|
+
if (!baseRef) return null;
|
|
821
|
+
const mark = tokens.mark();
|
|
822
|
+
tokens.advance();
|
|
823
|
+
const propertyToken = tokens.peek();
|
|
824
|
+
if (!propertyToken) {
|
|
825
|
+
tokens.reset(mark);
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
828
|
+
if (propertyToken.kind === "identifier" || propertyToken.kind === "keyword" && !isStructuralKeyword(propertyToken.value) || propertyToken.kind === "selector" && propertyToken.value.startsWith("*") || propertyToken.kind === "selector" && propertyToken.value.startsWith("@") || propertyToken.kind === "selector" && propertyToken.value.startsWith(".") && /^\.[a-zA-Z_]\w*/.test(propertyToken.value)) {
|
|
829
|
+
tokens.advance();
|
|
830
|
+
let propertyName = propertyToken.value;
|
|
831
|
+
if (propertyToken.kind === "selector" && propertyName.startsWith(".") && /^\.[a-zA-Z_]\w*/.test(propertyName)) {
|
|
832
|
+
propertyName = propertyName.substring(1);
|
|
833
|
+
}
|
|
834
|
+
let chainedProps = propertyName;
|
|
835
|
+
while (tokens.peek()?.kind === "selector" && tokens.peek().value.startsWith(".") && /^\.[a-zA-Z_]\w*/.test(tokens.peek().value)) {
|
|
836
|
+
chainedProps += tokens.peek().value;
|
|
837
|
+
tokens.advance();
|
|
838
|
+
}
|
|
839
|
+
const nextPeek = tokens.peek();
|
|
840
|
+
if (nextPeek?.kind === "literal" && nextPeek.value.startsWith("(")) {
|
|
841
|
+
chainedProps += nextPeek.value;
|
|
842
|
+
tokens.advance();
|
|
843
|
+
}
|
|
844
|
+
return (0, import_intent.createPropertyPath)((0, import_intent.createReference)(baseRef), chainedProps);
|
|
845
|
+
}
|
|
846
|
+
tokens.reset(mark);
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
function isStructuralKeyword(value) {
|
|
442
850
|
const structural = /* @__PURE__ */ new Set([
|
|
443
851
|
// Prepositions
|
|
444
852
|
"into",
|
|
@@ -651,1195 +1059,64 @@ function tokenToSemanticValue(token) {
|
|
|
651
1059
|
if (isValidReference(lower)) {
|
|
652
1060
|
return (0, import_intent.createReference)(lower);
|
|
653
1061
|
}
|
|
654
|
-
return (0, import_intent.createLiteral)(token.normalized || token.value);
|
|
655
|
-
}
|
|
656
|
-
case "identifier": {
|
|
657
|
-
if (typeof token.value === "string" && token.value.startsWith(":")) {
|
|
658
|
-
return (0, import_intent.createReference)(token.value);
|
|
659
|
-
}
|
|
660
|
-
const identLower = safeToLowerCase(token.value);
|
|
661
|
-
if (isValidReference(identLower)) {
|
|
662
|
-
return (0, import_intent.createReference)(identLower);
|
|
663
|
-
}
|
|
664
|
-
return { type: "expression", raw: token.value };
|
|
665
|
-
}
|
|
666
|
-
case "url":
|
|
667
|
-
return (0, import_intent.createLiteral)(token.value, "string");
|
|
668
|
-
default:
|
|
669
|
-
return null;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
function parseLiteralValue(value) {
|
|
673
|
-
if (value.startsWith('"') || value.startsWith("'") || value.startsWith("`") || value.startsWith("\u300C")) {
|
|
674
|
-
const inner = value.slice(1, -1);
|
|
675
|
-
return (0, import_intent.createLiteral)(inner, "string");
|
|
676
|
-
}
|
|
677
|
-
if (value === "true") return (0, import_intent.createLiteral)(true, "boolean");
|
|
678
|
-
if (value === "false") return (0, import_intent.createLiteral)(false, "boolean");
|
|
679
|
-
const durationMatch = value.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/);
|
|
680
|
-
if (durationMatch) {
|
|
681
|
-
const num2 = parseFloat(durationMatch[1]);
|
|
682
|
-
const unit = durationMatch[2];
|
|
683
|
-
if (unit) {
|
|
684
|
-
return (0, import_intent.createLiteral)(value, "duration");
|
|
685
|
-
}
|
|
686
|
-
return (0, import_intent.createLiteral)(num2, "number");
|
|
687
|
-
}
|
|
688
|
-
const num = parseFloat(value);
|
|
689
|
-
if (!isNaN(num)) {
|
|
690
|
-
return (0, import_intent.createLiteral)(num, "number");
|
|
691
|
-
}
|
|
692
|
-
return (0, import_intent.createLiteral)(value, "string");
|
|
693
|
-
}
|
|
694
|
-
var ENGLISH_NOISE_WORDS = /* @__PURE__ */ new Set(["the", "a", "an"]);
|
|
695
|
-
function skipNoiseWords(tokens) {
|
|
696
|
-
const token = tokens.peek();
|
|
697
|
-
if (!token) return;
|
|
698
|
-
const tokenLower = safeToLowerCase(token.value);
|
|
699
|
-
if (ENGLISH_NOISE_WORDS.has(tokenLower)) {
|
|
700
|
-
const mark = tokens.mark();
|
|
701
|
-
tokens.advance();
|
|
702
|
-
const nextToken = tokens.peek();
|
|
703
|
-
if (nextToken && nextToken.kind === "selector") {
|
|
704
|
-
return;
|
|
705
|
-
}
|
|
706
|
-
tokens.reset(mark);
|
|
707
|
-
}
|
|
708
|
-
if (tokenLower === "class") {
|
|
709
|
-
tokens.advance();
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// src/core/matching/stock-confidence.ts
|
|
714
|
-
function scoreParts(ctx) {
|
|
715
|
-
const { pattern, captured } = ctx;
|
|
716
|
-
let score = 0;
|
|
717
|
-
let maxScore = 0;
|
|
718
|
-
const hasDefault = (role) => {
|
|
719
|
-
return pattern.extraction?.[role]?.default !== void 0;
|
|
720
|
-
};
|
|
721
|
-
for (const token of pattern.template.tokens) {
|
|
722
|
-
if (token.type === "role") {
|
|
723
|
-
maxScore += 1;
|
|
724
|
-
if (captured.has(token.role)) {
|
|
725
|
-
score += 1;
|
|
726
|
-
}
|
|
727
|
-
} else if (token.type === "group") {
|
|
728
|
-
for (const subToken of token.tokens) {
|
|
729
|
-
if (subToken.type === "role") {
|
|
730
|
-
const roleHasDefault = hasDefault(subToken.role);
|
|
731
|
-
const weight = 0.8;
|
|
732
|
-
maxScore += weight;
|
|
733
|
-
if (captured.has(subToken.role)) {
|
|
734
|
-
score += weight;
|
|
735
|
-
} else if (roleHasDefault) {
|
|
736
|
-
score += weight * 0.6;
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
const roleCoverage = maxScore > 0 ? score / maxScore : 1;
|
|
743
|
-
let baseConfidence = roleCoverage;
|
|
744
|
-
let stemPenalty = 0;
|
|
745
|
-
if (ctx.stemMatchCount > 0 && ctx.totalKeywordMatches > 0) {
|
|
746
|
-
stemPenalty = ctx.stemMatchCount / ctx.totalKeywordMatches * 0.15;
|
|
747
|
-
baseConfidence = Math.max(0.5, baseConfidence - stemPenalty);
|
|
748
|
-
}
|
|
749
|
-
const vsoBoost = calculateVSOConfidenceBoost(pattern);
|
|
750
|
-
baseConfidence = Math.min(1, baseConfidence + vsoBoost);
|
|
751
|
-
const prepositionAdjustment = arabicPrepositionDisambiguation(pattern, captured);
|
|
752
|
-
baseConfidence = Math.max(0, Math.min(1, baseConfidence + prepositionAdjustment));
|
|
753
|
-
return {
|
|
754
|
-
roleCoverage,
|
|
755
|
-
stemPenalty,
|
|
756
|
-
languageBoost: vsoBoost,
|
|
757
|
-
languageAdjustment: prepositionAdjustment,
|
|
758
|
-
final: baseConfidence
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
|
-
function calculateVSOConfidenceBoost(pattern) {
|
|
762
|
-
if (pattern.language !== "ar") {
|
|
763
|
-
return 0;
|
|
764
|
-
}
|
|
765
|
-
const firstToken = pattern.template.tokens[0];
|
|
766
|
-
if (!firstToken || firstToken.type !== "literal") {
|
|
767
|
-
return 0;
|
|
768
|
-
}
|
|
769
|
-
const ARABIC_VERBS = /* @__PURE__ */ new Set([
|
|
770
|
-
"\u0628\u062F\u0644",
|
|
771
|
-
"\u063A\u064A\u0631",
|
|
772
|
-
"\u0623\u0636\u0641",
|
|
773
|
-
"\u0623\u0632\u0644",
|
|
774
|
-
"\u0636\u0639",
|
|
775
|
-
"\u0627\u062C\u0639\u0644",
|
|
776
|
-
"\u0639\u064A\u0646",
|
|
777
|
-
"\u0632\u062F",
|
|
778
|
-
"\u0627\u0646\u0642\u0635",
|
|
779
|
-
"\u0633\u062C\u0644",
|
|
780
|
-
"\u0623\u0638\u0647\u0631",
|
|
781
|
-
"\u0623\u062E\u0641",
|
|
782
|
-
"\u0634\u063A\u0644",
|
|
783
|
-
"\u0623\u0631\u0633\u0644",
|
|
784
|
-
"\u0631\u0643\u0632",
|
|
785
|
-
"\u0634\u0648\u0634",
|
|
786
|
-
"\u062A\u0648\u0642\u0641",
|
|
787
|
-
"\u0627\u0646\u0633\u062E",
|
|
788
|
-
"\u0627\u062D\u0630\u0641",
|
|
789
|
-
"\u0627\u0635\u0646\u0639",
|
|
790
|
-
"\u0627\u0646\u062A\u0638\u0631",
|
|
791
|
-
"\u0627\u0646\u062A\u0642\u0627\u0644",
|
|
792
|
-
"\u0623\u0648"
|
|
793
|
-
]);
|
|
794
|
-
if (ARABIC_VERBS.has(firstToken.value)) {
|
|
795
|
-
return 0.15;
|
|
796
|
-
}
|
|
797
|
-
if (firstToken.alternatives) {
|
|
798
|
-
for (const alt of firstToken.alternatives) {
|
|
799
|
-
if (ARABIC_VERBS.has(alt)) {
|
|
800
|
-
return 0.15;
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
return 0;
|
|
805
|
-
}
|
|
806
|
-
function arabicPrepositionDisambiguation(pattern, captured) {
|
|
807
|
-
if (pattern.language !== "ar") {
|
|
808
|
-
return 0;
|
|
809
|
-
}
|
|
810
|
-
let adjustment = 0;
|
|
811
|
-
const PREFERRED_PREPOSITIONS = {
|
|
812
|
-
patient: ["\u0639\u0644\u0649"],
|
|
813
|
-
// element selectors prefer على (on/upon)
|
|
814
|
-
destination: ["\u0625\u0644\u0649", "\u0627\u0644\u0649"],
|
|
815
|
-
// destination prefers إلى (to)
|
|
816
|
-
source: ["\u0645\u0646"],
|
|
817
|
-
// source prefers من (from)
|
|
818
|
-
agent: ["\u0645\u0646"],
|
|
819
|
-
// agent/by prefers من (from/by)
|
|
820
|
-
manner: ["\u0628"],
|
|
821
|
-
// manner prefers ب (with/by)
|
|
822
|
-
style: ["\u0628"],
|
|
823
|
-
// style prefers ب (with)
|
|
824
|
-
goal: ["\u0625\u0644\u0649", "\u0627\u0644\u0649"],
|
|
825
|
-
// target state prefers إلى (to)
|
|
826
|
-
method: ["\u0628"]
|
|
827
|
-
// method prefers ب (with/by)
|
|
828
|
-
};
|
|
829
|
-
for (const [role, value] of captured.entries()) {
|
|
830
|
-
const preferred = PREFERRED_PREPOSITIONS[role];
|
|
831
|
-
if (!preferred || preferred.length === 0) {
|
|
832
|
-
continue;
|
|
833
|
-
}
|
|
834
|
-
const metadata = "metadata" in value ? value.metadata : void 0;
|
|
835
|
-
if (metadata && typeof metadata.prepositionValue === "string") {
|
|
836
|
-
const usedPreposition = metadata.prepositionValue;
|
|
837
|
-
if (preferred.includes(usedPreposition)) {
|
|
838
|
-
adjustment += 0.1;
|
|
839
|
-
} else {
|
|
840
|
-
adjustment -= 0.1;
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
return Math.max(-0.1, Math.min(0.1, adjustment));
|
|
845
|
-
}
|
|
846
|
-
var stockConfidenceModel = {
|
|
847
|
-
calculate: (ctx) => scoreParts(ctx).final,
|
|
848
|
-
calculateWithBreakdown: (ctx) => scoreParts(ctx)
|
|
849
|
-
};
|
|
850
|
-
|
|
851
|
-
// src/core/pattern-matching/pattern-matcher.ts
|
|
852
|
-
var _PatternMatcher = class _PatternMatcher {
|
|
853
|
-
constructor() {
|
|
854
|
-
/** Debug logger */
|
|
855
|
-
this.logger = createLogger("pattern-matcher");
|
|
856
|
-
/**
|
|
857
|
-
* Track stem matches for confidence calculation.
|
|
858
|
-
* This is set during matching and read during confidence calculation.
|
|
859
|
-
*/
|
|
860
|
-
this.stemMatchCount = 0;
|
|
861
|
-
this.totalKeywordMatches = 0;
|
|
862
|
-
}
|
|
863
|
-
/**
|
|
864
|
-
* Try to match a single pattern against the token stream.
|
|
865
|
-
* Returns the match result or null if no match.
|
|
866
|
-
*
|
|
867
|
-
* @param tokens - Token stream to match against
|
|
868
|
-
* @param pattern - Pattern to match
|
|
869
|
-
* @param profile - Optional language profile for possessive handling
|
|
870
|
-
*/
|
|
871
|
-
matchPattern(tokens, pattern, profile) {
|
|
872
|
-
const mark = tokens.mark();
|
|
873
|
-
const captured = /* @__PURE__ */ new Map();
|
|
874
|
-
this.logger.debug("========================================");
|
|
875
|
-
this.logger.debug("matchPattern ENTRY");
|
|
876
|
-
this.logger.debug("Pattern ID:", pattern.id);
|
|
877
|
-
this.logger.debug("Pattern command:", pattern.command);
|
|
878
|
-
this.logger.debug("Pattern language:", pattern.language);
|
|
879
|
-
this.logger.debug("Pattern template:", JSON.stringify(pattern.template, null, 2));
|
|
880
|
-
if (this.logger.isEnabled()) {
|
|
881
|
-
const firstTokens = [];
|
|
882
|
-
for (let i = 0; i < 10; i++) {
|
|
883
|
-
const t = tokens.peek(i);
|
|
884
|
-
if (t)
|
|
885
|
-
firstTokens.push({
|
|
886
|
-
type: t.type,
|
|
887
|
-
value: t.value,
|
|
888
|
-
kind: t.kind
|
|
889
|
-
});
|
|
890
|
-
else break;
|
|
891
|
-
}
|
|
892
|
-
this.logger.debug("Input tokens (first 10):", firstTokens);
|
|
893
|
-
this.logger.debug("Profile code:", profile?.code);
|
|
894
|
-
}
|
|
895
|
-
this.currentProfile = profile;
|
|
896
|
-
this.stemMatchCount = 0;
|
|
897
|
-
this.totalKeywordMatches = 0;
|
|
898
|
-
this.logger.debug("--- Calling matchTokenSequence ---");
|
|
899
|
-
this.logger.debug("Pattern tokens to match:", JSON.stringify(pattern.template.tokens, null, 2));
|
|
900
|
-
const success = this.matchTokenSequence(tokens, pattern.template.tokens, captured);
|
|
901
|
-
this.logger.debug("matchTokenSequence returned:", success);
|
|
902
|
-
this.logger.debug(
|
|
903
|
-
"Captured roles:",
|
|
904
|
-
Array.from(captured.entries()).map(([k, v]) => [k, JSON.stringify(v)])
|
|
905
|
-
);
|
|
906
|
-
if (!success) {
|
|
907
|
-
this.logger.debug(">>> MATCH FAILED - resetting token position");
|
|
908
|
-
tokens.reset(mark);
|
|
909
|
-
return null;
|
|
910
|
-
}
|
|
911
|
-
const confidence = stockConfidenceModel.calculate({
|
|
912
|
-
pattern,
|
|
913
|
-
captured,
|
|
914
|
-
stemMatchCount: this.stemMatchCount,
|
|
915
|
-
totalKeywordMatches: this.totalKeywordMatches
|
|
916
|
-
});
|
|
917
|
-
this.applyExtractionRules(pattern, captured);
|
|
918
|
-
return {
|
|
919
|
-
pattern,
|
|
920
|
-
captured,
|
|
921
|
-
consumedTokens: tokens.position() - mark.position,
|
|
922
|
-
confidence
|
|
923
|
-
};
|
|
924
|
-
}
|
|
925
|
-
/**
|
|
926
|
-
* Try to match multiple patterns, return the best match.
|
|
927
|
-
*
|
|
928
|
-
* @param tokens - Token stream to match against
|
|
929
|
-
* @param patterns - Candidate patterns to try
|
|
930
|
-
* @param profile - Optional language profile for possessive handling
|
|
931
|
-
*/
|
|
932
|
-
matchBest(tokens, patterns, profile) {
|
|
933
|
-
const matches = [];
|
|
934
|
-
for (const pattern of patterns) {
|
|
935
|
-
const mark = tokens.mark();
|
|
936
|
-
const result = this.matchPattern(tokens, pattern, profile);
|
|
937
|
-
if (result) {
|
|
938
|
-
matches.push(result);
|
|
939
|
-
}
|
|
940
|
-
tokens.reset(mark);
|
|
941
|
-
}
|
|
942
|
-
if (matches.length === 0) {
|
|
943
|
-
return null;
|
|
944
|
-
}
|
|
945
|
-
matches.sort((a, b) => {
|
|
946
|
-
const priorityDiff = b.pattern.priority - a.pattern.priority;
|
|
947
|
-
if (priorityDiff !== 0) return priorityDiff;
|
|
948
|
-
const confidenceDiff = b.confidence - a.confidence;
|
|
949
|
-
if (Math.abs(confidenceDiff) > 1e-3) return confidenceDiff;
|
|
950
|
-
return b.consumedTokens - a.consumedTokens;
|
|
951
|
-
});
|
|
952
|
-
const best = matches[0];
|
|
953
|
-
this.matchPattern(tokens, best.pattern);
|
|
954
|
-
return best;
|
|
955
|
-
}
|
|
956
|
-
/**
|
|
957
|
-
* Match a sequence of pattern tokens against the token stream.
|
|
958
|
-
*
|
|
959
|
-
* Supports bounded single-step backtracking: if an optional role consumes
|
|
960
|
-
* a token and the immediately following pattern token fails, the matcher
|
|
961
|
-
* resets to before the optional role and retries the failed token.
|
|
962
|
-
*/
|
|
963
|
-
matchTokenSequence(tokens, patternTokens, captured, parentStopMarkers) {
|
|
964
|
-
const firstPatternToken = patternTokens[0];
|
|
965
|
-
const patternExpectsConjunction = firstPatternToken?.type === "literal" && (firstPatternToken.value === "and" || firstPatternToken.value === "then" || firstPatternToken.alternatives?.includes("and") || firstPatternToken.alternatives?.includes("then"));
|
|
966
|
-
if (this.currentProfile?.code === "ar" && !patternExpectsConjunction) {
|
|
967
|
-
while (tokens.peek()?.kind === "conjunction") {
|
|
968
|
-
tokens.advance();
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
let prevOptionalMark = null;
|
|
972
|
-
let prevOptionalRole = null;
|
|
973
|
-
for (let i = 0; i < patternTokens.length; i++) {
|
|
974
|
-
const patternToken = patternTokens[i];
|
|
975
|
-
this.logger.debug(" >> Matching pattern token:", JSON.stringify(patternToken, null, 2));
|
|
976
|
-
const currTok = tokens.peek();
|
|
977
|
-
this.logger.debug(
|
|
978
|
-
" >> Current input token:",
|
|
979
|
-
currTok ? JSON.stringify({
|
|
980
|
-
type: currTok.type,
|
|
981
|
-
value: currTok.value,
|
|
982
|
-
kind: currTok.kind
|
|
983
|
-
}) : "EOF"
|
|
984
|
-
);
|
|
985
|
-
if (patternToken.type === "role" && patternToken.greedy) {
|
|
986
|
-
const stopMarkers = this.collectStopMarkers(patternTokens, i + 1);
|
|
987
|
-
if (parentStopMarkers) {
|
|
988
|
-
for (const m of parentStopMarkers) stopMarkers.add(m);
|
|
989
|
-
}
|
|
990
|
-
const values = [];
|
|
991
|
-
while (!tokens.isAtEnd()) {
|
|
992
|
-
const nextToken = tokens.peek();
|
|
993
|
-
if (!nextToken) break;
|
|
994
|
-
if (this.isStopMarker(nextToken, stopMarkers)) break;
|
|
995
|
-
values.push(nextToken.value);
|
|
996
|
-
tokens.advance();
|
|
997
|
-
}
|
|
998
|
-
if (values.length > 0) {
|
|
999
|
-
captured.set(patternToken.role, { type: "expression", raw: values.join(" ") });
|
|
1000
|
-
prevOptionalMark = null;
|
|
1001
|
-
prevOptionalRole = null;
|
|
1002
|
-
continue;
|
|
1003
|
-
} else if (patternToken.optional) {
|
|
1004
|
-
continue;
|
|
1005
|
-
} else {
|
|
1006
|
-
return false;
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
if (patternToken.type === "group") {
|
|
1010
|
-
const siblingStopMarkers = this.collectStopMarkers(patternTokens, i + 1);
|
|
1011
|
-
if (parentStopMarkers) {
|
|
1012
|
-
for (const m of parentStopMarkers) siblingStopMarkers.add(m);
|
|
1013
|
-
}
|
|
1014
|
-
const groupMatched = this.matchGroupToken(
|
|
1015
|
-
tokens,
|
|
1016
|
-
patternToken,
|
|
1017
|
-
captured,
|
|
1018
|
-
siblingStopMarkers
|
|
1019
|
-
);
|
|
1020
|
-
if (groupMatched) {
|
|
1021
|
-
prevOptionalMark = null;
|
|
1022
|
-
prevOptionalRole = null;
|
|
1023
|
-
continue;
|
|
1024
|
-
}
|
|
1025
|
-
if (this.isOptional(patternToken)) {
|
|
1026
|
-
continue;
|
|
1027
|
-
}
|
|
1028
|
-
return false;
|
|
1029
|
-
}
|
|
1030
|
-
const isOptionalRole = patternToken.type === "role" && patternToken.optional === true;
|
|
1031
|
-
const markBefore = isOptionalRole ? tokens.mark() : null;
|
|
1032
|
-
const isRoleToken = patternToken.type === "role";
|
|
1033
|
-
if (isRoleToken && !isOptionalRole) {
|
|
1034
|
-
const nextPT = i + 1 < patternTokens.length ? patternTokens[i + 1] : null;
|
|
1035
|
-
if (nextPT?.type === "literal") {
|
|
1036
|
-
const currentToken = tokens.peek();
|
|
1037
|
-
if (currentToken && this.getMatchType(currentToken, nextPT.value) !== "none") {
|
|
1038
|
-
this.logger.debug(
|
|
1039
|
-
" >> MARKER-STEAL GUARD: current token matches next literal, skipping role"
|
|
1040
|
-
);
|
|
1041
|
-
this.logger.debug(" >> Token match FAILED");
|
|
1042
|
-
if (this.isOptional(patternToken)) {
|
|
1043
|
-
continue;
|
|
1044
|
-
}
|
|
1045
|
-
if (prevOptionalMark && prevOptionalRole) {
|
|
1046
|
-
this.logger.debug(" >> BACKTRACKING: undoing optional role", prevOptionalRole);
|
|
1047
|
-
tokens.reset(prevOptionalMark);
|
|
1048
|
-
captured.delete(prevOptionalRole);
|
|
1049
|
-
prevOptionalMark = null;
|
|
1050
|
-
prevOptionalRole = null;
|
|
1051
|
-
const retryMatched = this.matchPatternToken(tokens, patternToken, captured);
|
|
1052
|
-
this.logger.debug(" >> Backtrack retry result:", retryMatched);
|
|
1053
|
-
if (retryMatched) {
|
|
1054
|
-
continue;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
return false;
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
const matched = this.matchPatternToken(tokens, patternToken, captured);
|
|
1062
|
-
this.logger.debug(" >> Match result:", matched);
|
|
1063
|
-
if (matched) {
|
|
1064
|
-
if (isOptionalRole) {
|
|
1065
|
-
prevOptionalMark = markBefore;
|
|
1066
|
-
prevOptionalRole = patternToken.role;
|
|
1067
|
-
} else {
|
|
1068
|
-
prevOptionalMark = null;
|
|
1069
|
-
prevOptionalRole = null;
|
|
1070
|
-
}
|
|
1071
|
-
continue;
|
|
1072
|
-
}
|
|
1073
|
-
this.logger.debug(" >> Token match FAILED");
|
|
1074
|
-
if (this.isOptional(patternToken)) {
|
|
1075
|
-
continue;
|
|
1076
|
-
}
|
|
1077
|
-
if (prevOptionalMark && prevOptionalRole) {
|
|
1078
|
-
this.logger.debug(" >> BACKTRACKING: undoing optional role", prevOptionalRole);
|
|
1079
|
-
tokens.reset(prevOptionalMark);
|
|
1080
|
-
captured.delete(prevOptionalRole);
|
|
1081
|
-
prevOptionalMark = null;
|
|
1082
|
-
prevOptionalRole = null;
|
|
1083
|
-
const retryMatched = this.matchPatternToken(tokens, patternToken, captured);
|
|
1084
|
-
this.logger.debug(" >> Backtrack retry result:", retryMatched);
|
|
1085
|
-
if (retryMatched) {
|
|
1086
|
-
continue;
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
return false;
|
|
1090
|
-
}
|
|
1091
|
-
return true;
|
|
1092
|
-
}
|
|
1093
|
-
/**
|
|
1094
|
-
* Match a single pattern token against the current position in the stream.
|
|
1095
|
-
*/
|
|
1096
|
-
matchPatternToken(tokens, patternToken, captured) {
|
|
1097
|
-
switch (patternToken.type) {
|
|
1098
|
-
case "literal":
|
|
1099
|
-
return this.matchLiteralToken(tokens, patternToken);
|
|
1100
|
-
case "role":
|
|
1101
|
-
return readRoleValue(tokens, patternToken, captured, this.currentProfile);
|
|
1102
|
-
case "group":
|
|
1103
|
-
return this.matchGroupToken(tokens, patternToken, captured);
|
|
1104
|
-
default:
|
|
1105
|
-
return false;
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
/**
|
|
1109
|
-
* Match a literal pattern token (keyword or particle).
|
|
1110
|
-
*/
|
|
1111
|
-
matchLiteralToken(tokens, patternToken) {
|
|
1112
|
-
const token = tokens.peek();
|
|
1113
|
-
this.logger.debug(" >>> matchLiteralToken: expecting", patternToken.value);
|
|
1114
|
-
this.logger.debug(
|
|
1115
|
-
" >>> matchLiteralToken: got token",
|
|
1116
|
-
token ? JSON.stringify({
|
|
1117
|
-
type: token.type,
|
|
1118
|
-
value: token.value,
|
|
1119
|
-
kind: token.kind
|
|
1120
|
-
}) : "null"
|
|
1121
|
-
);
|
|
1122
|
-
if (!token) {
|
|
1123
|
-
this.logger.debug(" >>> matchLiteralToken: FAIL - no token");
|
|
1124
|
-
return false;
|
|
1125
|
-
}
|
|
1126
|
-
const matchType = this.getMatchType(token, patternToken.value);
|
|
1127
|
-
this.logger.debug(
|
|
1128
|
-
" >>> matchType for",
|
|
1129
|
-
token.value,
|
|
1130
|
-
"vs",
|
|
1131
|
-
patternToken.value,
|
|
1132
|
-
":",
|
|
1133
|
-
matchType
|
|
1134
|
-
);
|
|
1135
|
-
if (matchType !== "none") {
|
|
1136
|
-
this.totalKeywordMatches++;
|
|
1137
|
-
if (matchType === "stem") {
|
|
1138
|
-
this.stemMatchCount++;
|
|
1139
|
-
}
|
|
1140
|
-
tokens.advance();
|
|
1141
|
-
return true;
|
|
1142
|
-
}
|
|
1143
|
-
if (patternToken.alternatives) {
|
|
1144
|
-
for (const alt of patternToken.alternatives) {
|
|
1145
|
-
const altMatchType = this.getMatchType(token, alt);
|
|
1146
|
-
if (altMatchType !== "none") {
|
|
1147
|
-
this.totalKeywordMatches++;
|
|
1148
|
-
if (altMatchType === "stem") {
|
|
1149
|
-
this.stemMatchCount++;
|
|
1150
|
-
}
|
|
1151
|
-
tokens.advance();
|
|
1152
|
-
return true;
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
return false;
|
|
1157
|
-
}
|
|
1158
|
-
/**
|
|
1159
|
-
* Match a group pattern token (optional sequence).
|
|
1160
|
-
* When the group's leading marker isn't at the current position, scans ahead
|
|
1161
|
-
* up to MAX_MARKER_SCAN tokens to find it. This allows unmarked tokens
|
|
1162
|
-
* (e.g., a value like 'hello') to sit between groups without blocking later
|
|
1163
|
-
* marker matches.
|
|
1164
|
-
*/
|
|
1165
|
-
matchGroupToken(tokens, patternToken, captured, parentStopMarkers) {
|
|
1166
|
-
const mark = tokens.mark();
|
|
1167
|
-
const capturedBefore = new Set(captured.keys());
|
|
1168
|
-
const success = this.matchTokenSequence(
|
|
1169
|
-
tokens,
|
|
1170
|
-
patternToken.tokens,
|
|
1171
|
-
captured,
|
|
1172
|
-
parentStopMarkers
|
|
1173
|
-
);
|
|
1174
|
-
if (success) return true;
|
|
1175
|
-
tokens.reset(mark);
|
|
1176
|
-
for (const role of captured.keys()) {
|
|
1177
|
-
if (!capturedBefore.has(role)) captured.delete(role);
|
|
1178
|
-
}
|
|
1179
|
-
if (!patternToken.optional) return false;
|
|
1180
|
-
const leadingMarker = this.getGroupLeadingMarker(patternToken);
|
|
1181
|
-
if (leadingMarker) {
|
|
1182
|
-
for (let offset = 1; offset <= _PatternMatcher.MAX_MARKER_SCAN; offset++) {
|
|
1183
|
-
const ahead = tokens.peek(offset);
|
|
1184
|
-
if (!ahead) break;
|
|
1185
|
-
const aheadValue = (ahead.normalized || ahead.value).toLowerCase();
|
|
1186
|
-
if (aheadValue === leadingMarker) {
|
|
1187
|
-
this.logger.debug(
|
|
1188
|
-
" >> MARKER SCAN: found",
|
|
1189
|
-
leadingMarker,
|
|
1190
|
-
"at offset",
|
|
1191
|
-
offset,
|
|
1192
|
-
"- skipping intervening tokens"
|
|
1193
|
-
);
|
|
1194
|
-
for (let s = 0; s < offset; s++) tokens.advance();
|
|
1195
|
-
const retrySuccess = this.matchTokenSequence(
|
|
1196
|
-
tokens,
|
|
1197
|
-
patternToken.tokens,
|
|
1198
|
-
captured,
|
|
1199
|
-
parentStopMarkers
|
|
1200
|
-
);
|
|
1201
|
-
if (retrySuccess) return true;
|
|
1202
|
-
tokens.reset(mark);
|
|
1203
|
-
for (const role of captured.keys()) {
|
|
1204
|
-
if (!capturedBefore.has(role)) captured.delete(role);
|
|
1205
|
-
}
|
|
1206
|
-
break;
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
return true;
|
|
1211
|
-
}
|
|
1212
|
-
/**
|
|
1213
|
-
* Get the leading marker literal from an optional group.
|
|
1214
|
-
* Returns the lowercase marker value, or null if the group
|
|
1215
|
-
* doesn't start with a literal token (e.g., SOV groups where
|
|
1216
|
-
* the role comes before the marker).
|
|
1217
|
-
*/
|
|
1218
|
-
getGroupLeadingMarker(group) {
|
|
1219
|
-
const first = group.tokens[0];
|
|
1220
|
-
if (first?.type === "literal") return first.value.toLowerCase();
|
|
1221
|
-
return null;
|
|
1222
|
-
}
|
|
1223
|
-
/**
|
|
1224
|
-
* Get the type of match for a token against a value.
|
|
1225
|
-
* Used for confidence calculation.
|
|
1226
|
-
*/
|
|
1227
|
-
getMatchType(token, value) {
|
|
1228
|
-
if (token.value === value) return "exact";
|
|
1229
|
-
if (token.normalized === value) return "normalized";
|
|
1230
|
-
if (token.stem === value && token.stemConfidence !== void 0 && token.stemConfidence >= 0.7) {
|
|
1231
|
-
return "stem";
|
|
1232
|
-
}
|
|
1233
|
-
if (token.kind === "keyword" && safeToLowerCase(token.value) === value.toLowerCase()) {
|
|
1234
|
-
return "case-insensitive";
|
|
1235
|
-
}
|
|
1236
|
-
return "none";
|
|
1237
|
-
}
|
|
1238
|
-
/**
|
|
1239
|
-
* Collect literal values from upcoming pattern tokens that act as stop markers
|
|
1240
|
-
* for greedy role capture. Returns the set of lowercase values.
|
|
1241
|
-
*/
|
|
1242
|
-
collectStopMarkers(patternTokens, startIndex) {
|
|
1243
|
-
const markers = /* @__PURE__ */ new Set();
|
|
1244
|
-
for (let j = startIndex; j < patternTokens.length; j++) {
|
|
1245
|
-
const pt = patternTokens[j];
|
|
1246
|
-
if (pt.type === "literal") {
|
|
1247
|
-
markers.add(pt.value.toLowerCase());
|
|
1248
|
-
if (pt.alternatives) {
|
|
1249
|
-
for (const alt of pt.alternatives) {
|
|
1250
|
-
markers.add(alt.toLowerCase());
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
break;
|
|
1254
|
-
}
|
|
1255
|
-
if (pt.type === "group") {
|
|
1256
|
-
for (const gt of pt.tokens) {
|
|
1257
|
-
if (gt.type === "literal") {
|
|
1258
|
-
markers.add(gt.value.toLowerCase());
|
|
1259
|
-
if (gt.alternatives) {
|
|
1260
|
-
for (const alt of gt.alternatives) {
|
|
1261
|
-
markers.add(alt.toLowerCase());
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
break;
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
if (!pt.optional) break;
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
return markers;
|
|
1271
|
-
}
|
|
1272
|
-
/**
|
|
1273
|
-
* Check if a token matches any stop marker for greedy capture.
|
|
1274
|
-
*/
|
|
1275
|
-
isStopMarker(token, stopMarkers) {
|
|
1276
|
-
if (stopMarkers.size === 0) return false;
|
|
1277
|
-
if (stopMarkers.has(token.value.toLowerCase())) return true;
|
|
1278
|
-
if (token.normalized && stopMarkers.has(token.normalized.toLowerCase())) return true;
|
|
1279
|
-
return false;
|
|
1280
|
-
}
|
|
1281
|
-
// ==========================================================================
|
|
1282
|
-
// Depth Limits for Expression Parsing (security hardening)
|
|
1283
|
-
// ==========================================================================
|
|
1284
|
-
/**
|
|
1285
|
-
* Apply extraction rules to fill in static values and defaults for missing roles.
|
|
1286
|
-
*/
|
|
1287
|
-
applyExtractionRules(pattern, captured) {
|
|
1288
|
-
for (const [role, rule] of Object.entries(pattern.extraction)) {
|
|
1289
|
-
if (!captured.has(role)) {
|
|
1290
|
-
if (rule.value !== void 0) {
|
|
1291
|
-
captured.set(role, { type: "literal", value: rule.value });
|
|
1292
|
-
} else if (rule.default) {
|
|
1293
|
-
captured.set(role, rule.default);
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
/**
|
|
1299
|
-
* Check if a pattern token is optional.
|
|
1300
|
-
*/
|
|
1301
|
-
isOptional(patternToken) {
|
|
1302
|
-
return patternToken.type !== "literal" && patternToken.optional === true;
|
|
1303
|
-
}
|
|
1304
|
-
// ===========================================================================
|
|
1305
|
-
// English Idiom Support - Noise Word Handling
|
|
1306
|
-
// ===========================================================================
|
|
1307
|
-
/**
|
|
1308
|
-
* Extract event modifiers from the token stream.
|
|
1309
|
-
* Event modifiers are .once, .debounce(N), .throttle(N), .queue(strategy)
|
|
1310
|
-
* that can appear after event names.
|
|
1311
|
-
*
|
|
1312
|
-
* Returns EventModifiers object or undefined if no modifiers found.
|
|
1313
|
-
*/
|
|
1314
|
-
extractEventModifiers(tokens) {
|
|
1315
|
-
const modifiers = {};
|
|
1316
|
-
let foundModifier = false;
|
|
1317
|
-
while (!tokens.isAtEnd()) {
|
|
1318
|
-
const token = tokens.peek();
|
|
1319
|
-
if (!token || token.kind !== "event-modifier") {
|
|
1320
|
-
break;
|
|
1321
|
-
}
|
|
1322
|
-
const metadata = token.metadata;
|
|
1323
|
-
if (!metadata) {
|
|
1324
|
-
break;
|
|
1325
|
-
}
|
|
1326
|
-
foundModifier = true;
|
|
1327
|
-
switch (metadata.modifierName) {
|
|
1328
|
-
case "once":
|
|
1329
|
-
modifiers.once = true;
|
|
1330
|
-
break;
|
|
1331
|
-
case "debounce":
|
|
1332
|
-
if (typeof metadata.value === "number") {
|
|
1333
|
-
modifiers.debounce = metadata.value;
|
|
1334
|
-
}
|
|
1335
|
-
break;
|
|
1336
|
-
case "throttle":
|
|
1337
|
-
if (typeof metadata.value === "number") {
|
|
1338
|
-
modifiers.throttle = metadata.value;
|
|
1339
|
-
}
|
|
1340
|
-
break;
|
|
1341
|
-
case "queue":
|
|
1342
|
-
if (metadata.value === "first" || metadata.value === "last" || metadata.value === "all" || metadata.value === "none") {
|
|
1343
|
-
modifiers.queue = metadata.value;
|
|
1344
|
-
}
|
|
1345
|
-
break;
|
|
1346
|
-
}
|
|
1347
|
-
tokens.advance();
|
|
1348
|
-
}
|
|
1349
|
-
return foundModifier ? modifiers : void 0;
|
|
1350
|
-
}
|
|
1351
|
-
};
|
|
1352
|
-
/** Maximum tokens to scan ahead when looking for a group's leading marker */
|
|
1353
|
-
_PatternMatcher.MAX_MARKER_SCAN = 3;
|
|
1354
|
-
var PatternMatcher = _PatternMatcher;
|
|
1355
|
-
var patternMatcher = new PatternMatcher();
|
|
1356
|
-
function matchPattern(tokens, pattern) {
|
|
1357
|
-
return patternMatcher.matchPattern(tokens, pattern);
|
|
1358
|
-
}
|
|
1359
|
-
function matchBest(tokens, patterns) {
|
|
1360
|
-
return patternMatcher.matchBest(tokens, patterns);
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
// src/core/tokenization/token-utils.ts
|
|
1364
|
-
var TokenStreamImpl = class {
|
|
1365
|
-
constructor(tokens, language) {
|
|
1366
|
-
this.pos = 0;
|
|
1367
|
-
this.tokens = tokens;
|
|
1368
|
-
this.language = language;
|
|
1369
|
-
}
|
|
1370
|
-
peek(offset = 0) {
|
|
1371
|
-
const index = this.pos + offset;
|
|
1372
|
-
if (index < 0 || index >= this.tokens.length) {
|
|
1373
|
-
return null;
|
|
1374
|
-
}
|
|
1375
|
-
return this.tokens[index];
|
|
1376
|
-
}
|
|
1377
|
-
advance() {
|
|
1378
|
-
if (this.isAtEnd()) {
|
|
1379
|
-
throw new Error("Unexpected end of token stream");
|
|
1380
|
-
}
|
|
1381
|
-
return this.tokens[this.pos++];
|
|
1382
|
-
}
|
|
1383
|
-
isAtEnd() {
|
|
1384
|
-
return this.pos >= this.tokens.length;
|
|
1385
|
-
}
|
|
1386
|
-
mark() {
|
|
1387
|
-
return { position: this.pos };
|
|
1388
|
-
}
|
|
1389
|
-
reset(mark) {
|
|
1390
|
-
this.pos = mark.position;
|
|
1391
|
-
}
|
|
1392
|
-
position() {
|
|
1393
|
-
return this.pos;
|
|
1394
|
-
}
|
|
1395
|
-
/**
|
|
1396
|
-
* Get remaining tokens as an array.
|
|
1397
|
-
*/
|
|
1398
|
-
remaining() {
|
|
1399
|
-
return this.tokens.slice(this.pos);
|
|
1400
|
-
}
|
|
1401
|
-
/**
|
|
1402
|
-
* Consume tokens while predicate is true.
|
|
1403
|
-
*/
|
|
1404
|
-
takeWhile(predicate) {
|
|
1405
|
-
const result = [];
|
|
1406
|
-
while (!this.isAtEnd() && predicate(this.peek())) {
|
|
1407
|
-
result.push(this.advance());
|
|
1408
|
-
}
|
|
1409
|
-
return result;
|
|
1410
|
-
}
|
|
1411
|
-
/**
|
|
1412
|
-
* Skip tokens while predicate is true.
|
|
1413
|
-
*/
|
|
1414
|
-
skipWhile(predicate) {
|
|
1415
|
-
while (!this.isAtEnd() && predicate(this.peek())) {
|
|
1416
|
-
this.advance();
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
};
|
|
1420
|
-
function createPosition(start, end) {
|
|
1421
|
-
return { start, end };
|
|
1422
|
-
}
|
|
1423
|
-
function createToken(valueOrParams, kind, position, normalizedOrOptions) {
|
|
1424
|
-
if (typeof valueOrParams === "object") {
|
|
1425
|
-
const { value: value2, kind: kind2, position: position2, normalized: normalized2, stem, stemConfidence, metadata } = valueOrParams;
|
|
1426
|
-
return {
|
|
1427
|
-
value: value2,
|
|
1428
|
-
kind: kind2,
|
|
1429
|
-
position: position2,
|
|
1430
|
-
...normalized2 !== void 0 && { normalized: normalized2 },
|
|
1431
|
-
...stem !== void 0 && { stem },
|
|
1432
|
-
...stemConfidence !== void 0 && { stemConfidence },
|
|
1433
|
-
...metadata !== void 0 && { metadata }
|
|
1434
|
-
};
|
|
1435
|
-
}
|
|
1436
|
-
const value = valueOrParams;
|
|
1437
|
-
if (!kind || !position) {
|
|
1438
|
-
throw new Error("createToken requires kind and position parameters");
|
|
1439
|
-
}
|
|
1440
|
-
if (typeof normalizedOrOptions === "string") {
|
|
1441
|
-
return { value, kind, position, normalized: normalizedOrOptions };
|
|
1442
|
-
}
|
|
1443
|
-
if (normalizedOrOptions) {
|
|
1444
|
-
const { normalized: normalized2, stem, stemConfidence, metadata } = normalizedOrOptions;
|
|
1445
|
-
return {
|
|
1446
|
-
value,
|
|
1447
|
-
kind,
|
|
1448
|
-
position,
|
|
1449
|
-
...normalized2 !== void 0 && { normalized: normalized2 },
|
|
1450
|
-
...stem !== void 0 && { stem },
|
|
1451
|
-
...stemConfidence !== void 0 && { stemConfidence },
|
|
1452
|
-
...metadata !== void 0 && { metadata }
|
|
1453
|
-
};
|
|
1454
|
-
}
|
|
1455
|
-
return { value, kind, position };
|
|
1456
|
-
}
|
|
1457
|
-
function isWhitespace(char) {
|
|
1458
|
-
return /\s/.test(char);
|
|
1459
|
-
}
|
|
1460
|
-
function isSelectorStart(char) {
|
|
1461
|
-
return char === "#" || char === "." || char === "[" || char === "@" || char === "*" || char === "<";
|
|
1462
|
-
}
|
|
1463
|
-
function isQuote(char) {
|
|
1464
|
-
return char === '"' || char === "'" || char === "`" || char === "\u300C" || char === "\u300D";
|
|
1465
|
-
}
|
|
1466
|
-
function isDigit(char) {
|
|
1467
|
-
return /\d/.test(char);
|
|
1468
|
-
}
|
|
1469
|
-
function stripOptionalDiacritics(word) {
|
|
1470
|
-
return word.replace(/[\u064b-\u0652\u0670]/g, "");
|
|
1471
|
-
}
|
|
1472
|
-
function isAsciiLetter(char) {
|
|
1473
|
-
return /[a-zA-Z]/.test(char);
|
|
1474
|
-
}
|
|
1475
|
-
function isAsciiIdentifierChar(char) {
|
|
1476
|
-
return /[a-zA-Z0-9_-]/.test(char);
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
|
-
// src/core/matching/match-skeleton.ts
|
|
1480
|
-
function groupHasRole(group) {
|
|
1481
|
-
return group.tokens.some((t) => t.type === "role" || t.type === "group" && groupHasRole(t));
|
|
1482
|
-
}
|
|
1483
|
-
var MatchSkeleton = class {
|
|
1484
|
-
/**
|
|
1485
|
-
* @param makeReader - builds the grammar's reader, handed this matcher's view
|
|
1486
|
-
* of the match in progress (one reader per matcher).
|
|
1487
|
-
*/
|
|
1488
|
-
constructor(makeReader, confidenceModel) {
|
|
1489
|
-
/**
|
|
1490
|
-
* The pattern tokens still to match after the one being matched, through
|
|
1491
|
-
* every enclosing group: a value stops at any marker the pattern still owes.
|
|
1492
|
-
*/
|
|
1493
|
-
this.patternRest = [];
|
|
1494
|
-
/**
|
|
1495
|
-
* Whether the role slot being matched directly follows a literal the pattern
|
|
1496
|
-
* matched: its marker (`[por {quantity}]`), or the command's verb.
|
|
1497
|
-
*/
|
|
1498
|
-
this.slotAfterLiteral = false;
|
|
1499
|
-
/**
|
|
1500
|
-
* Selective memoization cache (Phase 6.1).
|
|
1501
|
-
* Caches pattern match results keyed by `tokenPosition:patternId`.
|
|
1502
|
-
* Cleared before each top-level matchBest() call.
|
|
1503
|
-
*/
|
|
1504
|
-
this.matchCache = /* @__PURE__ */ new Map();
|
|
1505
|
-
/**
|
|
1506
|
-
* Stream index where each role's capture began, for the current
|
|
1507
|
-
* `matchPattern` call. Rebuilt per call, written on the same success path as
|
|
1508
|
-
* `captured` — see `PatternMatchResult.roleStarts`.
|
|
1509
|
-
*/
|
|
1510
|
-
this.roleStarts = /* @__PURE__ */ new Map();
|
|
1511
|
-
/**
|
|
1512
|
-
* Track stem matches for confidence calculation.
|
|
1513
|
-
* This is set during matching and read during confidence calculation.
|
|
1514
|
-
*/
|
|
1515
|
-
this.stemMatchCount = 0;
|
|
1516
|
-
this.totalKeywordMatches = 0;
|
|
1517
|
-
this.confidenceModel = confidenceModel;
|
|
1518
|
-
this.reader = makeReader({
|
|
1519
|
-
patternRest: () => this.patternRest,
|
|
1520
|
-
slotAfterLiteral: () => this.slotAfterLiteral,
|
|
1521
|
-
wouldMatch: (pt, token) => this.patternTokenWouldMatch(pt, token)
|
|
1522
|
-
});
|
|
1523
|
-
}
|
|
1524
|
-
/**
|
|
1525
|
-
* Try to match a single pattern against the token stream.
|
|
1526
|
-
* Returns the match result or null if no match.
|
|
1527
|
-
*/
|
|
1528
|
-
matchPattern(tokens, pattern) {
|
|
1529
|
-
const mark = tokens.mark();
|
|
1530
|
-
const captured = /* @__PURE__ */ new Map();
|
|
1531
|
-
this.reader.beginPattern(pattern);
|
|
1532
|
-
this.stemMatchCount = 0;
|
|
1533
|
-
this.totalKeywordMatches = 0;
|
|
1534
|
-
this.roleStarts = /* @__PURE__ */ new Map();
|
|
1535
|
-
this.patternRest = [];
|
|
1536
|
-
const success = this.matchTokenSequence(tokens, pattern.template.tokens, captured);
|
|
1537
|
-
if (!success) {
|
|
1538
|
-
tokens.reset(mark);
|
|
1539
|
-
return null;
|
|
1540
|
-
}
|
|
1541
|
-
const confidence = this.confidenceModel.calculate({
|
|
1542
|
-
pattern,
|
|
1543
|
-
captured,
|
|
1544
|
-
stemMatchCount: this.stemMatchCount,
|
|
1545
|
-
totalKeywordMatches: this.totalKeywordMatches
|
|
1546
|
-
});
|
|
1547
|
-
this.applyExtractionRules(pattern, captured);
|
|
1548
|
-
return {
|
|
1549
|
-
pattern,
|
|
1550
|
-
captured,
|
|
1551
|
-
consumedTokens: tokens.position() - mark.position,
|
|
1552
|
-
confidence,
|
|
1553
|
-
roleStarts: this.roleStarts,
|
|
1554
|
-
...this.reader.resultFields?.()
|
|
1555
|
-
};
|
|
1556
|
-
}
|
|
1557
|
-
/**
|
|
1558
|
-
* Try to match multiple patterns, return the best match.
|
|
1559
|
-
*/
|
|
1560
|
-
matchBest(tokens, patterns) {
|
|
1561
|
-
this.matchCache.clear();
|
|
1562
|
-
const matches = [];
|
|
1563
|
-
const startPos = tokens.position();
|
|
1564
|
-
for (const pattern of patterns) {
|
|
1565
|
-
const mark = tokens.mark();
|
|
1566
|
-
const cacheKey = `${startPos}:${pattern.id}`;
|
|
1567
|
-
let result;
|
|
1568
|
-
if (this.matchCache.has(cacheKey)) {
|
|
1569
|
-
result = this.matchCache.get(cacheKey);
|
|
1570
|
-
} else {
|
|
1571
|
-
result = this.matchPattern(tokens, pattern);
|
|
1572
|
-
this.matchCache.set(cacheKey, result);
|
|
1573
|
-
}
|
|
1574
|
-
if (result) {
|
|
1575
|
-
matches.push(result);
|
|
1576
|
-
}
|
|
1577
|
-
tokens.reset(mark);
|
|
1578
|
-
}
|
|
1579
|
-
if (matches.length === 0) {
|
|
1580
|
-
return null;
|
|
1581
|
-
}
|
|
1582
|
-
matches.sort((a, b) => {
|
|
1583
|
-
const priorityDiff = b.pattern.priority - a.pattern.priority;
|
|
1584
|
-
if (priorityDiff !== 0) return priorityDiff;
|
|
1585
|
-
return b.confidence - a.confidence;
|
|
1586
|
-
});
|
|
1587
|
-
const best = matches[0];
|
|
1588
|
-
this.matchPattern(tokens, best.pattern);
|
|
1589
|
-
return best;
|
|
1590
|
-
}
|
|
1591
|
-
/**
|
|
1592
|
-
* Match a sequence of pattern tokens against the token stream.
|
|
1593
|
-
*/
|
|
1594
|
-
matchTokenSequence(tokens, patternTokens, captured, nextAfterSequence) {
|
|
1595
|
-
this.reader.skipLeading?.(tokens, patternTokens);
|
|
1596
|
-
for (let i = 0; i < patternTokens.length; i++) {
|
|
1597
|
-
const patternToken = patternTokens[i];
|
|
1598
|
-
this.reader.beforePatternToken?.(tokens, patternTokens, i, captured);
|
|
1599
|
-
if (this.reader.maySkipSlot(
|
|
1600
|
-
patternToken,
|
|
1601
|
-
tokens.peek(),
|
|
1602
|
-
patternTokens[i + 1] ?? nextAfterSequence
|
|
1603
|
-
)) {
|
|
1604
|
-
const skipMark = tokens.mark();
|
|
1605
|
-
const speculative = new Map(captured);
|
|
1606
|
-
if (this.matchTokenSequence(
|
|
1607
|
-
tokens,
|
|
1608
|
-
patternTokens.slice(i + 1),
|
|
1609
|
-
speculative,
|
|
1610
|
-
nextAfterSequence
|
|
1611
|
-
) && this.reader.atClauseEnd(tokens)) {
|
|
1612
|
-
captured.clear();
|
|
1613
|
-
for (const [role, value] of speculative) captured.set(role, value);
|
|
1614
|
-
return true;
|
|
1615
|
-
}
|
|
1616
|
-
tokens.reset(skipMark);
|
|
1617
|
-
}
|
|
1618
|
-
const outerRest = this.patternRest;
|
|
1619
|
-
this.patternRest = [...patternTokens.slice(i + 1), ...outerRest];
|
|
1620
|
-
this.slotAfterLiteral = patternToken.type === "role" && patternTokens[i - 1]?.type === "literal";
|
|
1621
|
-
const matched = this.matchPatternToken(
|
|
1622
|
-
tokens,
|
|
1623
|
-
patternToken,
|
|
1624
|
-
captured,
|
|
1625
|
-
patternTokens[i + 1] ?? nextAfterSequence
|
|
1626
|
-
);
|
|
1627
|
-
this.patternRest = outerRest;
|
|
1628
|
-
if (!matched) {
|
|
1629
|
-
if (this.isOptional(patternToken)) {
|
|
1630
|
-
continue;
|
|
1631
|
-
}
|
|
1632
|
-
return false;
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
return true;
|
|
1636
|
-
}
|
|
1637
|
-
/**
|
|
1638
|
-
* Match a single pattern token against the current position in the stream.
|
|
1639
|
-
*/
|
|
1640
|
-
matchPatternToken(tokens, patternToken, captured, nextPatternToken) {
|
|
1641
|
-
switch (patternToken.type) {
|
|
1642
|
-
case "literal":
|
|
1643
|
-
return this.matchLiteralToken(tokens, patternToken);
|
|
1644
|
-
case "role": {
|
|
1645
|
-
const roleStart = tokens.position();
|
|
1646
|
-
const matchedRole = this.reader.readRole(tokens, patternToken, captured, nextPatternToken);
|
|
1647
|
-
if (matchedRole && captured.has(patternToken.role)) {
|
|
1648
|
-
this.roleStarts.set(patternToken.role, roleStart);
|
|
1649
|
-
}
|
|
1650
|
-
return matchedRole;
|
|
1651
|
-
}
|
|
1652
|
-
case "group":
|
|
1653
|
-
return this.matchGroupToken(tokens, patternToken, captured, nextPatternToken);
|
|
1654
|
-
default:
|
|
1655
|
-
return false;
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
|
-
/**
|
|
1659
|
-
* Match a literal pattern token (keyword or particle).
|
|
1660
|
-
*/
|
|
1661
|
-
matchLiteralToken(tokens, patternToken) {
|
|
1662
|
-
const token = tokens.peek();
|
|
1663
|
-
if (!token) return false;
|
|
1664
|
-
const matchType = this.getMatchType(token, patternToken.value);
|
|
1665
|
-
if (matchType !== "none") {
|
|
1666
|
-
this.totalKeywordMatches++;
|
|
1667
|
-
if (matchType === "stem") {
|
|
1668
|
-
this.stemMatchCount++;
|
|
1669
|
-
}
|
|
1670
|
-
tokens.advance();
|
|
1671
|
-
return true;
|
|
1672
|
-
}
|
|
1673
|
-
if (patternToken.alternatives) {
|
|
1674
|
-
for (const alt of patternToken.alternatives) {
|
|
1675
|
-
const altMatchType = this.getMatchType(token, alt);
|
|
1676
|
-
if (altMatchType !== "none") {
|
|
1677
|
-
this.totalKeywordMatches++;
|
|
1678
|
-
if (altMatchType === "stem") {
|
|
1679
|
-
this.stemMatchCount++;
|
|
1680
|
-
}
|
|
1681
|
-
tokens.advance();
|
|
1682
|
-
return true;
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
for (const value of [patternToken.value, ...patternToken.alternatives ?? []]) {
|
|
1687
|
-
for (const sep of ["-", "_"]) {
|
|
1688
|
-
if (value.includes(sep) && this.tryMatchShatteredCompound(tokens, value, sep)) {
|
|
1689
|
-
this.totalKeywordMatches++;
|
|
1690
|
-
return true;
|
|
1691
|
-
}
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
return false;
|
|
1695
|
-
}
|
|
1696
|
-
/**
|
|
1697
|
-
* Match a compound keyword (`titik-henti`, `по_умолчанию`) against a
|
|
1698
|
-
* shattered token run (`titik` `-` `henti` / `по` `_` `умолчанию`): word
|
|
1699
|
-
* segments must be identifier/keyword/particle tokens equal to the expected
|
|
1700
|
-
* segments (case-insensitive; particle because prepositions like ru `по` /
|
|
1701
|
-
* uk `за` lead some underscore compounds), separators lone `sep` tokens.
|
|
1702
|
-
* Consumes the run on success, resets the stream on failure.
|
|
1703
|
-
*/
|
|
1704
|
-
tryMatchShatteredCompound(tokens, expected, sep) {
|
|
1705
|
-
const segments = expected.split(sep);
|
|
1706
|
-
if (segments.length < 2 || segments.some((s) => s.length === 0)) return false;
|
|
1707
|
-
const mark = tokens.mark();
|
|
1708
|
-
for (let i = 0; i < segments.length; i++) {
|
|
1709
|
-
if (i > 0) {
|
|
1710
|
-
const sepToken = tokens.peek();
|
|
1711
|
-
if (!sepToken || sepToken.value !== sep) {
|
|
1712
|
-
tokens.reset(mark);
|
|
1713
|
-
return false;
|
|
1714
|
-
}
|
|
1715
|
-
tokens.advance();
|
|
1716
|
-
}
|
|
1717
|
-
const word = tokens.peek();
|
|
1718
|
-
if (!word || word.kind !== "identifier" && word.kind !== "keyword" && word.kind !== "particle" || word.value.toLowerCase() !== segments[i].toLowerCase()) {
|
|
1719
|
-
tokens.reset(mark);
|
|
1720
|
-
return false;
|
|
1721
|
-
}
|
|
1722
|
-
tokens.advance();
|
|
1723
|
-
}
|
|
1724
|
-
return true;
|
|
1725
|
-
}
|
|
1726
|
-
/**
|
|
1727
|
-
* Match a group pattern token (optional sequence).
|
|
1728
|
-
*/
|
|
1729
|
-
matchGroupToken(tokens, patternToken, captured, nextPatternToken) {
|
|
1730
|
-
const mark = tokens.mark();
|
|
1731
|
-
const capturedBefore = new Map(captured);
|
|
1732
|
-
let success = this.matchTokenSequence(tokens, patternToken.tokens, captured, nextPatternToken);
|
|
1733
|
-
if (success && tokens.position() > mark.position && groupHasRole(patternToken) && [...captured].every(([role, value]) => capturedBefore.get(role) === value) && captured.size === capturedBefore.size) {
|
|
1734
|
-
success = false;
|
|
1735
|
-
}
|
|
1736
|
-
if (success) {
|
|
1737
|
-
const markerLiterals = [];
|
|
1738
|
-
for (const inner of patternToken.tokens) {
|
|
1739
|
-
if (inner.type === "group" && inner.renderRequired) {
|
|
1740
|
-
for (const t of inner.tokens) {
|
|
1741
|
-
if (t.type === "literal") markerLiterals.push(t.value, ...t.alternatives ?? []);
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
if (markerLiterals.length > 0) {
|
|
1746
|
-
const addedRole = [...captured.keys()].some((role) => !capturedBefore.has(role));
|
|
1747
|
-
if (addedRole) {
|
|
1748
|
-
const last = tokens.tokens[tokens.position() - 1];
|
|
1749
|
-
const markerSeen = last !== void 0 && tokens.position() > mark.position && markerLiterals.some((lit) => this.getMatchType(last, lit) !== "none");
|
|
1750
|
-
if (!markerSeen) success = false;
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
if (!success) {
|
|
1755
|
-
tokens.reset(mark);
|
|
1756
|
-
for (const role of captured.keys()) {
|
|
1757
|
-
if (!capturedBefore.has(role)) {
|
|
1758
|
-
captured.delete(role);
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
for (const [role, value] of capturedBefore) {
|
|
1762
|
-
if (captured.get(role) !== value) {
|
|
1763
|
-
captured.set(role, value);
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
return patternToken.optional || false;
|
|
1767
|
-
}
|
|
1768
|
-
return true;
|
|
1769
|
-
}
|
|
1770
|
-
/**
|
|
1771
|
-
* Whether a pattern token (literal, or a group starting with a literal) would
|
|
1772
|
-
* match the given stream token. Used to keep the event-head source-clause
|
|
1773
|
-
* consumption from stealing a marker the pattern explicitly expects.
|
|
1774
|
-
*/
|
|
1775
|
-
patternTokenWouldMatch(pt, token) {
|
|
1776
|
-
if (!pt) return false;
|
|
1777
|
-
if (pt.type === "literal") {
|
|
1778
|
-
if (this.getMatchType(token, pt.value) !== "none") return true;
|
|
1779
|
-
return !!pt.alternatives?.some((a) => this.getMatchType(token, a) !== "none");
|
|
1780
|
-
}
|
|
1781
|
-
if (pt.type === "group") {
|
|
1782
|
-
return this.patternTokenWouldMatch(pt.tokens?.[0], token);
|
|
1783
|
-
}
|
|
1784
|
-
return false;
|
|
1785
|
-
}
|
|
1786
|
-
/**
|
|
1787
|
-
* Get the type of match for a token against a value.
|
|
1788
|
-
* Used for confidence calculation.
|
|
1789
|
-
*/
|
|
1790
|
-
getMatchType(token, value) {
|
|
1791
|
-
if (token.value === value) return "exact";
|
|
1792
|
-
if (token.normalized === value) return "normalized";
|
|
1793
|
-
if (token.stem === value && token.stemConfidence !== void 0 && token.stemConfidence >= 0.7) {
|
|
1794
|
-
return "stem";
|
|
1795
|
-
}
|
|
1796
|
-
if (token.kind === "keyword" && token.value.toLowerCase() === value.toLowerCase()) {
|
|
1797
|
-
return "case-insensitive";
|
|
1798
|
-
}
|
|
1799
|
-
if (token.kind === "keyword") {
|
|
1800
|
-
const strippedToken = stripOptionalDiacritics(token.value).toLowerCase();
|
|
1801
|
-
if (strippedToken && strippedToken === stripOptionalDiacritics(value).toLowerCase()) {
|
|
1802
|
-
return "case-insensitive";
|
|
1062
|
+
return (0, import_intent.createLiteral)(token.normalized || token.value);
|
|
1063
|
+
}
|
|
1064
|
+
case "identifier": {
|
|
1065
|
+
if (typeof token.value === "string" && token.value.startsWith(":")) {
|
|
1066
|
+
return (0, import_intent.createReference)(token.value);
|
|
1067
|
+
}
|
|
1068
|
+
const identLower = safeToLowerCase(token.value);
|
|
1069
|
+
if (isValidReference(identLower)) {
|
|
1070
|
+
return (0, import_intent.createReference)(identLower);
|
|
1803
1071
|
}
|
|
1072
|
+
return { type: "expression", raw: token.value };
|
|
1804
1073
|
}
|
|
1805
|
-
|
|
1074
|
+
case "url":
|
|
1075
|
+
return (0, import_intent.createLiteral)(token.value, "string");
|
|
1076
|
+
default:
|
|
1077
|
+
return null;
|
|
1806
1078
|
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
}
|
|
1822
|
-
} else if (rule.value !== void 0) {
|
|
1823
|
-
captured.set(
|
|
1824
|
-
role,
|
|
1825
|
-
rule.valueIsExpression ? { type: "expression", raw: rule.value } : { type: "literal", value: rule.value }
|
|
1826
|
-
);
|
|
1827
|
-
} else if (rule.default) {
|
|
1828
|
-
const implicitDefault = {
|
|
1829
|
-
...rule.default,
|
|
1830
|
-
implicit: true
|
|
1831
|
-
};
|
|
1832
|
-
captured.set(role, implicitDefault);
|
|
1833
|
-
}
|
|
1079
|
+
}
|
|
1080
|
+
function parseLiteralValue(value) {
|
|
1081
|
+
if (value.startsWith('"') || value.startsWith("'") || value.startsWith("`") || value.startsWith("\u300C")) {
|
|
1082
|
+
const inner = value.slice(1, -1);
|
|
1083
|
+
return (0, import_intent.createLiteral)(inner, "string");
|
|
1084
|
+
}
|
|
1085
|
+
if (value === "true") return (0, import_intent.createLiteral)(true, "boolean");
|
|
1086
|
+
if (value === "false") return (0, import_intent.createLiteral)(false, "boolean");
|
|
1087
|
+
const durationMatch = value.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/);
|
|
1088
|
+
if (durationMatch) {
|
|
1089
|
+
const num2 = parseFloat(durationMatch[1]);
|
|
1090
|
+
const unit = durationMatch[2];
|
|
1091
|
+
if (unit) {
|
|
1092
|
+
return (0, import_intent.createLiteral)(value, "duration");
|
|
1834
1093
|
}
|
|
1094
|
+
return (0, import_intent.createLiteral)(num2, "number");
|
|
1835
1095
|
}
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
isOptional(patternToken) {
|
|
1840
|
-
return patternToken.type !== "literal" && patternToken.optional === true;
|
|
1096
|
+
const num = parseFloat(value);
|
|
1097
|
+
if (!isNaN(num)) {
|
|
1098
|
+
return (0, import_intent.createLiteral)(num, "number");
|
|
1841
1099
|
}
|
|
1842
|
-
|
|
1100
|
+
return (0, import_intent.createLiteral)(value, "string");
|
|
1101
|
+
}
|
|
1102
|
+
var ENGLISH_NOISE_WORDS = /* @__PURE__ */ new Set(["the", "a", "an"]);
|
|
1103
|
+
function skipNoiseWords(tokens) {
|
|
1104
|
+
const token = tokens.peek();
|
|
1105
|
+
if (!token) return;
|
|
1106
|
+
const tokenLower = safeToLowerCase(token.value);
|
|
1107
|
+
if (ENGLISH_NOISE_WORDS.has(tokenLower)) {
|
|
1108
|
+
const mark = tokens.mark();
|
|
1109
|
+
tokens.advance();
|
|
1110
|
+
const nextToken = tokens.peek();
|
|
1111
|
+
if (nextToken && nextToken.kind === "selector") {
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
tokens.reset(mark);
|
|
1115
|
+
}
|
|
1116
|
+
if (tokenLower === "class") {
|
|
1117
|
+
tokens.advance();
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1843
1120
|
|
|
1844
1121
|
// src/core/matching/stock-reader.ts
|
|
1845
1122
|
function greedyStopMarkers(rest) {
|
|
@@ -1876,6 +1153,11 @@ function readGreedy(tokens, stopMarkers) {
|
|
|
1876
1153
|
}
|
|
1877
1154
|
return values.length > 0 ? { type: "expression", raw: values.join(" ") } : null;
|
|
1878
1155
|
}
|
|
1156
|
+
function isListedWord(token, words) {
|
|
1157
|
+
if (!token) return false;
|
|
1158
|
+
const forms = [token.value, token.normalized].filter((f) => typeof f === "string");
|
|
1159
|
+
return forms.some((form) => words.some((word) => word.toLowerCase() === form.toLowerCase()));
|
|
1160
|
+
}
|
|
1879
1161
|
var StockValueReader = class {
|
|
1880
1162
|
constructor(view, profiles = {}) {
|
|
1881
1163
|
this.view = view;
|
|
@@ -1908,6 +1190,11 @@ var StockValueReader = class {
|
|
|
1908
1190
|
const token = tokens.peek();
|
|
1909
1191
|
if (token && this.view.wouldMatch(next, token)) return false;
|
|
1910
1192
|
}
|
|
1193
|
+
if (slot.words && !isListedWord(tokens.peek(), slot.words)) return slot.optional ?? false;
|
|
1194
|
+
if (slot.optional && !slot.greedy) {
|
|
1195
|
+
const token = tokens.peek();
|
|
1196
|
+
if (token && isStopMarker(token, greedyStopMarkers(this.view.patternRest()))) return true;
|
|
1197
|
+
}
|
|
1911
1198
|
if (slot.greedy) {
|
|
1912
1199
|
const value = readGreedy(tokens, greedyStopMarkers(this.view.patternRest()));
|
|
1913
1200
|
if (value) {
|
|
@@ -1931,284 +1218,151 @@ function createStockReader(profiles = {}) {
|
|
|
1931
1218
|
return (view) => new StockValueReader(view, profiles);
|
|
1932
1219
|
}
|
|
1933
1220
|
|
|
1934
|
-
// src/
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
const priority = config.basePriority ?? 100;
|
|
1942
|
-
const keyword = profile.keywords[schema.action];
|
|
1943
|
-
if (!keyword) {
|
|
1944
|
-
throw new Error(`No keyword translation for '${schema.action}' in ${profile.code}`);
|
|
1945
|
-
}
|
|
1946
|
-
const tokens = buildTokens(schema, profile, keyword.primary, keyword.alternatives);
|
|
1947
|
-
const extraction = buildExtractionRules(schema, profile);
|
|
1948
|
-
const format = buildFormatString(schema, profile, keyword.primary);
|
|
1949
|
-
return {
|
|
1950
|
-
id,
|
|
1951
|
-
language: profile.code,
|
|
1952
|
-
command: schema.action,
|
|
1953
|
-
priority,
|
|
1954
|
-
template: {
|
|
1955
|
-
format,
|
|
1956
|
-
tokens
|
|
1957
|
-
},
|
|
1958
|
-
extraction
|
|
1959
|
-
};
|
|
1960
|
-
}
|
|
1961
|
-
function buildTokens(schema, profile, keyword, alternatives) {
|
|
1962
|
-
const tokens = [];
|
|
1963
|
-
const keywordToken = {
|
|
1964
|
-
type: "literal",
|
|
1965
|
-
value: keyword,
|
|
1966
|
-
...alternatives?.length && { alternatives }
|
|
1967
|
-
};
|
|
1968
|
-
const sortedRoles = sortRolesByWordOrder(schema.roles, profile.wordOrder);
|
|
1969
|
-
if (profile.wordOrder === "SVO") {
|
|
1970
|
-
tokens.push(keywordToken);
|
|
1971
|
-
for (const role of sortedRoles) {
|
|
1972
|
-
addRoleWithMarker(tokens, role, profile);
|
|
1973
|
-
}
|
|
1974
|
-
} else if (profile.wordOrder === "SOV") {
|
|
1975
|
-
const preVerb = [];
|
|
1976
|
-
const postVerb = [];
|
|
1977
|
-
for (const role of sortedRoles) {
|
|
1978
|
-
addRoleWithMarker(role.sovSlot === "postVerb" ? postVerb : preVerb, role, profile);
|
|
1979
|
-
}
|
|
1980
|
-
tokens.push(...preVerb, keywordToken, ...postVerb);
|
|
1981
|
-
} else if (profile.wordOrder === "VSO") {
|
|
1982
|
-
tokens.push(keywordToken);
|
|
1983
|
-
for (const role of sortedRoles) {
|
|
1984
|
-
addRoleWithMarker(tokens, role, profile);
|
|
1985
|
-
}
|
|
1986
|
-
} else {
|
|
1987
|
-
tokens.push(keywordToken);
|
|
1988
|
-
for (const role of sortedRoles) {
|
|
1989
|
-
addRoleWithMarker(tokens, role, profile);
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
return tokens;
|
|
1993
|
-
}
|
|
1994
|
-
function addRoleWithMarker(tokens, roleSpec, profile) {
|
|
1995
|
-
const marker = getMarkerForRole(roleSpec, profile);
|
|
1996
|
-
const isOptional = !roleSpec.required;
|
|
1997
|
-
const roleToken = {
|
|
1998
|
-
type: "role",
|
|
1999
|
-
role: roleSpec.role,
|
|
2000
|
-
optional: isOptional,
|
|
2001
|
-
expectedTypes: roleSpec.expectedTypes,
|
|
2002
|
-
...roleSpec.greedy && { greedy: true }
|
|
1221
|
+
// src/core/matching/stock-confidence.ts
|
|
1222
|
+
function scoreParts(ctx) {
|
|
1223
|
+
const { pattern, captured } = ctx;
|
|
1224
|
+
let score = 0;
|
|
1225
|
+
let maxScore = 0;
|
|
1226
|
+
const hasDefault = (role) => {
|
|
1227
|
+
return pattern.extraction?.[role]?.default !== void 0;
|
|
2003
1228
|
};
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
if (position === "before") {
|
|
2010
|
-
tokens.push({
|
|
2011
|
-
type: "group",
|
|
2012
|
-
optional: true,
|
|
2013
|
-
tokens: [{ type: "literal", value: marker }, roleToken]
|
|
2014
|
-
});
|
|
2015
|
-
} else {
|
|
2016
|
-
tokens.push({
|
|
2017
|
-
type: "group",
|
|
2018
|
-
optional: true,
|
|
2019
|
-
tokens: [roleToken, { type: "literal", value: marker }]
|
|
2020
|
-
});
|
|
1229
|
+
for (const token of pattern.template.tokens) {
|
|
1230
|
+
if (token.type === "role") {
|
|
1231
|
+
maxScore += 1;
|
|
1232
|
+
if (captured.has(token.role)) {
|
|
1233
|
+
score += 1;
|
|
2021
1234
|
}
|
|
2022
|
-
} else {
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
1235
|
+
} else if (token.type === "group") {
|
|
1236
|
+
for (const subToken of token.tokens) {
|
|
1237
|
+
if (subToken.type === "role") {
|
|
1238
|
+
const roleHasDefault = hasDefault(subToken.role);
|
|
1239
|
+
const weight = 0.8;
|
|
1240
|
+
maxScore += weight;
|
|
1241
|
+
if (captured.has(subToken.role)) {
|
|
1242
|
+
score += weight;
|
|
1243
|
+
} else if (roleHasDefault) {
|
|
1244
|
+
score += weight * 0.6;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
2029
1247
|
}
|
|
2030
1248
|
}
|
|
2031
|
-
} else {
|
|
2032
|
-
tokens.push(roleToken);
|
|
2033
|
-
}
|
|
2034
|
-
}
|
|
2035
|
-
function getMarkerForRole(roleSpec, profile) {
|
|
2036
|
-
if (roleSpec.markerOverride?.[profile.code]) {
|
|
2037
|
-
return roleSpec.markerOverride[profile.code];
|
|
2038
|
-
}
|
|
2039
|
-
return profile.roleMarkers?.[roleSpec.role]?.primary;
|
|
2040
|
-
}
|
|
2041
|
-
function sortRolesByWordOrder(roles, wordOrder) {
|
|
2042
|
-
const sorted = [...roles];
|
|
2043
|
-
if (wordOrder === "SVO") {
|
|
2044
|
-
sorted.sort((a, b) => (b.svoPosition ?? 0) - (a.svoPosition ?? 0));
|
|
2045
|
-
} else if (wordOrder === "SOV") {
|
|
2046
|
-
sorted.sort((a, b) => (b.sovPosition ?? 0) - (a.sovPosition ?? 0));
|
|
2047
1249
|
}
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
const rule = {
|
|
2055
|
-
...roleSpec.default !== void 0 && { default: roleSpec.default },
|
|
2056
|
-
...marker && { marker }
|
|
2057
|
-
};
|
|
2058
|
-
rules[roleSpec.role] = rule;
|
|
1250
|
+
const roleCoverage = maxScore > 0 ? score / maxScore : 1;
|
|
1251
|
+
let baseConfidence = roleCoverage;
|
|
1252
|
+
let stemPenalty = 0;
|
|
1253
|
+
if (ctx.stemMatchCount > 0 && ctx.totalKeywordMatches > 0) {
|
|
1254
|
+
stemPenalty = ctx.stemMatchCount / ctx.totalKeywordMatches * 0.15;
|
|
1255
|
+
baseConfidence = Math.max(0.5, baseConfidence - stemPenalty);
|
|
2059
1256
|
}
|
|
2060
|
-
|
|
1257
|
+
const vsoBoost = calculateVSOConfidenceBoost(pattern);
|
|
1258
|
+
baseConfidence = Math.min(1, baseConfidence + vsoBoost);
|
|
1259
|
+
const prepositionAdjustment = arabicPrepositionDisambiguation(pattern, captured);
|
|
1260
|
+
baseConfidence = Math.max(0, Math.min(1, baseConfidence + prepositionAdjustment));
|
|
1261
|
+
return {
|
|
1262
|
+
roleCoverage,
|
|
1263
|
+
stemPenalty,
|
|
1264
|
+
languageBoost: vsoBoost,
|
|
1265
|
+
languageAdjustment: prepositionAdjustment,
|
|
1266
|
+
final: baseConfidence
|
|
1267
|
+
};
|
|
2061
1268
|
}
|
|
2062
|
-
function
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
const postVerb = [];
|
|
2066
|
-
const sortedRoles = sortRolesByWordOrder(schema.roles, profile.wordOrder);
|
|
2067
|
-
for (const role of sortedRoles) {
|
|
2068
|
-
const marker = getMarkerForRole(role, profile);
|
|
2069
|
-
const roleName = `{${role.role}}`;
|
|
2070
|
-
const bucket = isSOV && role.sovSlot === "postVerb" ? postVerb : preVerb;
|
|
2071
|
-
if (marker) {
|
|
2072
|
-
const markerInfo = profile.roleMarkers?.[role.role];
|
|
2073
|
-
if (markerInfo?.position === "after") {
|
|
2074
|
-
bucket.push(`${roleName} ${marker}`);
|
|
2075
|
-
} else {
|
|
2076
|
-
bucket.push(`${marker} ${roleName}`);
|
|
2077
|
-
}
|
|
2078
|
-
} else {
|
|
2079
|
-
bucket.push(roleName);
|
|
2080
|
-
}
|
|
1269
|
+
function calculateVSOConfidenceBoost(pattern) {
|
|
1270
|
+
if (pattern.language !== "ar") {
|
|
1271
|
+
return 0;
|
|
2081
1272
|
}
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
}
|
|
2086
|
-
function generatePatternVariants(schema, profile, config = defaultConfig) {
|
|
2087
|
-
const patterns = [];
|
|
2088
|
-
patterns.push(generatePattern(schema, profile, config));
|
|
2089
|
-
return patterns;
|
|
2090
|
-
}
|
|
2091
|
-
|
|
2092
|
-
// src/generation/renderer.ts
|
|
2093
|
-
function lookupKeyword(keywords, action, language) {
|
|
2094
|
-
return keywords[action]?.[language] ?? action;
|
|
2095
|
-
}
|
|
2096
|
-
function lookupMarker(markers, marker, language) {
|
|
2097
|
-
return markers[marker]?.[language] ?? marker;
|
|
2098
|
-
}
|
|
2099
|
-
function buildPhrase(...parts) {
|
|
2100
|
-
return parts.filter(Boolean).join(" ");
|
|
2101
|
-
}
|
|
2102
|
-
function buildTablesFromProfiles(schemas, profiles) {
|
|
2103
|
-
const keywords = {};
|
|
2104
|
-
const markers = {};
|
|
2105
|
-
for (const profile of profiles) {
|
|
2106
|
-
for (const [action, kw] of Object.entries(profile.keywords)) {
|
|
2107
|
-
if (!keywords[action]) keywords[action] = {};
|
|
2108
|
-
keywords[action][profile.code] = kw.primary;
|
|
2109
|
-
}
|
|
1273
|
+
const firstToken = pattern.template.tokens[0];
|
|
1274
|
+
if (!firstToken || firstToken.type !== "literal") {
|
|
1275
|
+
return 0;
|
|
2110
1276
|
}
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
1277
|
+
const ARABIC_VERBS = /* @__PURE__ */ new Set([
|
|
1278
|
+
"\u0628\u062F\u0644",
|
|
1279
|
+
"\u063A\u064A\u0631",
|
|
1280
|
+
"\u0623\u0636\u0641",
|
|
1281
|
+
"\u0623\u0632\u0644",
|
|
1282
|
+
"\u0636\u0639",
|
|
1283
|
+
"\u0627\u062C\u0639\u0644",
|
|
1284
|
+
"\u0639\u064A\u0646",
|
|
1285
|
+
"\u0632\u062F",
|
|
1286
|
+
"\u0627\u0646\u0642\u0635",
|
|
1287
|
+
"\u0633\u062C\u0644",
|
|
1288
|
+
"\u0623\u0638\u0647\u0631",
|
|
1289
|
+
"\u0623\u062E\u0641",
|
|
1290
|
+
"\u0634\u063A\u0644",
|
|
1291
|
+
"\u0623\u0631\u0633\u0644",
|
|
1292
|
+
"\u0631\u0643\u0632",
|
|
1293
|
+
"\u0634\u0648\u0634",
|
|
1294
|
+
"\u062A\u0648\u0642\u0641",
|
|
1295
|
+
"\u0627\u0646\u0633\u062E",
|
|
1296
|
+
"\u0627\u062D\u0630\u0641",
|
|
1297
|
+
"\u0627\u0635\u0646\u0639",
|
|
1298
|
+
"\u0627\u0646\u062A\u0638\u0631",
|
|
1299
|
+
"\u0627\u0646\u062A\u0642\u0627\u0644",
|
|
1300
|
+
"\u0623\u0648"
|
|
1301
|
+
]);
|
|
1302
|
+
if (ARABIC_VERBS.has(firstToken.value)) {
|
|
1303
|
+
return 0.15;
|
|
2121
1304
|
}
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
markers[markerKey][profile.code] = markerDef.primary;
|
|
1305
|
+
if (firstToken.alternatives) {
|
|
1306
|
+
for (const alt of firstToken.alternatives) {
|
|
1307
|
+
if (ARABIC_VERBS.has(alt)) {
|
|
1308
|
+
return 0.15;
|
|
2127
1309
|
}
|
|
2128
1310
|
}
|
|
2129
1311
|
}
|
|
2130
|
-
return
|
|
1312
|
+
return 0;
|
|
2131
1313
|
}
|
|
2132
|
-
function
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
for (const profile of profiles) {
|
|
2136
|
-
if (profile.wordOrder === "SOV") sovLanguages.add(profile.code);
|
|
2137
|
-
if (profile.wordOrder === "VSO") vsoLanguages.add(profile.code);
|
|
1314
|
+
function arabicPrepositionDisambiguation(pattern, captured) {
|
|
1315
|
+
if (pattern.language !== "ar") {
|
|
1316
|
+
return 0;
|
|
2138
1317
|
}
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
1318
|
+
let adjustment = 0;
|
|
1319
|
+
const PREFERRED_PREPOSITIONS = {
|
|
1320
|
+
patient: ["\u0639\u0644\u0649"],
|
|
1321
|
+
// element selectors prefer على (on/upon)
|
|
1322
|
+
destination: ["\u0625\u0644\u0649", "\u0627\u0644\u0649"],
|
|
1323
|
+
// destination prefers إلى (to)
|
|
1324
|
+
source: ["\u0645\u0646"],
|
|
1325
|
+
// source prefers من (from)
|
|
1326
|
+
agent: ["\u0645\u0646"],
|
|
1327
|
+
// agent/by prefers من (from/by)
|
|
1328
|
+
manner: ["\u0628"],
|
|
1329
|
+
// manner prefers ب (with/by)
|
|
1330
|
+
style: ["\u0628"],
|
|
1331
|
+
// style prefers ب (with)
|
|
1332
|
+
goal: ["\u0625\u0644\u0649", "\u0627\u0644\u0649"],
|
|
1333
|
+
// target state prefers إلى (to)
|
|
1334
|
+
method: ["\u0628"]
|
|
1335
|
+
// method prefers ب (with/by)
|
|
2157
1336
|
};
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
const markerPositions = {};
|
|
2163
|
-
for (const profile of profiles) {
|
|
2164
|
-
for (const [role, markerDef] of Object.entries(profile.roleMarkers ?? {})) {
|
|
2165
|
-
if (!markerDef.position) continue;
|
|
2166
|
-
markerPositions[role] ?? (markerPositions[role] = {});
|
|
2167
|
-
markerPositions[role][profile.code] = markerDef.position;
|
|
2168
|
-
}
|
|
2169
|
-
}
|
|
2170
|
-
const schemaMap = /* @__PURE__ */ new Map();
|
|
2171
|
-
for (const s of schemas) schemaMap.set(s.action, s);
|
|
2172
|
-
return { keywords, markers, markerPositions, sovLanguages, schemaMap };
|
|
2173
|
-
}
|
|
2174
|
-
function renderFromSchema(node, language, tables) {
|
|
2175
|
-
const schema = tables.schemaMap.get(node.action);
|
|
2176
|
-
if (!schema) return null;
|
|
2177
|
-
const keyword = lookupKeyword(tables.keywords, node.action, language);
|
|
2178
|
-
const isSOV = tables.sovLanguages.has(language);
|
|
2179
|
-
const ordered = sortRolesByWordOrder([...schema.roles], isSOV ? "SOV" : "SVO");
|
|
2180
|
-
const preVerb = [];
|
|
2181
|
-
const postVerb = [];
|
|
2182
|
-
for (const role of ordered) {
|
|
2183
|
-
let value = (0, import_intent.extractRoleValue)(node, role.role);
|
|
2184
|
-
if (!value && role.default !== void 0) {
|
|
2185
|
-
value = String((0, import_intent.extractValue)(role.default));
|
|
2186
|
-
}
|
|
2187
|
-
if (!value) continue;
|
|
2188
|
-
if (role.quoteMultiword && /\s/.test(value) && !/^(["']).*\1$/.test(value)) {
|
|
2189
|
-
value = `"${value}"`;
|
|
1337
|
+
for (const [role, value] of captured.entries()) {
|
|
1338
|
+
const preferred = PREFERRED_PREPOSITIONS[role];
|
|
1339
|
+
if (!preferred || preferred.length === 0) {
|
|
1340
|
+
continue;
|
|
2190
1341
|
}
|
|
2191
|
-
const
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
bucket.push(value);
|
|
1342
|
+
const metadata = "metadata" in value ? value.metadata : void 0;
|
|
1343
|
+
if (metadata && typeof metadata.prepositionValue === "string") {
|
|
1344
|
+
const usedPreposition = metadata.prepositionValue;
|
|
1345
|
+
if (preferred.includes(usedPreposition)) {
|
|
1346
|
+
adjustment += 0.1;
|
|
1347
|
+
} else {
|
|
1348
|
+
adjustment -= 0.1;
|
|
1349
|
+
}
|
|
2200
1350
|
}
|
|
2201
1351
|
}
|
|
2202
|
-
return
|
|
1352
|
+
return Math.max(-0.1, Math.min(0.1, adjustment));
|
|
2203
1353
|
}
|
|
1354
|
+
var stockConfidenceModel = {
|
|
1355
|
+
calculate: (ctx) => scoreParts(ctx).final,
|
|
1356
|
+
calculateWithBreakdown: (ctx) => scoreParts(ctx)
|
|
1357
|
+
};
|
|
2204
1358
|
|
|
2205
1359
|
// src/generation/schema-patterns.ts
|
|
2206
1360
|
function createSchemaPatternGenerator(grammar) {
|
|
2207
1361
|
return {
|
|
2208
|
-
generatePattern: (schema, profile, config) =>
|
|
1362
|
+
generatePattern: (schema, profile, config) => generatePattern(grammar, schema, profile, config),
|
|
2209
1363
|
generateSimplePattern: (schema, profile, config) => generateSimplePattern(grammar, schema, profile, config),
|
|
2210
1364
|
generateVerbFirstPattern: (schema, profile, config) => generateVerbFirstPattern(schema, profile, config),
|
|
2211
|
-
generatePatternVariants: (schema, profile, config) =>
|
|
1365
|
+
generatePatternVariants: (schema, profile, config) => generatePatternVariants(grammar, schema, profile, config),
|
|
2212
1366
|
buildRoleToken
|
|
2213
1367
|
};
|
|
2214
1368
|
}
|
|
@@ -2258,21 +1412,21 @@ function schemaMarkerAlternatives(roleSpec, languageCode, marker) {
|
|
|
2258
1412
|
const alternatives = [.../* @__PURE__ */ new Set([...legacy, ...variants])].filter((a) => a && a !== marker);
|
|
2259
1413
|
return alternatives.length ? alternatives : void 0;
|
|
2260
1414
|
}
|
|
2261
|
-
var
|
|
1415
|
+
var defaultConfig = {
|
|
2262
1416
|
basePriority: 100,
|
|
2263
1417
|
generateSimpleVariants: true,
|
|
2264
1418
|
generateAlternatives: true
|
|
2265
1419
|
};
|
|
2266
|
-
function
|
|
1420
|
+
function generatePattern(grammar, schema, profile, config = defaultConfig) {
|
|
2267
1421
|
const id = `${schema.action}-${profile.code}-generated`;
|
|
2268
1422
|
const priority = config.basePriority ?? 100;
|
|
2269
1423
|
const keyword = profile.keywords[schema.action];
|
|
2270
1424
|
if (!keyword) {
|
|
2271
1425
|
throw new Error(`No keyword translation for '${schema.action}' in ${profile.code}`);
|
|
2272
1426
|
}
|
|
2273
|
-
const tokens =
|
|
1427
|
+
const tokens = buildTokens(grammar, schema, profile, keyword);
|
|
2274
1428
|
const extraction = buildExtractionRulesWithDefaults(schema, profile);
|
|
2275
|
-
const format =
|
|
1429
|
+
const format = buildFormatString(grammar, schema, profile, keyword);
|
|
2276
1430
|
return {
|
|
2277
1431
|
id,
|
|
2278
1432
|
language: profile.code,
|
|
@@ -2285,7 +1439,7 @@ function generatePattern2(grammar, schema, profile, config = defaultConfig2) {
|
|
|
2285
1439
|
extraction
|
|
2286
1440
|
};
|
|
2287
1441
|
}
|
|
2288
|
-
function generateSimplePattern(grammar, schema, profile, config =
|
|
1442
|
+
function generateSimplePattern(grammar, schema, profile, config = defaultConfig) {
|
|
2289
1443
|
const optionalRoles = schema.roles.filter((r) => !r.required);
|
|
2290
1444
|
if (optionalRoles.length === 0) {
|
|
2291
1445
|
return null;
|
|
@@ -2295,7 +1449,7 @@ function generateSimplePattern(grammar, schema, profile, config = defaultConfig2
|
|
|
2295
1449
|
...schema,
|
|
2296
1450
|
roles: requiredRoles
|
|
2297
1451
|
};
|
|
2298
|
-
const pattern =
|
|
1452
|
+
const pattern = generatePattern(grammar, simpleSchema, profile, config);
|
|
2299
1453
|
return {
|
|
2300
1454
|
...pattern,
|
|
2301
1455
|
id: `${schema.action}-${profile.code}-generated-simple`,
|
|
@@ -2304,7 +1458,7 @@ function generateSimplePattern(grammar, schema, profile, config = defaultConfig2
|
|
|
2304
1458
|
extraction: buildExtractionRulesWithDefaults(schema, profile)
|
|
2305
1459
|
};
|
|
2306
1460
|
}
|
|
2307
|
-
function generateVerbFirstPattern(schema, profile, config =
|
|
1461
|
+
function generateVerbFirstPattern(schema, profile, config = defaultConfig) {
|
|
2308
1462
|
if (profile.wordOrder !== "SOV") return null;
|
|
2309
1463
|
const requiredRoles = schema.roles.filter((r) => r.required);
|
|
2310
1464
|
if (requiredRoles.length !== 1) return null;
|
|
@@ -2313,11 +1467,13 @@ function generateVerbFirstPattern(schema, profile, config = defaultConfig2) {
|
|
|
2313
1467
|
const verbToken = keyword.alternatives ? { type: "literal", value: keyword.primary, alternatives: keyword.alternatives } : { type: "literal", value: keyword.primary };
|
|
2314
1468
|
const roleTokens = requiredRoles.flatMap((r) => {
|
|
2315
1469
|
const prefix = r.valuePrefixLiteral?.[profile.code];
|
|
1470
|
+
const words = wordsFor(r, profile.code);
|
|
2316
1471
|
const roleToken = {
|
|
2317
1472
|
type: "role",
|
|
2318
1473
|
role: r.role,
|
|
2319
1474
|
optional: false,
|
|
2320
|
-
expectedTypes: r.expectedTypes
|
|
1475
|
+
expectedTypes: r.expectedTypes,
|
|
1476
|
+
...words ? { words } : {}
|
|
2321
1477
|
};
|
|
2322
1478
|
return prefix ? [{ type: "literal", value: prefix }, roleToken] : [roleToken];
|
|
2323
1479
|
});
|
|
@@ -2335,11 +1491,11 @@ function generateVerbFirstPattern(schema, profile, config = defaultConfig2) {
|
|
|
2335
1491
|
extraction: buildExtractionRulesWithDefaults(schema, profile)
|
|
2336
1492
|
};
|
|
2337
1493
|
}
|
|
2338
|
-
function
|
|
1494
|
+
function generatePatternVariants(grammar, schema, profile, config = defaultConfig) {
|
|
2339
1495
|
const patterns = [];
|
|
2340
1496
|
const full = schema;
|
|
2341
1497
|
schema = withoutVariantOnlyRoles(schema);
|
|
2342
|
-
patterns.push(
|
|
1498
|
+
patterns.push(generatePattern(grammar, schema, profile, config));
|
|
2343
1499
|
if (config.generateSimpleVariants) {
|
|
2344
1500
|
const simple = generateSimplePattern(grammar, schema, profile, config);
|
|
2345
1501
|
if (simple) {
|
|
@@ -2351,7 +1507,7 @@ function generatePatternVariants2(grammar, schema, profile, config = defaultConf
|
|
|
2351
1507
|
...schema,
|
|
2352
1508
|
roles: schema.roles.filter((r) => r.role !== omitted)
|
|
2353
1509
|
};
|
|
2354
|
-
const variant =
|
|
1510
|
+
const variant = generatePattern(grammar, variantSchema, profile, config);
|
|
2355
1511
|
patterns.push({
|
|
2356
1512
|
...variant,
|
|
2357
1513
|
id: `${schema.action}-${profile.code}-generated-no-${omitted}`,
|
|
@@ -2366,7 +1522,7 @@ function generatePatternVariants2(grammar, schema, profile, config = defaultConf
|
|
|
2366
1522
|
(r) => r.role === widen.role ? { ...r, expectedTypes: [...widen.types] } : r
|
|
2367
1523
|
)
|
|
2368
1524
|
};
|
|
2369
|
-
const main =
|
|
1525
|
+
const main = generatePattern(grammar, cloneSchema, profile, config);
|
|
2370
1526
|
patterns.push({
|
|
2371
1527
|
...main,
|
|
2372
1528
|
id: `${schema.action}-${profile.code}-generated-${widen.idSuffix}`,
|
|
@@ -2381,7 +1537,7 @@ function generatePatternVariants2(grammar, schema, profile, config = defaultConf
|
|
|
2381
1537
|
return r.role === v.widen?.role ? { ...required, expectedTypes: [...v.widen.types] } : required;
|
|
2382
1538
|
})
|
|
2383
1539
|
};
|
|
2384
|
-
const variant =
|
|
1540
|
+
const variant = generatePattern(grammar, variantSchema, profile, config);
|
|
2385
1541
|
patterns.push({
|
|
2386
1542
|
...variant,
|
|
2387
1543
|
id: `${schema.action}-${profile.code}-generated-${v.idSuffix}`,
|
|
@@ -2406,7 +1562,7 @@ function generatePatternVariants2(grammar, schema, profile, config = defaultConf
|
|
|
2406
1562
|
};
|
|
2407
1563
|
const delta = v.priorityDelta ?? 5;
|
|
2408
1564
|
const carrier = v.methodCarrier ? { [v.methodCarrier]: { value: literal } } : {};
|
|
2409
|
-
const main =
|
|
1565
|
+
const main = generatePattern(grammar, cloneSchema, profile, config);
|
|
2410
1566
|
patterns.push({
|
|
2411
1567
|
...main,
|
|
2412
1568
|
id: `${schema.action}-${profile.code}-generated-${v.idSuffix}`,
|
|
@@ -2427,7 +1583,7 @@ function generatePatternVariants2(grammar, schema, profile, config = defaultConf
|
|
|
2427
1583
|
}
|
|
2428
1584
|
return patterns;
|
|
2429
1585
|
}
|
|
2430
|
-
function
|
|
1586
|
+
function buildTokens(grammar, schema, profile, keyword) {
|
|
2431
1587
|
const tokens = [];
|
|
2432
1588
|
const verbToken = keyword.alternatives ? { type: "literal", value: keyword.primary, alternatives: keyword.alternatives } : { type: "literal", value: keyword.primary };
|
|
2433
1589
|
const postVerbRoles = profile.wordOrder === "SOV" ? schema.roles.filter((r) => r.sovSlot === "postVerb") : [];
|
|
@@ -2487,11 +1643,17 @@ function buildRoleTokens(grammar, schema, profile) {
|
|
|
2487
1643
|
}
|
|
2488
1644
|
return tokens;
|
|
2489
1645
|
}
|
|
1646
|
+
function wordsFor(roleSpec, language) {
|
|
1647
|
+
if (!roleSpec.words) return void 0;
|
|
1648
|
+
const words = [...roleSpec.words["*"] ?? [], ...roleSpec.words[language] ?? []];
|
|
1649
|
+
return words.length > 0 ? words : void 0;
|
|
1650
|
+
}
|
|
2490
1651
|
function buildRoleToken(roleSpec, profile) {
|
|
2491
1652
|
const tokens = [];
|
|
2492
1653
|
const overrideMarker = roleSpec.markerOverride?.[profile.code];
|
|
2493
1654
|
const defaultMarker = profile.roleMarkers[roleSpec.role];
|
|
2494
1655
|
const suppressMarker = roleSpec.renderOverride?.[profile.code] === "";
|
|
1656
|
+
const words = wordsFor(roleSpec, profile.code);
|
|
2495
1657
|
const roleValueToken = {
|
|
2496
1658
|
type: "role",
|
|
2497
1659
|
role: roleSpec.role,
|
|
@@ -2503,7 +1665,9 @@ function buildRoleToken(roleSpec, profile) {
|
|
|
2503
1665
|
...roleSpec.valueShape !== void 0 ? { valueShape: roleSpec.valueShape } : {},
|
|
2504
1666
|
// A greedy role reads up to the next marker the pattern owes (a domain's
|
|
2505
1667
|
// SQL condition); its value reader does the reading.
|
|
2506
|
-
...roleSpec.greedy ? { greedy: true } : {}
|
|
1668
|
+
...roleSpec.greedy ? { greedy: true } : {},
|
|
1669
|
+
// A role with a word list reads only one of its words.
|
|
1670
|
+
...words ? { words } : {}
|
|
2507
1671
|
};
|
|
2508
1672
|
const prefixLiteral = roleSpec.valuePrefixLiteral?.[profile.code];
|
|
2509
1673
|
const pushPrefixed = () => {
|
|
@@ -2573,7 +1737,7 @@ function buildRoleToken(roleSpec, profile) {
|
|
|
2573
1737
|
}
|
|
2574
1738
|
return tokens;
|
|
2575
1739
|
}
|
|
2576
|
-
function
|
|
1740
|
+
function buildExtractionRules(schema, profile) {
|
|
2577
1741
|
const rules = {};
|
|
2578
1742
|
for (const roleSpec of schema.roles) {
|
|
2579
1743
|
const overrideMarker = roleSpec.markerOverride?.[profile.code];
|
|
@@ -2603,7 +1767,7 @@ function buildExtractionRules2(schema, profile) {
|
|
|
2603
1767
|
return rules;
|
|
2604
1768
|
}
|
|
2605
1769
|
function buildExtractionRulesWithDefaults(schema, profile) {
|
|
2606
|
-
const baseRules =
|
|
1770
|
+
const baseRules = buildExtractionRules(schema, profile);
|
|
2607
1771
|
const rules = {};
|
|
2608
1772
|
for (const roleSpec of schema.roles) {
|
|
2609
1773
|
const baseRule = baseRules[roleSpec.role] || {};
|
|
@@ -2615,7 +1779,7 @@ function buildExtractionRulesWithDefaults(schema, profile) {
|
|
|
2615
1779
|
}
|
|
2616
1780
|
return rules;
|
|
2617
1781
|
}
|
|
2618
|
-
function
|
|
1782
|
+
function buildFormatString(grammar, schema, profile, keyword) {
|
|
2619
1783
|
const parts = [];
|
|
2620
1784
|
const sortedRoles = sortRolesByPosition(schema.roles, profile.wordOrder, grammar.positionOrder);
|
|
2621
1785
|
const roleParts = sortedRoles.map((roleSpec) => {
|
|
@@ -4044,12 +3208,15 @@ function readingDifference(source, back) {
|
|
|
4044
3208
|
|
|
4045
3209
|
// src/api/create-dsl.ts
|
|
4046
3210
|
var UnreadInputError = class extends Error {
|
|
4047
|
-
constructor(input, language, reading, unread) {
|
|
4048
|
-
super(
|
|
3211
|
+
constructor(input, language, reading, unread, hint) {
|
|
3212
|
+
super(
|
|
3213
|
+
`"${input}" (${language}) reads as ${reading.action} only by leaving "${unread}" unread` + (hint ? `; ${hint}` : "")
|
|
3214
|
+
);
|
|
4049
3215
|
this.input = input;
|
|
4050
3216
|
this.language = language;
|
|
4051
3217
|
this.reading = reading;
|
|
4052
3218
|
this.unread = unread;
|
|
3219
|
+
this.hint = hint;
|
|
4053
3220
|
this.code = "unread-input";
|
|
4054
3221
|
this.name = "UnreadInputError";
|
|
4055
3222
|
}
|
|
@@ -4168,15 +3335,32 @@ var MultilingualDSLImpl = class {
|
|
|
4168
3335
|
}
|
|
4169
3336
|
if (longestPartial) {
|
|
4170
3337
|
const unread = tokens.tokens.slice(longestPartial.consumedTokens).map((t) => t.value).join(" ");
|
|
3338
|
+
const hint = this.quoteHint(longestPartial, tokens.tokens);
|
|
4171
3339
|
throw new UnreadInputError(
|
|
4172
3340
|
input,
|
|
4173
3341
|
language,
|
|
4174
3342
|
this.nodeOf(longestPartial, input, language),
|
|
4175
|
-
unread
|
|
3343
|
+
unread,
|
|
3344
|
+
...hint ? [hint] : []
|
|
4176
3345
|
);
|
|
4177
3346
|
}
|
|
4178
3347
|
throw new Error(`No pattern matched for input: ${input}`);
|
|
4179
3348
|
}
|
|
3349
|
+
/**
|
|
3350
|
+
* `if "buy milk" is one value, quote it`, where the last token a match read is a bare word it took
|
|
3351
|
+
* as a role's value (a role with no word list) and the first it left is a bare word too.
|
|
3352
|
+
*/
|
|
3353
|
+
quoteHint(match, tokens) {
|
|
3354
|
+
const last = tokens[match.consumedTokens - 1];
|
|
3355
|
+
const next = tokens[match.consumedTokens];
|
|
3356
|
+
if (last?.kind !== "identifier" || next?.kind !== "identifier") return void 0;
|
|
3357
|
+
const schema = this.schemaLookup.getSchema(match.pattern.command);
|
|
3358
|
+
const role = [...match.captured].find(
|
|
3359
|
+
([, v]) => v.raw === last.value
|
|
3360
|
+
)?.[0];
|
|
3361
|
+
if (!role || schema?.roles.find((r) => r.role === role)?.words) return void 0;
|
|
3362
|
+
return `if "${last.value} ${next.value}" is one value, quote it`;
|
|
3363
|
+
}
|
|
4180
3364
|
nodeOf(match, input, language) {
|
|
4181
3365
|
return {
|
|
4182
3366
|
kind: "command",
|
|
@@ -4400,21 +3584,14 @@ function createMultilingualDSL(config) {
|
|
|
4400
3584
|
return new MultilingualDSLImpl(effectiveConfig, registry, transformer, profileProvider, engine);
|
|
4401
3585
|
}
|
|
4402
3586
|
function engineFor(config) {
|
|
4403
|
-
const name = config.engine ?? "
|
|
4404
|
-
if (name === "classic") return classicEngine();
|
|
3587
|
+
const name = config.engine ?? "core";
|
|
4405
3588
|
if (name === "core") return coreEngine();
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
match: (tokens, pattern, language) => matcher.matchPattern(tokens, pattern, { code: language }),
|
|
4413
|
-
fallbackRenderer: (config) => createDomainRenderer({
|
|
4414
|
-
schemas: config.schemas,
|
|
4415
|
-
profiles: config.languages.map((l) => l.patternProfile)
|
|
4416
|
-
})
|
|
4417
|
-
};
|
|
3589
|
+
if (name === "classic") {
|
|
3590
|
+
throw new Error(
|
|
3591
|
+
'The "classic" DSL engine was removed in @lokascript/framework 5.0. Omit `engine` (or pass "core"): every domain reads on the core.'
|
|
3592
|
+
);
|
|
3593
|
+
}
|
|
3594
|
+
throw new Error(`Unknown DSL engine "${name}" (expected "core")`);
|
|
4418
3595
|
}
|
|
4419
3596
|
function schemaProfileOf(profile) {
|
|
4420
3597
|
const side = profile.wordOrder === "SOV" ? "after" : "before";
|
|
@@ -6102,6 +5279,130 @@ async function registryToAOTBackends(registry) {
|
|
|
6102
5279
|
// src/schema/command-schema.ts
|
|
6103
5280
|
var import_intent6 = require("@lokascript/intent");
|
|
6104
5281
|
|
|
5282
|
+
// src/generation/profile.ts
|
|
5283
|
+
function sortRolesByWordOrder(roles, wordOrder) {
|
|
5284
|
+
const sorted = [...roles];
|
|
5285
|
+
if (wordOrder === "SVO") {
|
|
5286
|
+
sorted.sort((a, b) => (b.svoPosition ?? 0) - (a.svoPosition ?? 0));
|
|
5287
|
+
} else if (wordOrder === "SOV") {
|
|
5288
|
+
sorted.sort((a, b) => (b.sovPosition ?? 0) - (a.sovPosition ?? 0));
|
|
5289
|
+
}
|
|
5290
|
+
return sorted;
|
|
5291
|
+
}
|
|
5292
|
+
|
|
5293
|
+
// src/generation/renderer.ts
|
|
5294
|
+
function lookupKeyword(keywords, action, language) {
|
|
5295
|
+
return keywords[action]?.[language] ?? action;
|
|
5296
|
+
}
|
|
5297
|
+
function lookupMarker(markers, marker, language) {
|
|
5298
|
+
return markers[marker]?.[language] ?? marker;
|
|
5299
|
+
}
|
|
5300
|
+
function buildPhrase(...parts) {
|
|
5301
|
+
return parts.filter(Boolean).join(" ");
|
|
5302
|
+
}
|
|
5303
|
+
function buildTablesFromProfiles(schemas, profiles) {
|
|
5304
|
+
const keywords = {};
|
|
5305
|
+
const markers = {};
|
|
5306
|
+
for (const profile of profiles) {
|
|
5307
|
+
for (const [action, kw] of Object.entries(profile.keywords)) {
|
|
5308
|
+
if (!keywords[action]) keywords[action] = {};
|
|
5309
|
+
keywords[action][profile.code] = kw.primary;
|
|
5310
|
+
}
|
|
5311
|
+
}
|
|
5312
|
+
for (const schema of schemas) {
|
|
5313
|
+
for (const role of schema.roles) {
|
|
5314
|
+
if (role.markerOverride) {
|
|
5315
|
+
const markerKey = role.role;
|
|
5316
|
+
if (!markers[markerKey]) markers[markerKey] = {};
|
|
5317
|
+
for (const [lang, markerText] of Object.entries(role.markerOverride)) {
|
|
5318
|
+
markers[markerKey][lang] = markerText;
|
|
5319
|
+
}
|
|
5320
|
+
}
|
|
5321
|
+
}
|
|
5322
|
+
}
|
|
5323
|
+
for (const profile of profiles) {
|
|
5324
|
+
if (profile.roleMarkers) {
|
|
5325
|
+
for (const [markerKey, markerDef] of Object.entries(profile.roleMarkers)) {
|
|
5326
|
+
if (!markers[markerKey]) markers[markerKey] = {};
|
|
5327
|
+
markers[markerKey][profile.code] = markerDef.primary;
|
|
5328
|
+
}
|
|
5329
|
+
}
|
|
5330
|
+
}
|
|
5331
|
+
return { keywords, markers };
|
|
5332
|
+
}
|
|
5333
|
+
function detectWordOrders(profiles) {
|
|
5334
|
+
const sovLanguages = /* @__PURE__ */ new Set();
|
|
5335
|
+
const vsoLanguages = /* @__PURE__ */ new Set();
|
|
5336
|
+
for (const profile of profiles) {
|
|
5337
|
+
if (profile.wordOrder === "SOV") sovLanguages.add(profile.code);
|
|
5338
|
+
if (profile.wordOrder === "VSO") vsoLanguages.add(profile.code);
|
|
5339
|
+
}
|
|
5340
|
+
return { sovLanguages, vsoLanguages };
|
|
5341
|
+
}
|
|
5342
|
+
function createSchemaRenderer(schemas, profiles) {
|
|
5343
|
+
const tables = buildRenderTables(schemas, profiles);
|
|
5344
|
+
return {
|
|
5345
|
+
render(node, language) {
|
|
5346
|
+
return renderFromSchema(node, language, tables) ?? node.action;
|
|
5347
|
+
}
|
|
5348
|
+
};
|
|
5349
|
+
}
|
|
5350
|
+
function createDomainRenderer(config) {
|
|
5351
|
+
const { schemas, profiles, overrides } = config;
|
|
5352
|
+
let tables;
|
|
5353
|
+
return (node, language) => {
|
|
5354
|
+
const override = overrides?.[node.action];
|
|
5355
|
+
if (override) return override(node, language);
|
|
5356
|
+
tables ?? (tables = buildRenderTables(schemas, profiles));
|
|
5357
|
+
return renderFromSchema(node, language, tables);
|
|
5358
|
+
};
|
|
5359
|
+
}
|
|
5360
|
+
function buildRenderTables(schemas, profiles) {
|
|
5361
|
+
const { keywords, markers } = buildTablesFromProfiles(schemas, profiles);
|
|
5362
|
+
const { sovLanguages } = detectWordOrders(profiles);
|
|
5363
|
+
const markerPositions = {};
|
|
5364
|
+
for (const profile of profiles) {
|
|
5365
|
+
for (const [role, markerDef] of Object.entries(profile.roleMarkers ?? {})) {
|
|
5366
|
+
if (!markerDef.position) continue;
|
|
5367
|
+
markerPositions[role] ?? (markerPositions[role] = {});
|
|
5368
|
+
markerPositions[role][profile.code] = markerDef.position;
|
|
5369
|
+
}
|
|
5370
|
+
}
|
|
5371
|
+
const schemaMap = /* @__PURE__ */ new Map();
|
|
5372
|
+
for (const s of schemas) schemaMap.set(s.action, s);
|
|
5373
|
+
return { keywords, markers, markerPositions, sovLanguages, schemaMap };
|
|
5374
|
+
}
|
|
5375
|
+
function renderFromSchema(node, language, tables) {
|
|
5376
|
+
const schema = tables.schemaMap.get(node.action);
|
|
5377
|
+
if (!schema) return null;
|
|
5378
|
+
const keyword = lookupKeyword(tables.keywords, node.action, language);
|
|
5379
|
+
const isSOV = tables.sovLanguages.has(language);
|
|
5380
|
+
const ordered = sortRolesByWordOrder([...schema.roles], isSOV ? "SOV" : "SVO");
|
|
5381
|
+
const preVerb = [];
|
|
5382
|
+
const postVerb = [];
|
|
5383
|
+
for (const role of ordered) {
|
|
5384
|
+
let value = (0, import_intent.extractRoleValue)(node, role.role);
|
|
5385
|
+
if (!value && role.default !== void 0) {
|
|
5386
|
+
value = String((0, import_intent.extractValue)(role.default));
|
|
5387
|
+
}
|
|
5388
|
+
if (!value) continue;
|
|
5389
|
+
if (role.quoteMultiword && /\s/.test(value) && !/^(["']).*\1$/.test(value)) {
|
|
5390
|
+
value = `"${value}"`;
|
|
5391
|
+
}
|
|
5392
|
+
const markerText = role.renderOverride?.[language] ?? role.markerOverride?.[language] ?? role.renderOverride?.["*"] ?? tables.markers[role.role]?.[language];
|
|
5393
|
+
const markerPosition = role.markerPositionOverride?.[language] ?? role.markerPosition ?? tables.markerPositions[role.role]?.[language] ?? (isSOV ? "after" : "before");
|
|
5394
|
+
const bucket = isSOV && role.sovSlot === "postVerb" ? postVerb : preVerb;
|
|
5395
|
+
if (markerText && markerPosition === "before") {
|
|
5396
|
+
bucket.push(markerText, value);
|
|
5397
|
+
} else if (markerText) {
|
|
5398
|
+
bucket.push(value, markerText);
|
|
5399
|
+
} else {
|
|
5400
|
+
bucket.push(value);
|
|
5401
|
+
}
|
|
5402
|
+
}
|
|
5403
|
+
return isSOV ? buildPhrase(...preVerb, keyword, ...postVerb) : buildPhrase(keyword, ...preVerb, ...postVerb);
|
|
5404
|
+
}
|
|
5405
|
+
|
|
6105
5406
|
// src/generation/diagnostics.ts
|
|
6106
5407
|
var import_intent7 = require("@lokascript/intent");
|
|
6107
5408
|
|
|
@@ -7956,7 +7257,6 @@ function accumulateIndented(statements, config) {
|
|
|
7956
7257
|
NullProfileProvider,
|
|
7957
7258
|
NumberExtractor,
|
|
7958
7259
|
OperatorExtractor,
|
|
7959
|
-
PatternMatcher,
|
|
7960
7260
|
PatternTracker,
|
|
7961
7261
|
PunctuationExtractor,
|
|
7962
7262
|
StockValueReader,
|
|
@@ -8020,8 +7320,6 @@ function accumulateIndented(statements, config) {
|
|
|
8020
7320
|
fromInterchangeNode,
|
|
8021
7321
|
fromProtocolJSON,
|
|
8022
7322
|
generateExamples,
|
|
8023
|
-
generatePattern,
|
|
8024
|
-
generatePatternVariants,
|
|
8025
7323
|
generatePrompt,
|
|
8026
7324
|
generateProtocolReference,
|
|
8027
7325
|
getAllPossessiveKeywords,
|
|
@@ -8060,8 +7358,6 @@ function accumulateIndented(statements, config) {
|
|
|
8060
7358
|
legacyMarkerAlternatives,
|
|
8061
7359
|
lookupKeyword,
|
|
8062
7360
|
lookupMarker,
|
|
8063
|
-
matchBest,
|
|
8064
|
-
matchPattern,
|
|
8065
7361
|
needsDisambiguation,
|
|
8066
7362
|
noChange,
|
|
8067
7363
|
normalized,
|
|
@@ -8069,7 +7365,6 @@ function accumulateIndented(statements, config) {
|
|
|
8069
7365
|
parseDocument,
|
|
8070
7366
|
parseExplicit,
|
|
8071
7367
|
parseJSONL,
|
|
8072
|
-
patternMatcher,
|
|
8073
7368
|
readGreedy,
|
|
8074
7369
|
readRoleValue,
|
|
8075
7370
|
registryToAOTBackends,
|