@lokascript/framework 4.7.0 → 4.8.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/dist/api/create-dsl.d.ts +5 -2
  3. package/dist/api/create-dsl.d.ts.map +1 -1
  4. package/dist/api/index.js +45 -5
  5. package/dist/api/index.js.map +1 -1
  6. package/dist/core/index.js +10 -0
  7. package/dist/core/index.js.map +1 -1
  8. package/dist/core/matching/stock-reader.d.ts +8 -1
  9. package/dist/core/matching/stock-reader.d.ts.map +1 -1
  10. package/dist/core/pattern-matching/index.js.map +1 -1
  11. package/dist/core/types.d.ts +5 -0
  12. package/dist/core/types.d.ts.map +1 -1
  13. package/dist/core/types.js.map +1 -1
  14. package/dist/generation/index.js +12 -2
  15. package/dist/generation/index.js.map +1 -1
  16. package/dist/index.cjs +45 -5
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.js +45 -5
  19. package/dist/index.js.map +1 -1
  20. package/dist/ir/index.js.map +1 -1
  21. package/dist/parsing/index.js.map +1 -1
  22. package/dist/testing/gate/index.js +44 -12
  23. package/dist/testing/gate/index.js.map +1 -1
  24. package/dist/testing/gate/round-trip.d.ts +24 -3
  25. package/dist/testing/gate/round-trip.d.ts.map +1 -1
  26. package/package.json +2 -2
  27. package/src/api/create-dsl-engine.test.ts +65 -1
  28. package/src/api/create-dsl.test.ts +7 -0
  29. package/src/api/create-dsl.ts +32 -4
  30. package/src/core/matching/stock-reader.test.ts +87 -3
  31. package/src/core/matching/stock-reader.ts +20 -1
  32. package/src/core/types.ts +5 -0
  33. package/src/generation/schema-patterns.test.ts +41 -1
  34. package/src/generation/schema-patterns.ts +12 -0
  35. package/src/testing/gate/round-trip.test.ts +99 -9
  36. package/src/testing/gate/round-trip.ts +76 -11
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/types.ts"],"sourcesContent":["/**\n * Core Types — re-exported from @lokascript/intent + framework-specific extensions\n *\n * The universal semantic types (SemanticNode, SemanticValue, etc.) now live in\n * @lokascript/intent and are re-exported here for backward compatibility.\n *\n * Framework-specific types (tokenization, pattern matching) are defined below.\n */\n\n// =============================================================================\n// Re-export everything from @lokascript/intent\n// =============================================================================\n\nexport type {\n ActionType,\n SemanticRole,\n SemanticValue,\n ExpectedType,\n LiteralValue,\n SelectorValue,\n ReferenceValue,\n PropertyPathValue,\n ExpressionValue,\n FlagValue,\n Annotation,\n ProtocolDiagnostic,\n AsyncVariant,\n MatchArm,\n SemanticNode,\n SemanticMetadata,\n SourcePosition,\n CommandSemanticNode,\n EventHandlerSemanticNode,\n EventModifiers,\n ConditionalSemanticNode,\n CompoundSemanticNode,\n LoopVariant,\n LoopSemanticNode,\n LSEEnvelope,\n} from '@lokascript/intent';\n\nexport {\n createLiteral,\n createSelector,\n createReference,\n createPropertyPath,\n createExpression,\n createFlag,\n createCommandNode,\n createEventHandlerNode,\n createConditionalNode,\n createCompoundNode,\n createLoopNode,\n createTryNode,\n createAsyncNode,\n createMatchNode,\n extractValue,\n extractRoleValue,\n getRoleValue,\n} from '@lokascript/intent';\n\n// =============================================================================\n// Framework-specific types (multilingual DSL infrastructure)\n// =============================================================================\n\nimport type {\n ActionType,\n SemanticRole,\n SemanticValue,\n SelectorValue,\n ExpectedType,\n SourcePosition,\n} from '@lokascript/intent';\n\n/**\n * Token kind - categorizes what type of token this is.\n */\nexport type TokenKind =\n | 'keyword' // Command or modifier keyword\n | 'selector' // CSS selector or identifier (#id, .class, table-name)\n | 'literal' // String or number literal\n | 'particle' // Grammatical particle (を, に, من)\n | 'conjunction' // Grammatical conjunction\n | 'event-modifier' // Event modifier (.once, .debounce(300))\n | 'identifier' // Generic identifier\n | 'operator' // Operators (., +, -, etc.)\n | 'punctuation' // Punctuation (parentheses, etc.)\n | 'url'; // URL or path\n\n/**\n * A language token - the result of tokenization.\n */\nexport interface LanguageToken {\n readonly value: string;\n readonly kind: TokenKind;\n readonly position: SourcePosition;\n readonly normalized?: string;\n readonly stem?: string;\n readonly stemConfidence?: number;\n readonly metadata?: Record<string, unknown>;\n}\n\n/**\n * Token stream - provides sequential access to tokens with backtracking.\n */\nexport interface TokenStream {\n readonly tokens: readonly LanguageToken[];\n readonly language: string;\n peek(offset?: number): LanguageToken | null;\n advance(): LanguageToken;\n isAtEnd(): boolean;\n mark(): StreamMark;\n reset(mark: StreamMark): void;\n position(): number;\n}\n\nexport interface StreamMark {\n readonly position: number;\n}\n\n/**\n * Language tokenizer interface - converts text to tokens.\n */\nexport interface LanguageTokenizer {\n readonly language: string;\n readonly direction: 'ltr' | 'rtl';\n tokenize(input: string): TokenStream;\n classifyToken(token: string): TokenKind;\n}\n\n// =============================================================================\n// Pattern Matching Types\n// =============================================================================\n\nexport interface LanguagePattern {\n readonly id: string;\n readonly language: string;\n readonly command: ActionType;\n readonly priority: number;\n readonly template: PatternTemplate;\n readonly extraction: ExtractionRules;\n readonly constraints?: PatternConstraints;\n /**\n * The pattern reads a form the renderer does not write by choosing it\n * (hyperscript's wait `esperar evento {event}`: the renderer writes the event\n * word itself, before a name no table knows). The renderer skips it.\n */\n readonly parseOnly?: boolean;\n}\n\nexport interface PatternTemplate {\n readonly format: string;\n readonly tokens: PatternToken[];\n}\n\nexport type PatternToken = LiteralPatternToken | RolePatternToken | GroupPatternToken;\n\nexport interface LiteralPatternToken {\n readonly type: 'literal';\n readonly value: string;\n readonly alternatives?: string[];\n /**\n * Parse-only marker: the matcher still consumes this literal, but the\n * renderer skips it. Set when a role declares `renderOverride: { <lang>: '' }`\n * (hyperscript's fetch `from`: required to PARSE `fetch from /api`, absent from\n * the render, since `fetch from \"/api\"` is not canonical _hyperscript).\n */\n readonly renderSuppress?: boolean;\n}\n\nexport interface RolePatternToken {\n readonly type: 'role';\n readonly role: SemanticRole;\n readonly optional?: boolean;\n readonly expectedTypes?: Array<ExpectedType>;\n readonly greedy?: boolean;\n /**\n * Mirrors the schema's `RoleSpec.valueShape`: the slot's surface form is\n * unambiguous on its own. Carried onto the pattern token because the value\n * reader needs it too, not just the confidence model (hyperscript's\n * `'keyword'` slots hold a fixed keyword phrase and are exempt from the\n * trailing-slot verb guard).\n */\n readonly valueShape?: 'time' | 'reference' | 'keyword' | 'object' | 'clause' | 'list';\n}\n\nexport interface GroupPatternToken {\n readonly type: 'group';\n readonly tokens: PatternToken[];\n readonly optional?: boolean;\n /**\n * The group is optional for PARSING but belongs in canonical output: the\n * marker-only groups a profile's optional case markers produce (Turkish case\n * suffixes may be dropped colloquially, so the parse accepts both forms).\n * The matcher's skeleton also requires such a group's marker after a role it\n * captures (`MatchSkeleton`).\n */\n readonly renderRequired?: boolean;\n}\n\nexport interface ExtractionRules {\n readonly [role: string]: ExtractionRule;\n}\n\nexport interface ExtractionRule {\n readonly position?: number;\n readonly marker?: string;\n readonly markerAlternatives?: string[];\n readonly transform?: (raw: string) => SemanticValue;\n readonly default?: SemanticValue;\n readonly value?: string;\n /**\n * The static `value` is an expression, as the English parse types the word\n * (hyperscript's `go back` destination), not a literal.\n */\n readonly valueIsExpression?: true;\n readonly fromRole?: string;\n}\n\nexport interface PatternConstraints {\n readonly requiredRoles?: SemanticRole[];\n readonly forbiddenRoles?: SemanticRole[];\n readonly validPatientTypes?: Array<SelectorValue['selectorKind']>;\n readonly conflictsWith?: string[];\n}\n\nexport interface PatternMatchResult {\n readonly pattern: LanguagePattern;\n readonly captured: ReadonlyMap<SemanticRole, SemanticValue>;\n readonly consumedTokens: number;\n readonly confidence: number;\n /**\n * Stream index at which each captured role's value STARTED, for callers that\n * need to re-read a slot's own span rather than the tail after it. Populated\n * on the same success path as `captured` (`MatchSkeleton`), so a role left\n * behind by a rolled-back group survives in both maps or in neither.\n */\n readonly roleStarts?: ReadonlyMap<SemanticRole, number>;\n /**\n * A source phrase hyperscript's reader found in an event HEAD (`from #btn`\n * right after the event) rather than in a pattern slot: kept out of\n * `captured`, so a fused pattern's own source group can neither overwrite it\n * nor be overwritten (`ValueReader.resultFields`).\n */\n readonly eventSource?: SemanticValue;\n}\n\nexport interface PatternMatchError {\n readonly message: string;\n readonly position: SourcePosition;\n readonly expectedPatterns?: string[];\n readonly partialMatch?: Partial<PatternMatchResult>;\n}\n"],"mappings":";AAyCA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":[]}
1
+ {"version":3,"sources":["../../src/core/types.ts"],"sourcesContent":["/**\n * Core Types — re-exported from @lokascript/intent + framework-specific extensions\n *\n * The universal semantic types (SemanticNode, SemanticValue, etc.) now live in\n * @lokascript/intent and are re-exported here for backward compatibility.\n *\n * Framework-specific types (tokenization, pattern matching) are defined below.\n */\n\n// =============================================================================\n// Re-export everything from @lokascript/intent\n// =============================================================================\n\nexport type {\n ActionType,\n SemanticRole,\n SemanticValue,\n ExpectedType,\n LiteralValue,\n SelectorValue,\n ReferenceValue,\n PropertyPathValue,\n ExpressionValue,\n FlagValue,\n Annotation,\n ProtocolDiagnostic,\n AsyncVariant,\n MatchArm,\n SemanticNode,\n SemanticMetadata,\n SourcePosition,\n CommandSemanticNode,\n EventHandlerSemanticNode,\n EventModifiers,\n ConditionalSemanticNode,\n CompoundSemanticNode,\n LoopVariant,\n LoopSemanticNode,\n LSEEnvelope,\n} from '@lokascript/intent';\n\nexport {\n createLiteral,\n createSelector,\n createReference,\n createPropertyPath,\n createExpression,\n createFlag,\n createCommandNode,\n createEventHandlerNode,\n createConditionalNode,\n createCompoundNode,\n createLoopNode,\n createTryNode,\n createAsyncNode,\n createMatchNode,\n extractValue,\n extractRoleValue,\n getRoleValue,\n} from '@lokascript/intent';\n\n// =============================================================================\n// Framework-specific types (multilingual DSL infrastructure)\n// =============================================================================\n\nimport type {\n ActionType,\n SemanticRole,\n SemanticValue,\n SelectorValue,\n ExpectedType,\n SourcePosition,\n} from '@lokascript/intent';\n\n/**\n * Token kind - categorizes what type of token this is.\n */\nexport type TokenKind =\n | 'keyword' // Command or modifier keyword\n | 'selector' // CSS selector or identifier (#id, .class, table-name)\n | 'literal' // String or number literal\n | 'particle' // Grammatical particle (を, に, من)\n | 'conjunction' // Grammatical conjunction\n | 'event-modifier' // Event modifier (.once, .debounce(300))\n | 'identifier' // Generic identifier\n | 'operator' // Operators (., +, -, etc.)\n | 'punctuation' // Punctuation (parentheses, etc.)\n | 'url'; // URL or path\n\n/**\n * A language token - the result of tokenization.\n */\nexport interface LanguageToken {\n readonly value: string;\n readonly kind: TokenKind;\n readonly position: SourcePosition;\n readonly normalized?: string;\n readonly stem?: string;\n readonly stemConfidence?: number;\n readonly metadata?: Record<string, unknown>;\n}\n\n/**\n * Token stream - provides sequential access to tokens with backtracking.\n */\nexport interface TokenStream {\n readonly tokens: readonly LanguageToken[];\n readonly language: string;\n peek(offset?: number): LanguageToken | null;\n advance(): LanguageToken;\n isAtEnd(): boolean;\n mark(): StreamMark;\n reset(mark: StreamMark): void;\n position(): number;\n}\n\nexport interface StreamMark {\n readonly position: number;\n}\n\n/**\n * Language tokenizer interface - converts text to tokens.\n */\nexport interface LanguageTokenizer {\n readonly language: string;\n readonly direction: 'ltr' | 'rtl';\n tokenize(input: string): TokenStream;\n classifyToken(token: string): TokenKind;\n}\n\n// =============================================================================\n// Pattern Matching Types\n// =============================================================================\n\nexport interface LanguagePattern {\n readonly id: string;\n readonly language: string;\n readonly command: ActionType;\n readonly priority: number;\n readonly template: PatternTemplate;\n readonly extraction: ExtractionRules;\n readonly constraints?: PatternConstraints;\n /**\n * The pattern reads a form the renderer does not write by choosing it\n * (hyperscript's wait `esperar evento {event}`: the renderer writes the event\n * word itself, before a name no table knows). The renderer skips it.\n */\n readonly parseOnly?: boolean;\n}\n\nexport interface PatternTemplate {\n readonly format: string;\n readonly tokens: PatternToken[];\n}\n\nexport type PatternToken = LiteralPatternToken | RolePatternToken | GroupPatternToken;\n\nexport interface LiteralPatternToken {\n readonly type: 'literal';\n readonly value: string;\n readonly alternatives?: string[];\n /**\n * Parse-only marker: the matcher still consumes this literal, but the\n * renderer skips it. Set when a role declares `renderOverride: { <lang>: '' }`\n * (hyperscript's fetch `from`: required to PARSE `fetch from /api`, absent from\n * the render, since `fetch from \"/api\"` is not canonical _hyperscript).\n */\n readonly renderSuppress?: boolean;\n}\n\nexport interface RolePatternToken {\n readonly type: 'role';\n readonly role: SemanticRole;\n readonly optional?: boolean;\n readonly expectedTypes?: Array<ExpectedType>;\n readonly greedy?: boolean;\n /**\n * The words the slot reads, from the schema's `RoleSpec.words` for the pattern's language (its\n * `'*'` list and the language's own): the stock reader reads the slot only from one of them.\n */\n readonly words?: readonly string[];\n /**\n * Mirrors the schema's `RoleSpec.valueShape`: the slot's surface form is\n * unambiguous on its own. Carried onto the pattern token because the value\n * reader needs it too, not just the confidence model (hyperscript's\n * `'keyword'` slots hold a fixed keyword phrase and are exempt from the\n * trailing-slot verb guard).\n */\n readonly valueShape?: 'time' | 'reference' | 'keyword' | 'object' | 'clause' | 'list';\n}\n\nexport interface GroupPatternToken {\n readonly type: 'group';\n readonly tokens: PatternToken[];\n readonly optional?: boolean;\n /**\n * The group is optional for PARSING but belongs in canonical output: the\n * marker-only groups a profile's optional case markers produce (Turkish case\n * suffixes may be dropped colloquially, so the parse accepts both forms).\n * The matcher's skeleton also requires such a group's marker after a role it\n * captures (`MatchSkeleton`).\n */\n readonly renderRequired?: boolean;\n}\n\nexport interface ExtractionRules {\n readonly [role: string]: ExtractionRule;\n}\n\nexport interface ExtractionRule {\n readonly position?: number;\n readonly marker?: string;\n readonly markerAlternatives?: string[];\n readonly transform?: (raw: string) => SemanticValue;\n readonly default?: SemanticValue;\n readonly value?: string;\n /**\n * The static `value` is an expression, as the English parse types the word\n * (hyperscript's `go back` destination), not a literal.\n */\n readonly valueIsExpression?: true;\n readonly fromRole?: string;\n}\n\nexport interface PatternConstraints {\n readonly requiredRoles?: SemanticRole[];\n readonly forbiddenRoles?: SemanticRole[];\n readonly validPatientTypes?: Array<SelectorValue['selectorKind']>;\n readonly conflictsWith?: string[];\n}\n\nexport interface PatternMatchResult {\n readonly pattern: LanguagePattern;\n readonly captured: ReadonlyMap<SemanticRole, SemanticValue>;\n readonly consumedTokens: number;\n readonly confidence: number;\n /**\n * Stream index at which each captured role's value STARTED, for callers that\n * need to re-read a slot's own span rather than the tail after it. Populated\n * on the same success path as `captured` (`MatchSkeleton`), so a role left\n * behind by a rolled-back group survives in both maps or in neither.\n */\n readonly roleStarts?: ReadonlyMap<SemanticRole, number>;\n /**\n * A source phrase hyperscript's reader found in an event HEAD (`from #btn`\n * right after the event) rather than in a pattern slot: kept out of\n * `captured`, so a fused pattern's own source group can neither overwrite it\n * nor be overwritten (`ValueReader.resultFields`).\n */\n readonly eventSource?: SemanticValue;\n}\n\nexport interface PatternMatchError {\n readonly message: string;\n readonly position: SourcePosition;\n readonly expectedPatterns?: string[];\n readonly partialMatch?: Partial<PatternMatchResult>;\n}\n"],"mappings":";AAyCA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":[]}
@@ -404,11 +404,13 @@ function generateVerbFirstPattern(schema, profile, config = defaultConfig2) {
404
404
  const verbToken = keyword.alternatives ? { type: "literal", value: keyword.primary, alternatives: keyword.alternatives } : { type: "literal", value: keyword.primary };
405
405
  const roleTokens = requiredRoles.flatMap((r) => {
406
406
  const prefix = r.valuePrefixLiteral?.[profile.code];
407
+ const words = wordsFor(r, profile.code);
407
408
  const roleToken = {
408
409
  type: "role",
409
410
  role: r.role,
410
411
  optional: false,
411
- expectedTypes: r.expectedTypes
412
+ expectedTypes: r.expectedTypes,
413
+ ...words ? { words } : {}
412
414
  };
413
415
  return prefix ? [{ type: "literal", value: prefix }, roleToken] : [roleToken];
414
416
  });
@@ -578,11 +580,17 @@ function buildRoleTokens(grammar, schema, profile) {
578
580
  }
579
581
  return tokens;
580
582
  }
