@lokascript/framework 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aot/index.d.ts +1 -0
- package/dist/aot/index.d.ts.map +1 -1
- package/dist/aot/registry-bridge.d.ts +27 -0
- package/dist/aot/registry-bridge.d.ts.map +1 -0
- package/dist/api/create-dsl.d.ts.map +1 -1
- package/dist/api/dispatcher.d.ts +22 -0
- package/dist/api/dispatcher.d.ts.map +1 -1
- package/dist/api/domain-registry.d.ts +41 -0
- package/dist/api/domain-registry.d.ts.map +1 -1
- package/dist/api/index.js +1438 -21
- package/dist/api/index.js.map +1 -1
- package/dist/core/index.js +247 -10
- package/dist/core/index.js.map +1 -1
- package/dist/core/pattern-matching/index.js +74 -10
- package/dist/core/pattern-matching/index.js.map +1 -1
- package/dist/core/pattern-matching/pattern-matcher.d.ts.map +1 -1
- package/dist/core/tokenization/index.js +133 -0
- package/dist/core/tokenization/index.js.map +1 -1
- package/dist/core/tokenization/morphology/base-normalizer.d.ts +94 -0
- package/dist/core/tokenization/morphology/base-normalizer.d.ts.map +1 -0
- package/dist/core/tokenization/morphology/index.d.ts +3 -1
- package/dist/core/tokenization/morphology/index.d.ts.map +1 -1
- package/dist/core/types.d.ts +81 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +40 -0
- package/dist/core/types.js.map +1 -1
- package/dist/feedback/confidence-gate.d.ts +28 -0
- package/dist/feedback/confidence-gate.d.ts.map +1 -0
- package/dist/feedback/feedback-formatter.d.ts +21 -0
- package/dist/feedback/feedback-formatter.d.ts.map +1 -0
- package/dist/feedback/index.d.ts +11 -0
- package/dist/feedback/index.d.ts.map +1 -0
- package/dist/feedback/pattern-tracker.d.ts +47 -0
- package/dist/feedback/pattern-tracker.d.ts.map +1 -0
- package/dist/feedback/types.d.ts +113 -0
- package/dist/feedback/types.d.ts.map +1 -0
- package/dist/generation/index.js +2 -1
- package/dist/generation/index.js.map +1 -1
- package/dist/index.cjs +3264 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3223 -132
- package/dist/index.js.map +1 -1
- package/dist/ir/explicit-parser.d.ts +86 -0
- package/dist/ir/explicit-parser.d.ts.map +1 -0
- package/dist/ir/explicit-renderer.d.ts +34 -0
- package/dist/ir/explicit-renderer.d.ts.map +1 -0
- package/dist/ir/from-interchange.d.ts +51 -0
- package/dist/ir/from-interchange.d.ts.map +1 -0
- package/dist/ir/index.d.ts +31 -0
- package/dist/ir/index.d.ts.map +1 -0
- package/dist/ir/index.js +1934 -0
- package/dist/ir/index.js.map +1 -0
- package/dist/ir/json-schema.d.ts +40 -0
- package/dist/ir/json-schema.d.ts.map +1 -0
- package/dist/ir/protocol-json.d.ts +73 -0
- package/dist/ir/protocol-json.d.ts.map +1 -0
- package/dist/ir/references.d.ts +19 -0
- package/dist/ir/references.d.ts.map +1 -0
- package/dist/ir/to-runtime-ast.d.ts +53 -0
- package/dist/ir/to-runtime-ast.d.ts.map +1 -0
- package/dist/ir/types.d.ts +151 -0
- package/dist/ir/types.d.ts.map +1 -0
- package/dist/parsing/index.js +74 -10
- package/dist/parsing/index.js.map +1 -1
- package/dist/prompts/index.d.ts +9 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/prompt-generator.d.ts +49 -0
- package/dist/prompts/prompt-generator.d.ts.map +1 -0
- package/dist/prompts/types.d.ts +64 -0
- package/dist/prompts/types.d.ts.map +1 -0
- package/dist/schema/command-schema.d.ts +17 -0
- package/dist/schema/command-schema.d.ts.map +1 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/testing/index.js +8673 -8260
- package/dist/testing/index.js.map +1 -1
- package/dist/training/index.d.ts +11 -0
- package/dist/training/index.d.ts.map +1 -0
- package/dist/training/jsonl-writer.d.ts +42 -0
- package/dist/training/jsonl-writer.d.ts.map +1 -0
- package/dist/training/schema-synthesizer.d.ts +20 -0
- package/dist/training/schema-synthesizer.d.ts.map +1 -0
- package/dist/training/types.d.ts +73 -0
- package/dist/training/types.d.ts.map +1 -0
- package/package.json +5 -1
- package/src/aot/index.ts +1 -0
- package/src/aot/registry-bridge.test.ts +104 -0
- package/src/aot/registry-bridge.ts +66 -0
- package/src/api/create-dsl.test.ts +306 -0
- package/src/api/create-dsl.ts +21 -0
- package/src/api/dispatcher.test.ts +66 -0
- package/src/api/dispatcher.ts +77 -9
- package/src/api/domain-registry.ts +120 -4
- package/src/core/pattern-matching/pattern-matcher.test.ts +131 -0
- package/src/core/pattern-matching/pattern-matcher.ts +102 -19
- package/src/core/tokenization/morphology/base-normalizer.ts +249 -0
- package/src/core/tokenization/morphology/index.ts +3 -1
- package/src/core/types.ts +147 -2
- package/src/feedback/confidence-gate.test.ts +111 -0
- package/src/feedback/confidence-gate.ts +70 -0
- package/src/feedback/feedback-formatter.test.ts +198 -0
- package/src/feedback/feedback-formatter.ts +298 -0
- package/src/feedback/index.ts +24 -0
- package/src/feedback/pattern-tracker.test.ts +198 -0
- package/src/feedback/pattern-tracker.ts +140 -0
- package/src/feedback/types.ts +149 -0
- package/src/generation/pattern-generator.ts +2 -2
- package/src/index.ts +29 -0
- package/src/integration/llm-lse-integration.test.ts +457 -0
- package/src/ir/conformance.test.ts +120 -0
- package/src/ir/explicit-parser.test.ts +612 -0
- package/src/ir/explicit-parser.ts +793 -0
- package/src/ir/explicit-renderer.test.ts +229 -0
- package/src/ir/explicit-renderer.ts +198 -0
- package/src/ir/from-interchange.test.ts +853 -0
- package/src/ir/from-interchange.ts +594 -0
- package/src/ir/index.ts +77 -0
- package/src/ir/json-schema.test.ts +281 -0
- package/src/ir/json-schema.ts +299 -0
- package/src/ir/protocol-json.test.ts +1187 -0
- package/src/ir/protocol-json.ts +751 -0
- package/src/ir/references.test.ts +38 -0
- package/src/ir/references.ts +33 -0
- package/src/ir/round-trip.test.ts +557 -0
- package/src/ir/to-runtime-ast.ts +203 -0
- package/src/ir/types.ts +191 -0
- package/src/prompts/index.ts +15 -0
- package/src/prompts/prompt-generator.test.ts +386 -0
- package/src/prompts/prompt-generator.ts +531 -0
- package/src/prompts/types.ts +89 -0
- package/src/schema/command-schema.ts +19 -0
- package/src/training/index.ts +13 -0
- package/src/training/jsonl-writer.test.ts +138 -0
- package/src/training/jsonl-writer.ts +67 -0
- package/src/training/schema-synthesizer.test.ts +250 -0
- package/src/training/schema-synthesizer.ts +306 -0
- package/src/training/types.ts +103 -0
|
@@ -210,7 +210,8 @@ export class PatternMatcher {
|
|
|
210
210
|
private matchTokenSequence(
|
|
211
211
|
tokens: TokenStream,
|
|
212
212
|
patternTokens: PatternToken[],
|
|
213
|
-
captured: Map<SemanticRole, SemanticValue
|
|
213
|
+
captured: Map<SemanticRole, SemanticValue>,
|
|
214
|
+
parentStopMarkers?: Set<string>
|
|
214
215
|
): boolean {
|
|
215
216
|
// Skip leading conjunctions for Arabic (proclitics: و, ف, ول, وب, etc.)
|
|
216
217
|
// BUT NOT if the pattern explicitly expects a conjunction (proclitic patterns)
|
|
@@ -251,6 +252,11 @@ export class PatternMatcher {
|
|
|
251
252
|
// recognized marker keyword or end of input
|
|
252
253
|
if (patternToken.type === 'role' && patternToken.greedy) {
|
|
253
254
|
const stopMarkers = this.collectStopMarkers(patternTokens, i + 1);
|
|
255
|
+
// Merge parent-level stop markers so greedy roles inside groups
|
|
256
|
+
// know when to stop for tokens outside the group (e.g., SOV verb keywords)
|
|
257
|
+
if (parentStopMarkers) {
|
|
258
|
+
for (const m of parentStopMarkers) stopMarkers.add(m);
|
|
259
|
+
}
|
|
254
260
|
const values: string[] = [];
|
|
255
261
|
while (!tokens.isAtEnd()) {
|
|
256
262
|
const nextToken = tokens.peek();
|
|
@@ -271,10 +277,78 @@ export class PatternMatcher {
|
|
|
271
277
|
}
|
|
272
278
|
}
|
|
273
279
|
|
|
280
|
+
// Group tokens: compute sibling stop markers and forward to inner matching
|
|
281
|
+
if (patternToken.type === 'group') {
|
|
282
|
+
const siblingStopMarkers = this.collectStopMarkers(patternTokens, i + 1);
|
|
283
|
+
if (parentStopMarkers) {
|
|
284
|
+
for (const m of parentStopMarkers) siblingStopMarkers.add(m);
|
|
285
|
+
}
|
|
286
|
+
const groupMatched = this.matchGroupToken(
|
|
287
|
+
tokens,
|
|
288
|
+
patternToken as PatternToken & { type: 'group' },
|
|
289
|
+
captured,
|
|
290
|
+
siblingStopMarkers
|
|
291
|
+
);
|
|
292
|
+
if (groupMatched) {
|
|
293
|
+
prevOptionalMark = null;
|
|
294
|
+
prevOptionalRole = null;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (this.isOptional(patternToken)) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
|
|
274
303
|
// Save stream position before attempting optional roles
|
|
275
304
|
const isOptionalRole = patternToken.type === 'role' && patternToken.optional === true;
|
|
276
305
|
const markBefore = isOptionalRole ? tokens.mark() : null;
|
|
277
306
|
|
|
307
|
+
// SOV marker-stealing guard: before matching a required role, check if
|
|
308
|
+
// the current input token would match the immediately-following pattern
|
|
309
|
+
// literal (the role's own postpositional marker). If so, skip consuming
|
|
310
|
+
// this token as a role value — it should be matched as the literal instead.
|
|
311
|
+
const isRoleToken = patternToken.type === 'role';
|
|
312
|
+
if (isRoleToken && !isOptionalRole) {
|
|
313
|
+
const nextPT = i + 1 < patternTokens.length ? patternTokens[i + 1] : null;
|
|
314
|
+
if (nextPT?.type === 'literal') {
|
|
315
|
+
const currentToken = tokens.peek();
|
|
316
|
+
if (currentToken && this.getMatchType(currentToken, nextPT.value) !== 'none') {
|
|
317
|
+
// Current token is the marker literal, not a role value — treat role as failed
|
|
318
|
+
this.logger.debug(
|
|
319
|
+
' >> MARKER-STEAL GUARD: current token matches next literal, skipping role'
|
|
320
|
+
);
|
|
321
|
+
// Fall through to failure handling below
|
|
322
|
+
// (which will trigger backtracking of previous optional role)
|
|
323
|
+
|
|
324
|
+
// Match failed
|
|
325
|
+
this.logger.debug(' >> Token match FAILED');
|
|
326
|
+
|
|
327
|
+
if (this.isOptional(patternToken)) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Required token failed — try backtracking over the previous optional role
|
|
332
|
+
if (prevOptionalMark && prevOptionalRole) {
|
|
333
|
+
this.logger.debug(' >> BACKTRACKING: undoing optional role', prevOptionalRole);
|
|
334
|
+
tokens.reset(prevOptionalMark);
|
|
335
|
+
captured.delete(prevOptionalRole);
|
|
336
|
+
prevOptionalMark = null;
|
|
337
|
+
prevOptionalRole = null;
|
|
338
|
+
|
|
339
|
+
// Retry the current (failed) pattern token from the restored position
|
|
340
|
+
const retryMatched = this.matchPatternToken(tokens, patternToken, captured);
|
|
341
|
+
this.logger.debug(' >> Backtrack retry result:', retryMatched);
|
|
342
|
+
if (retryMatched) {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
278
352
|
const matched = this.matchPatternToken(tokens, patternToken, captured);
|
|
279
353
|
this.logger.debug(' >> Match result:', matched);
|
|
280
354
|
|
|
@@ -436,10 +510,7 @@ export class PatternMatcher {
|
|
|
436
510
|
if (possessiveValue) {
|
|
437
511
|
// Validate expected types if specified
|
|
438
512
|
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
439
|
-
if (
|
|
440
|
-
!patternToken.expectedTypes.includes(possessiveValue.type) &&
|
|
441
|
-
!patternToken.expectedTypes.includes('expression')
|
|
442
|
-
) {
|
|
513
|
+
if (!isTypeCompatible(possessiveValue.type, patternToken.expectedTypes)) {
|
|
443
514
|
return patternToken.optional || false;
|
|
444
515
|
}
|
|
445
516
|
}
|
|
@@ -451,10 +522,7 @@ export class PatternMatcher {
|
|
|
451
522
|
const methodCallValue = this.tryMatchMethodCallExpression(tokens);
|
|
452
523
|
if (methodCallValue) {
|
|
453
524
|
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
454
|
-
if (
|
|
455
|
-
!patternToken.expectedTypes.includes(methodCallValue.type) &&
|
|
456
|
-
!patternToken.expectedTypes.includes('expression')
|
|
457
|
-
) {
|
|
525
|
+
if (!isTypeCompatible(methodCallValue.type, patternToken.expectedTypes)) {
|
|
458
526
|
return patternToken.optional || false;
|
|
459
527
|
}
|
|
460
528
|
}
|
|
@@ -479,10 +547,7 @@ export class PatternMatcher {
|
|
|
479
547
|
const propertyAccessValue = this.tryMatchPropertyAccessExpression(tokens);
|
|
480
548
|
if (propertyAccessValue) {
|
|
481
549
|
if (patternToken.expectedTypes && patternToken.expectedTypes.length > 0) {
|
|
482
|
-
if (
|
|
483
|
-
!patternToken.expectedTypes.includes(propertyAccessValue.type) &&
|
|
484
|
-
!patternToken.expectedTypes.includes('expression')
|
|
485
|
-
) {
|
|
550
|
+
if (!isTypeCompatible(propertyAccessValue.type, patternToken.expectedTypes)) {
|
|
486
551
|
return patternToken.optional || false;
|
|
487
552
|
}
|
|
488
553
|
}
|
|
@@ -928,12 +993,18 @@ export class PatternMatcher {
|
|
|
928
993
|
private matchGroupToken(
|
|
929
994
|
tokens: TokenStream,
|
|
930
995
|
patternToken: PatternToken & { type: 'group' },
|
|
931
|
-
captured: Map<SemanticRole, SemanticValue
|
|
996
|
+
captured: Map<SemanticRole, SemanticValue>,
|
|
997
|
+
parentStopMarkers?: Set<string>
|
|
932
998
|
): boolean {
|
|
933
999
|
const mark = tokens.mark();
|
|
934
1000
|
const capturedBefore = new Set(captured.keys());
|
|
935
1001
|
|
|
936
|
-
const success = this.matchTokenSequence(
|
|
1002
|
+
const success = this.matchTokenSequence(
|
|
1003
|
+
tokens,
|
|
1004
|
+
patternToken.tokens,
|
|
1005
|
+
captured,
|
|
1006
|
+
parentStopMarkers
|
|
1007
|
+
);
|
|
937
1008
|
if (success) return true;
|
|
938
1009
|
|
|
939
1010
|
// Reset from failed attempt
|
|
@@ -965,7 +1036,12 @@ export class PatternMatcher {
|
|
|
965
1036
|
for (let s = 0; s < offset; s++) tokens.advance();
|
|
966
1037
|
|
|
967
1038
|
// Retry group match from marker position
|
|
968
|
-
const retrySuccess = this.matchTokenSequence(
|
|
1039
|
+
const retrySuccess = this.matchTokenSequence(
|
|
1040
|
+
tokens,
|
|
1041
|
+
patternToken.tokens,
|
|
1042
|
+
captured,
|
|
1043
|
+
parentStopMarkers
|
|
1044
|
+
);
|
|
969
1045
|
if (retrySuccess) return true;
|
|
970
1046
|
|
|
971
1047
|
// Retry failed — full reset
|
|
@@ -1050,7 +1126,10 @@ export class PatternMatcher {
|
|
|
1050
1126
|
break;
|
|
1051
1127
|
}
|
|
1052
1128
|
}
|
|
1053
|
-
|
|
1129
|
+
// For optional groups, continue scanning past them so the greedy
|
|
1130
|
+
// capture also sees subsequent required literals (e.g., the SOV verb
|
|
1131
|
+
// keyword after an optional WHERE group).
|
|
1132
|
+
if (!pt.optional) break;
|
|
1054
1133
|
}
|
|
1055
1134
|
}
|
|
1056
1135
|
return markers;
|
|
@@ -1061,8 +1140,12 @@ export class PatternMatcher {
|
|
|
1061
1140
|
*/
|
|
1062
1141
|
private isStopMarker(token: LanguageToken, stopMarkers: Set<string>): boolean {
|
|
1063
1142
|
if (stopMarkers.size === 0) return false;
|
|
1064
|
-
|
|
1065
|
-
|
|
1143
|
+
// Check both raw value and normalized form against stop markers.
|
|
1144
|
+
// Pattern literals use native keywords (e.g., '選択') while tokenizers may
|
|
1145
|
+
// set normalized to the English equivalent (e.g., 'select'). Both must match.
|
|
1146
|
+
if (stopMarkers.has(token.value.toLowerCase())) return true;
|
|
1147
|
+
if (token.normalized && stopMarkers.has(token.normalized.toLowerCase())) return true;
|
|
1148
|
+
return false;
|
|
1066
1149
|
}
|
|
1067
1150
|
|
|
1068
1151
|
/**
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseMorphologicalNormalizer — Shared base class for language normalizers.
|
|
3
|
+
*
|
|
4
|
+
* Provides the common suffix/prefix stripping loop, reflexive verb handling,
|
|
5
|
+
* and normalize() pipeline. Language-specific normalizers extend this class
|
|
6
|
+
* and provide their conjugation rules.
|
|
7
|
+
*
|
|
8
|
+
* Phase 3.1 of parser-ecosystem-plan-v3.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
MorphologicalNormalizer,
|
|
13
|
+
NormalizationResult,
|
|
14
|
+
ConjugationType,
|
|
15
|
+
SuffixRule,
|
|
16
|
+
PrefixRule,
|
|
17
|
+
} from './types';
|
|
18
|
+
import { noChange, normalized } from './types';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Conjugation ending rule for verb classes (Romance languages etc.)
|
|
22
|
+
* Broader than SuffixRule — includes the replacement stem (e.g., strip -ando, add -ar).
|
|
23
|
+
*/
|
|
24
|
+
export interface ConjugationEnding {
|
|
25
|
+
readonly ending: string;
|
|
26
|
+
readonly stem: string;
|
|
27
|
+
readonly confidence: number;
|
|
28
|
+
readonly type: ConjugationType;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Configuration for BaseMorphologicalNormalizer.
|
|
33
|
+
* Subclasses provide this in their constructor.
|
|
34
|
+
*/
|
|
35
|
+
export interface NormalizerConfig {
|
|
36
|
+
/** Language code */
|
|
37
|
+
readonly language: string;
|
|
38
|
+
|
|
39
|
+
/** Minimum word length to attempt normalization */
|
|
40
|
+
readonly minWordLength?: number;
|
|
41
|
+
|
|
42
|
+
/** Minimum stem length after stripping (default: 2) */
|
|
43
|
+
readonly minStemLength?: number;
|
|
44
|
+
|
|
45
|
+
/** Conjugation endings sorted longest-first */
|
|
46
|
+
readonly endings?: readonly ConjugationEnding[];
|
|
47
|
+
|
|
48
|
+
/** Suffix rules (for SuffixRule-style normalizers) */
|
|
49
|
+
readonly suffixRules?: readonly SuffixRule[];
|
|
50
|
+
|
|
51
|
+
/** Prefix rules */
|
|
52
|
+
readonly prefixRules?: readonly PrefixRule[];
|
|
53
|
+
|
|
54
|
+
/** Reflexive suffixes (for Romance languages) */
|
|
55
|
+
readonly reflexiveSuffixes?: readonly string[];
|
|
56
|
+
|
|
57
|
+
/** Infinitive endings (for checking if already normalized) */
|
|
58
|
+
readonly infinitiveEndings?: readonly string[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Abstract base class for morphological normalizers.
|
|
63
|
+
*
|
|
64
|
+
* Subclasses must implement `isNormalizable()` and can override any
|
|
65
|
+
* normalization step. The default `normalize()` pipeline is:
|
|
66
|
+
*
|
|
67
|
+
* 1. Check if already in dictionary form → noChange
|
|
68
|
+
* 2. Try reflexive normalization (if reflexiveSuffixes configured)
|
|
69
|
+
* 3. Try conjugation endings (if endings configured)
|
|
70
|
+
* 4. Try suffix rules (if suffixRules configured)
|
|
71
|
+
* 5. Try prefix rules (if prefixRules configured)
|
|
72
|
+
* 6. Return noChange
|
|
73
|
+
*/
|
|
74
|
+
export abstract class BaseMorphologicalNormalizer implements MorphologicalNormalizer {
|
|
75
|
+
readonly language: string;
|
|
76
|
+
protected readonly config: NormalizerConfig;
|
|
77
|
+
|
|
78
|
+
constructor(config: NormalizerConfig) {
|
|
79
|
+
this.language = config.language;
|
|
80
|
+
this.config = {
|
|
81
|
+
minWordLength: 3,
|
|
82
|
+
minStemLength: 2,
|
|
83
|
+
...config,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Check if a word can be normalized. Subclasses must implement this
|
|
89
|
+
* with language-specific script/character detection.
|
|
90
|
+
*/
|
|
91
|
+
abstract isNormalizable(word: string): boolean;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Standard normalization pipeline. Override for custom behavior.
|
|
95
|
+
*/
|
|
96
|
+
normalize(word: string): NormalizationResult {
|
|
97
|
+
const lower = word.toLowerCase();
|
|
98
|
+
|
|
99
|
+
// Check if already in dictionary form
|
|
100
|
+
if (this.isAlreadyNormalized(lower)) {
|
|
101
|
+
return noChange(word);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Try reflexive normalization (Romance languages)
|
|
105
|
+
if (this.config.reflexiveSuffixes) {
|
|
106
|
+
const reflexive = this.tryReflexiveNormalization(lower);
|
|
107
|
+
if (reflexive) return reflexive;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Try conjugation endings
|
|
111
|
+
if (this.config.endings) {
|
|
112
|
+
const conjugation = this.tryConjugationEndings(lower);
|
|
113
|
+
if (conjugation) return conjugation;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Try suffix rules
|
|
117
|
+
if (this.config.suffixRules) {
|
|
118
|
+
const suffix = this.trySuffixRules(lower);
|
|
119
|
+
if (suffix) return suffix;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Try prefix rules
|
|
123
|
+
if (this.config.prefixRules) {
|
|
124
|
+
const prefix = this.tryPrefixRules(lower);
|
|
125
|
+
if (prefix) return prefix;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return noChange(word);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Check if word is already in dictionary form (e.g., ends in -ar/-er/-ir).
|
|
133
|
+
* Override for language-specific checks.
|
|
134
|
+
*/
|
|
135
|
+
protected isAlreadyNormalized(word: string): boolean {
|
|
136
|
+
if (this.config.infinitiveEndings) {
|
|
137
|
+
return this.config.infinitiveEndings.some(e => word.endsWith(e));
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Try to strip reflexive suffixes and normalize the remainder.
|
|
144
|
+
* Common in Romance languages (Spanish, Portuguese, French).
|
|
145
|
+
*/
|
|
146
|
+
protected tryReflexiveNormalization(word: string): NormalizationResult | null {
|
|
147
|
+
const suffixes = this.config.reflexiveSuffixes;
|
|
148
|
+
if (!suffixes) return null;
|
|
149
|
+
|
|
150
|
+
for (const suffix of suffixes) {
|
|
151
|
+
if (!word.endsWith(suffix)) continue;
|
|
152
|
+
const remainder = word.slice(0, -suffix.length);
|
|
153
|
+
|
|
154
|
+
// Check if remainder is already an infinitive
|
|
155
|
+
if (this.isAlreadyNormalized(remainder)) {
|
|
156
|
+
return normalized(remainder, 0.88, {
|
|
157
|
+
removedSuffixes: [suffix],
|
|
158
|
+
conjugationType: 'reflexive',
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Try to normalize the remainder
|
|
163
|
+
const inner = this.tryConjugationEndings(remainder) || this.trySuffixRules(remainder);
|
|
164
|
+
if (inner && inner.stem !== remainder) {
|
|
165
|
+
return normalized(inner.stem, inner.confidence * 0.95, {
|
|
166
|
+
removedSuffixes: [suffix, ...(inner.metadata?.removedSuffixes || [])],
|
|
167
|
+
conjugationType: 'reflexive',
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Try conjugation endings (verb class endings like -ar/-er/-ir patterns).
|
|
177
|
+
* Endings must be pre-sorted longest-first.
|
|
178
|
+
*/
|
|
179
|
+
protected tryConjugationEndings(word: string): NormalizationResult | null {
|
|
180
|
+
const endings = this.config.endings;
|
|
181
|
+
if (!endings) return null;
|
|
182
|
+
|
|
183
|
+
const minStem = this.config.minStemLength ?? 2;
|
|
184
|
+
|
|
185
|
+
for (const rule of endings) {
|
|
186
|
+
if (!word.endsWith(rule.ending)) continue;
|
|
187
|
+
|
|
188
|
+
const stemBase = word.slice(0, -rule.ending.length);
|
|
189
|
+
if (stemBase.length < minStem) continue;
|
|
190
|
+
|
|
191
|
+
const infinitive = stemBase + rule.stem;
|
|
192
|
+
return normalized(infinitive, rule.confidence, {
|
|
193
|
+
removedSuffixes: [rule.ending],
|
|
194
|
+
conjugationType: rule.type,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Try SuffixRule-style normalization.
|
|
203
|
+
* Rules must be pre-sorted longest-first.
|
|
204
|
+
*/
|
|
205
|
+
protected trySuffixRules(word: string): NormalizationResult | null {
|
|
206
|
+
const rules = this.config.suffixRules;
|
|
207
|
+
if (!rules) return null;
|
|
208
|
+
|
|
209
|
+
const defaultMinStem = this.config.minStemLength ?? 2;
|
|
210
|
+
|
|
211
|
+
for (const rule of rules) {
|
|
212
|
+
if (!word.endsWith(rule.pattern)) continue;
|
|
213
|
+
|
|
214
|
+
const stem = word.slice(0, -rule.pattern.length);
|
|
215
|
+
const minStem = rule.minStemLength ?? defaultMinStem;
|
|
216
|
+
if (stem.length < minStem) continue;
|
|
217
|
+
|
|
218
|
+
const result = stem + (rule.replacement || '');
|
|
219
|
+
return normalized(result, rule.confidence, {
|
|
220
|
+
removedSuffixes: [rule.pattern],
|
|
221
|
+
...(rule.conjugationType && { conjugationType: rule.conjugationType }),
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Try PrefixRule-style normalization.
|
|
230
|
+
*/
|
|
231
|
+
protected tryPrefixRules(word: string): NormalizationResult | null {
|
|
232
|
+
const rules = this.config.prefixRules;
|
|
233
|
+
if (!rules) return null;
|
|
234
|
+
|
|
235
|
+
for (const rule of rules) {
|
|
236
|
+
if (!word.startsWith(rule.pattern)) continue;
|
|
237
|
+
|
|
238
|
+
const remainder = word.slice(rule.pattern.length);
|
|
239
|
+
const minRemaining = rule.minRemaining ?? this.config.minStemLength ?? 2;
|
|
240
|
+
if (remainder.length < minRemaining) continue;
|
|
241
|
+
|
|
242
|
+
return normalized(remainder, 1.0 - rule.confidencePenalty, {
|
|
243
|
+
removedPrefixes: [rule.pattern],
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Morphological normalization types and
|
|
2
|
+
* Morphological normalization types, interfaces, and base class
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export * from './types';
|
|
6
|
+
export { BaseMorphologicalNormalizer } from './base-normalizer';
|
|
7
|
+
export type { ConjugationEnding, NormalizerConfig } from './base-normalizer';
|
package/src/core/types.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* These types are domain-agnostic - they work for any DSL (SQL, animations, etc.)
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import type { Diagnostic } from '../generation/diagnostics';
|
|
12
|
+
|
|
11
13
|
// =============================================================================
|
|
12
14
|
// Action Types (Generic)
|
|
13
15
|
// =============================================================================
|
|
@@ -56,7 +58,8 @@ export type SemanticValue =
|
|
|
56
58
|
| SelectorValue
|
|
57
59
|
| ReferenceValue
|
|
58
60
|
| PropertyPathValue
|
|
59
|
-
| ExpressionValue
|
|
61
|
+
| ExpressionValue
|
|
62
|
+
| FlagValue;
|
|
60
63
|
|
|
61
64
|
/**
|
|
62
65
|
* Expected value types for role tokens.
|
|
@@ -93,6 +96,49 @@ export interface ExpressionValue {
|
|
|
93
96
|
readonly raw: string;
|
|
94
97
|
}
|
|
95
98
|
|
|
99
|
+
/**
|
|
100
|
+
* A boolean flag value — present (+flag) or negated (~flag).
|
|
101
|
+
* Used in declarative domains for no-value attributes like primary-key, not-null.
|
|
102
|
+
*/
|
|
103
|
+
export interface FlagValue {
|
|
104
|
+
readonly type: 'flag';
|
|
105
|
+
readonly name: string;
|
|
106
|
+
readonly enabled: boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// =============================================================================
|
|
110
|
+
// Annotations & Diagnostics (v1.2)
|
|
111
|
+
// =============================================================================
|
|
112
|
+
|
|
113
|
+
/** A metadata annotation on a node (v1.2). */
|
|
114
|
+
export interface Annotation {
|
|
115
|
+
readonly name: string;
|
|
116
|
+
readonly value?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* A type constraint diagnostic attached to a node (v1.2).
|
|
121
|
+
*
|
|
122
|
+
* Named `ProtocolDiagnostic` to avoid collision with the framework's own
|
|
123
|
+
* `Diagnostic` type (which has `severity: 'error' | 'warning' | 'info'`
|
|
124
|
+
* and `suggestion?: string`).
|
|
125
|
+
*/
|
|
126
|
+
export interface ProtocolDiagnostic {
|
|
127
|
+
readonly level: 'error' | 'warning';
|
|
128
|
+
readonly role: string;
|
|
129
|
+
readonly message: string;
|
|
130
|
+
readonly code: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Async coordination variant (v1.2). */
|
|
134
|
+
export type AsyncVariant = 'all' | 'race';
|
|
135
|
+
|
|
136
|
+
/** A single arm in a match command (v1.2). */
|
|
137
|
+
export interface MatchArm {
|
|
138
|
+
readonly pattern: SemanticValue;
|
|
139
|
+
readonly body: SemanticNode[];
|
|
140
|
+
}
|
|
141
|
+
|
|
96
142
|
// =============================================================================
|
|
97
143
|
// Semantic Nodes
|
|
98
144
|
// =============================================================================
|
|
@@ -106,6 +152,10 @@ export interface SemanticNode {
|
|
|
106
152
|
readonly action: ActionType;
|
|
107
153
|
readonly roles: ReadonlyMap<SemanticRole, SemanticValue>;
|
|
108
154
|
readonly metadata?: SemanticMetadata;
|
|
155
|
+
/** Metadata annotations (v1.2). */
|
|
156
|
+
readonly annotations?: readonly Annotation[];
|
|
157
|
+
/** Diagnostics from parsing, validation, and schema checks (v1.2, extended v1.2.1). */
|
|
158
|
+
readonly diagnostics?: readonly Diagnostic[];
|
|
109
159
|
}
|
|
110
160
|
|
|
111
161
|
/**
|
|
@@ -137,9 +187,27 @@ export interface SourcePosition {
|
|
|
137
187
|
|
|
138
188
|
/**
|
|
139
189
|
* A command semantic node - represents a single DSL command.
|
|
190
|
+
*
|
|
191
|
+
* v1.2 adds optional fields for try/catch/finally, async coordination (all/race),
|
|
192
|
+
* and pattern matching (match/arms). These are encoded as fields on command nodes
|
|
193
|
+
* (matching the protocol wire format) rather than new node kinds.
|
|
140
194
|
*/
|
|
141
195
|
export interface CommandSemanticNode extends SemanticNode {
|
|
142
196
|
readonly kind: 'command';
|
|
197
|
+
/** try body — commands to execute in the try block (v1.2). */
|
|
198
|
+
readonly body?: readonly SemanticNode[];
|
|
199
|
+
/** catch branch — commands to execute on error (v1.2). */
|
|
200
|
+
readonly catchBranch?: readonly SemanticNode[];
|
|
201
|
+
/** finally branch — cleanup commands that always execute (v1.2). */
|
|
202
|
+
readonly finallyBranch?: readonly SemanticNode[];
|
|
203
|
+
/** Async coordination variant: all (wait for all) or race (first wins) (v1.2). */
|
|
204
|
+
readonly asyncVariant?: AsyncVariant;
|
|
205
|
+
/** Async body — concurrent commands for all/race (v1.2). */
|
|
206
|
+
readonly asyncBody?: readonly SemanticNode[];
|
|
207
|
+
/** Match arms — pattern/body pairs for match command (v1.2). */
|
|
208
|
+
readonly arms?: readonly MatchArm[];
|
|
209
|
+
/** Default arm — executed when no match arm matches (v1.2). */
|
|
210
|
+
readonly defaultArm?: readonly SemanticNode[];
|
|
143
211
|
}
|
|
144
212
|
|
|
145
213
|
/**
|
|
@@ -177,7 +245,7 @@ export interface ConditionalSemanticNode extends SemanticNode {
|
|
|
177
245
|
export interface CompoundSemanticNode extends SemanticNode {
|
|
178
246
|
readonly kind: 'compound';
|
|
179
247
|
readonly statements: SemanticNode[];
|
|
180
|
-
readonly chainType: 'then' | 'and' | 'async' | 'sequential';
|
|
248
|
+
readonly chainType: 'then' | 'and' | 'async' | 'sequential' | 'pipe';
|
|
181
249
|
}
|
|
182
250
|
|
|
183
251
|
/**
|
|
@@ -444,6 +512,13 @@ export function createExpression(raw: string): ExpressionValue {
|
|
|
444
512
|
return { type: 'expression', raw };
|
|
445
513
|
}
|
|
446
514
|
|
|
515
|
+
/**
|
|
516
|
+
* Create a boolean flag value (+flag or ~flag)
|
|
517
|
+
*/
|
|
518
|
+
export function createFlag(name: string, enabled: boolean = true): FlagValue {
|
|
519
|
+
return { type: 'flag', name, enabled };
|
|
520
|
+
}
|
|
521
|
+
|
|
447
522
|
/**
|
|
448
523
|
* Create a command semantic node
|
|
449
524
|
*/
|
|
@@ -542,6 +617,7 @@ export function createCompoundNode(
|
|
|
542
617
|
* eliminating the need for `as any` casts in domain code generators.
|
|
543
618
|
*/
|
|
544
619
|
export function extractValue(value: SemanticValue): string {
|
|
620
|
+
if (value.type === 'flag') return value.name;
|
|
545
621
|
if ('raw' in value && value.raw !== undefined) return String(value.raw);
|
|
546
622
|
if ('value' in value && value.value !== undefined) return String(value.value);
|
|
547
623
|
if (value.type === 'property-path') return `${extractValue(value.object)}.${value.property}`;
|
|
@@ -587,3 +663,72 @@ export function createLoopNode(
|
|
|
587
663
|
...(metadata && { metadata }),
|
|
588
664
|
};
|
|
589
665
|
}
|
|
666
|
+
|
|
667
|
+
// =============================================================================
|
|
668
|
+
// v1.2 Factory Helpers
|
|
669
|
+
// =============================================================================
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Create a try/catch/finally command node (v1.2).
|
|
673
|
+
*/
|
|
674
|
+
export function createTryNode(
|
|
675
|
+
body: SemanticNode[],
|
|
676
|
+
catchBranch?: SemanticNode[],
|
|
677
|
+
finallyBranch?: SemanticNode[],
|
|
678
|
+
metadata?: SemanticMetadata
|
|
679
|
+
): CommandSemanticNode {
|
|
680
|
+
return {
|
|
681
|
+
kind: 'command',
|
|
682
|
+
action: 'try',
|
|
683
|
+
roles: new Map(),
|
|
684
|
+
body,
|
|
685
|
+
...(catchBranch && catchBranch.length > 0 ? { catchBranch } : {}),
|
|
686
|
+
...(finallyBranch && finallyBranch.length > 0 ? { finallyBranch } : {}),
|
|
687
|
+
...(metadata ? { metadata } : {}),
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Create an async coordination (all/race) command node (v1.2).
|
|
693
|
+
*/
|
|
694
|
+
export function createAsyncNode(
|
|
695
|
+
variant: AsyncVariant,
|
|
696
|
+
asyncBody: SemanticNode[],
|
|
697
|
+
metadata?: SemanticMetadata
|
|
698
|
+
): CommandSemanticNode {
|
|
699
|
+
return {
|
|
700
|
+
kind: 'command',
|
|
701
|
+
action: variant,
|
|
702
|
+
roles: new Map(),
|
|
703
|
+
asyncVariant: variant,
|
|
704
|
+
asyncBody,
|
|
705
|
+
...(metadata ? { metadata } : {}),
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Create a match command node with pattern arms (v1.2).
|
|
711
|
+
*/
|
|
712
|
+
export function createMatchNode(
|
|
713
|
+
roles: Record<SemanticRole, SemanticValue> | Map<SemanticRole, SemanticValue>,
|
|
714
|
+
arms: MatchArm[],
|
|
715
|
+
defaultArm?: SemanticNode[],
|
|
716
|
+
metadata?: SemanticMetadata
|
|
717
|
+
): CommandSemanticNode {
|
|
718
|
+
const rolesMap = roles instanceof Map ? roles : new Map(Object.entries(roles));
|
|
719
|
+
return {
|
|
720
|
+
kind: 'command',
|
|
721
|
+
action: 'match',
|
|
722
|
+
roles: rolesMap,
|
|
723
|
+
arms,
|
|
724
|
+
...(defaultArm && defaultArm.length > 0 ? { defaultArm } : {}),
|
|
725
|
+
...(metadata ? { metadata } : {}),
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/** Wire format envelope with version metadata (v1.2). */
|
|
730
|
+
export interface LSEEnvelope {
|
|
731
|
+
readonly lseVersion: string;
|
|
732
|
+
readonly features?: readonly string[];
|
|
733
|
+
readonly nodes: readonly SemanticNode[];
|
|
734
|
+
}
|