583
+ function wordsFor(roleSpec, language) {
584
+ if (!roleSpec.words) return void 0;
585
+ const words = [...roleSpec.words["*"] ?? [], ...roleSpec.words[language] ?? []];
586
+ return words.length > 0 ? words : void 0;
587
+ }
581
588
  function buildRoleToken(roleSpec, profile) {
582
589
  const tokens = [];
583
590
  const overrideMarker = roleSpec.markerOverride?.[profile.code];
584
591
  const defaultMarker = profile.roleMarkers[roleSpec.role];
585
592
  const suppressMarker = roleSpec.renderOverride?.[profile.code] === "";
593
+ const words = wordsFor(roleSpec, profile.code);
586
594
  const roleValueToken = {
587
595
  type: "role",
588
596
  role: roleSpec.role,
@@ -594,7 +602,9 @@ function buildRoleToken(roleSpec, profile) {
594
602
  ...roleSpec.valueShape !== void 0 ? { valueShape: roleSpec.valueShape } : {},
595
603
  // A greedy role reads up to the next marker the pattern owes (a domain's
596
604
  // SQL condition); its value reader does the reading.
597
- ...roleSpec.greedy ? { greedy: true } : {}
605
+ ...roleSpec.greedy ? { greedy: true } : {},
606
+ // A role with a word list reads only one of its words.
607
+ ...words ? { words } : {}
598
608
  };
599
609
  const prefixLiteral = roleSpec.valuePrefixLiteral?.[profile.code];
600
610
  const pushPrefixed = () => {