@lokascript/framework 2.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.
Files changed (181) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +142 -0
  3. package/dist/aot/aot-orchestrator.d.ts +75 -0
  4. package/dist/aot/aot-orchestrator.d.ts.map +1 -0
  5. package/dist/aot/domain-scanner.d.ts +27 -0
  6. package/dist/aot/domain-scanner.d.ts.map +1 -0
  7. package/dist/aot/index.d.ts +8 -0
  8. package/dist/aot/index.d.ts.map +1 -0
  9. package/dist/aot/types.d.ts +103 -0
  10. package/dist/aot/types.d.ts.map +1 -0
  11. package/dist/api/create-dsl.d.ts +91 -0
  12. package/dist/api/create-dsl.d.ts.map +1 -0
  13. package/dist/api/dispatcher.d.ts +108 -0
  14. package/dist/api/dispatcher.d.ts.map +1 -0
  15. package/dist/api/domain-registry.d.ts +152 -0
  16. package/dist/api/domain-registry.d.ts.map +1 -0
  17. package/dist/api/index.d.ts +7 -0
  18. package/dist/api/index.d.ts.map +1 -0
  19. package/dist/api/index.js +2082 -0
  20. package/dist/api/index.js.map +1 -0
  21. package/dist/core/index.d.ts +7 -0
  22. package/dist/core/index.d.ts.map +1 -0
  23. package/dist/core/index.js +2674 -0
  24. package/dist/core/index.js.map +1 -0
  25. package/dist/core/logger.d.ts +32 -0
  26. package/dist/core/logger.d.ts.map +1 -0
  27. package/dist/core/pattern-matching/index.d.ts +6 -0
  28. package/dist/core/pattern-matching/index.d.ts.map +1 -0
  29. package/dist/core/pattern-matching/index.js +1239 -0
  30. package/dist/core/pattern-matching/index.js.map +1 -0
  31. package/dist/core/pattern-matching/pattern-matcher.d.ts +239 -0
  32. package/dist/core/pattern-matching/pattern-matcher.d.ts.map +1 -0
  33. package/dist/core/pattern-matching/utils/index.d.ts +6 -0
  34. package/dist/core/pattern-matching/utils/index.d.ts.map +1 -0
  35. package/dist/core/pattern-matching/utils/possessive-keywords.d.ts +38 -0
  36. package/dist/core/pattern-matching/utils/possessive-keywords.d.ts.map +1 -0
  37. package/dist/core/pattern-matching/utils/type-validation.d.ts +63 -0
  38. package/dist/core/pattern-matching/utils/type-validation.d.ts.map +1 -0
  39. package/dist/core/tokenization/base-tokenizer.d.ts +344 -0
  40. package/dist/core/tokenization/base-tokenizer.d.ts.map +1 -0
  41. package/dist/core/tokenization/char-classifiers.d.ts +56 -0
  42. package/dist/core/tokenization/char-classifiers.d.ts.map +1 -0
  43. package/dist/core/tokenization/default-extractors.d.ts +48 -0
  44. package/dist/core/tokenization/default-extractors.d.ts.map +1 -0
  45. package/dist/core/tokenization/extractors/index.d.ts +9 -0
  46. package/dist/core/tokenization/extractors/index.d.ts.map +1 -0
  47. package/dist/core/tokenization/extractors/operator.d.ts +23 -0
  48. package/dist/core/tokenization/extractors/operator.d.ts.map +1 -0
  49. package/dist/core/tokenization/extractors/punctuation.d.ts +22 -0
  50. package/dist/core/tokenization/extractors/punctuation.d.ts.map +1 -0
  51. package/dist/core/tokenization/extractors.d.ts +61 -0
  52. package/dist/core/tokenization/extractors.d.ts.map +1 -0
  53. package/dist/core/tokenization/index.d.ts +11 -0
  54. package/dist/core/tokenization/index.d.ts.map +1 -0
  55. package/dist/core/tokenization/index.js +1345 -0
  56. package/dist/core/tokenization/index.js.map +1 -0
  57. package/dist/core/tokenization/morphology/index.d.ts +5 -0
  58. package/dist/core/tokenization/morphology/index.d.ts.map +1 -0
  59. package/dist/core/tokenization/morphology/types.d.ts +110 -0
  60. package/dist/core/tokenization/morphology/types.d.ts.map +1 -0
  61. package/dist/core/tokenization/token-utils.d.ts +111 -0
  62. package/dist/core/tokenization/token-utils.d.ts.map +1 -0
  63. package/dist/core/types.d.ts +382 -0
  64. package/dist/core/types.d.ts.map +1 -0
  65. package/dist/core/types.js +108 -0
  66. package/dist/core/types.js.map +1 -0
  67. package/dist/generation/diagnostics.d.ts +120 -0
  68. package/dist/generation/diagnostics.d.ts.map +1 -0
  69. package/dist/generation/index.d.ts +7 -0
  70. package/dist/generation/index.d.ts.map +1 -0
  71. package/dist/generation/index.js +339 -0
  72. package/dist/generation/index.js.map +1 -0
  73. package/dist/generation/pattern-generator.d.ts +48 -0
  74. package/dist/generation/pattern-generator.d.ts.map +1 -0
  75. package/dist/generation/renderer.d.ts +115 -0
  76. package/dist/generation/renderer.d.ts.map +1 -0
  77. package/dist/grammar/index.d.ts +10 -0
  78. package/dist/grammar/index.d.ts.map +1 -0
  79. package/dist/grammar/index.js +391 -0
  80. package/dist/grammar/index.js.map +1 -0
  81. package/dist/grammar/transformer.d.ts +56 -0
  82. package/dist/grammar/transformer.d.ts.map +1 -0
  83. package/dist/grammar/types.d.ts +236 -0
  84. package/dist/grammar/types.d.ts.map +1 -0
  85. package/dist/index.cjs +4454 -0
  86. package/dist/index.cjs.map +1 -0
  87. package/dist/index.d.ts +46 -0
  88. package/dist/index.d.ts.map +1 -0
  89. package/dist/index.js +4336 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/interfaces/dictionary.d.ts +82 -0
  92. package/dist/interfaces/dictionary.d.ts.map +1 -0
  93. package/dist/interfaces/index.d.ts +10 -0
  94. package/dist/interfaces/index.d.ts.map +1 -0
  95. package/dist/interfaces/profile-provider.d.ts +67 -0
  96. package/dist/interfaces/profile-provider.d.ts.map +1 -0
  97. package/dist/interfaces/value-extractor.d.ts +168 -0
  98. package/dist/interfaces/value-extractor.d.ts.map +1 -0
  99. package/dist/multilingual/index.d.ts +8 -0
  100. package/dist/multilingual/index.d.ts.map +1 -0
  101. package/dist/multilingual/index.js +1 -0
  102. package/dist/multilingual/index.js.map +1 -0
  103. package/dist/parsing/index.d.ts +8 -0
  104. package/dist/parsing/index.d.ts.map +1 -0
  105. package/dist/parsing/index.js +1415 -0
  106. package/dist/parsing/index.js.map +1 -0
  107. package/dist/parsing/multi-statement.d.ts +265 -0
  108. package/dist/parsing/multi-statement.d.ts.map +1 -0
  109. package/dist/schema/command-schema.d.ts +78 -0
  110. package/dist/schema/command-schema.d.ts.map +1 -0
  111. package/dist/schema/index.d.ts +5 -0
  112. package/dist/schema/index.d.ts.map +1 -0
  113. package/dist/schema/index.js +25 -0
  114. package/dist/schema/index.js.map +1 -0
  115. package/dist/test-setup.d.ts +9 -0
  116. package/dist/test-setup.d.ts.map +1 -0
  117. package/dist/testing/index.d.ts +50 -0
  118. package/dist/testing/index.d.ts.map +1 -0
  119. package/dist/testing/index.js +16969 -0
  120. package/dist/testing/index.js.map +1 -0
  121. package/package.json +122 -0
  122. package/src/__test__/fixtures/sql-dsl.ts +232 -0
  123. package/src/__test__/sql-integration.test.ts +189 -0
  124. package/src/__test__/test-utils.ts +260 -0
  125. package/src/aot/aot-orchestrator.test.ts +413 -0
  126. package/src/aot/aot-orchestrator.ts +238 -0
  127. package/src/aot/domain-scanner.ts +178 -0
  128. package/src/aot/index.ts +8 -0
  129. package/src/aot/types.ts +124 -0
  130. package/src/api/create-dsl.ts +367 -0
  131. package/src/api/dispatcher.test.ts +336 -0
  132. package/src/api/dispatcher.ts +222 -0
  133. package/src/api/domain-registry.test.ts +336 -0
  134. package/src/api/domain-registry.ts +500 -0
  135. package/src/api/index.ts +7 -0
  136. package/src/core/index.ts +7 -0
  137. package/src/core/logger.ts +130 -0
  138. package/src/core/pattern-matching/index.ts +6 -0
  139. package/src/core/pattern-matching/pattern-matcher.test.ts +900 -0
  140. package/src/core/pattern-matching/pattern-matcher.ts +1548 -0
  141. package/src/core/pattern-matching/pattern-matcher.ts.backup +1267 -0
  142. package/src/core/pattern-matching/utils/index.ts +6 -0
  143. package/src/core/pattern-matching/utils/possessive-keywords.ts +55 -0
  144. package/src/core/pattern-matching/utils/type-validation.test.ts +316 -0
  145. package/src/core/pattern-matching/utils/type-validation.ts +134 -0
  146. package/src/core/tokenization/base-tokenizer.ts +916 -0
  147. package/src/core/tokenization/char-classifiers.ts +79 -0
  148. package/src/core/tokenization/create-simple-tokenizer.test.ts +260 -0
  149. package/src/core/tokenization/default-extractors.ts +69 -0
  150. package/src/core/tokenization/extractors/index.ts +9 -0
  151. package/src/core/tokenization/extractors/operator.ts +75 -0
  152. package/src/core/tokenization/extractors/punctuation.ts +39 -0
  153. package/src/core/tokenization/extractors.ts +452 -0
  154. package/src/core/tokenization/index.ts +11 -0
  155. package/src/core/tokenization/morphology/index.ts +5 -0
  156. package/src/core/tokenization/morphology/types.ts +211 -0
  157. package/src/core/tokenization/token-utils.ts +252 -0
  158. package/src/core/types.ts +589 -0
  159. package/src/generation/diagnostics.test.ts +171 -0
  160. package/src/generation/diagnostics.ts +239 -0
  161. package/src/generation/index.ts +7 -0
  162. package/src/generation/pattern-generator.test.ts +430 -0
  163. package/src/generation/pattern-generator.ts +315 -0
  164. package/src/generation/renderer.test.ts +266 -0
  165. package/src/generation/renderer.ts +244 -0
  166. package/src/grammar/index.ts +12 -0
  167. package/src/grammar/transformer.ts +159 -0
  168. package/src/grammar/types.ts +630 -0
  169. package/src/index.ts +157 -0
  170. package/src/interfaces/dictionary.ts +123 -0
  171. package/src/interfaces/index.ts +10 -0
  172. package/src/interfaces/profile-provider.ts +88 -0
  173. package/src/interfaces/value-extractor.ts +435 -0
  174. package/src/multilingual/index.ts +9 -0
  175. package/src/parsing/index.ts +27 -0
  176. package/src/parsing/multi-statement.test.ts +480 -0
  177. package/src/parsing/multi-statement.ts +648 -0
  178. package/src/schema/command-schema.ts +118 -0
  179. package/src/schema/index.ts +5 -0
  180. package/src/test-setup.ts +45 -0
  181. package/src/testing/index.ts +137 -0
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Natural Language Renderer
3
+ *
4
+ * Interface and utilities for rendering SemanticNode → human-readable text
5
+ * in any supported language. This is the inverse of parsing: it converts
6
+ * a language-neutral semantic representation back to natural-language syntax.
7
+ *
8
+ * Every domain that supports `translate()` needs a renderer.
9
+ */
10
+
11
+ import type { SemanticNode } from '../core/types';
12
+ import { extractRoleValue } from '../core/types';
13
+ import type { CommandSchema, RoleSpec } from '../schema';
14
+ import type { PatternGenLanguageProfile } from './pattern-generator';
15
+
16
+ // =============================================================================
17
+ // Renderer Interface
18
+ // =============================================================================
19
+
20
+ /**
21
+ * Renders a SemanticNode to human-readable text in a target language.
22
+ */
23
+ export interface NaturalLanguageRenderer {
24
+ render(node: SemanticNode, language: string): string;
25
+ }
26
+
27
+ // =============================================================================
28
+ // Renderer Helpers
29
+ // =============================================================================
30
+
31
+ /**
32
+ * Keyword table: action → language → translated keyword.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * const KEYWORDS: KeywordTable = {
37
+ * select: { en: 'select', ja: '選択', es: 'seleccionar' },
38
+ * insert: { en: 'insert', ja: '挿入', es: 'insertar' },
39
+ * };
40
+ * ```
41
+ */
42
+ export type KeywordTable = Record<string, Record<string, string>>;
43
+
44
+ /**
45
+ * Marker table: marker name → language → translated marker.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const MARKERS: MarkerTable = {
50
+ * from: { en: 'from', ja: 'から', es: 'de' },
51
+ * into: { en: 'into', ja: 'に', es: 'en' },
52
+ * };
53
+ * ```
54
+ */
55
+ export type MarkerTable = Record<string, Record<string, string>>;
56
+
57
+ /**
58
+ * Configuration for building a renderer from schemas and profiles.
59
+ */
60
+ export interface RendererConfig {
61
+ /** Command keyword translations per language */
62
+ readonly keywords: KeywordTable;
63
+
64
+ /** Role marker translations per language (prepositions/particles) */
65
+ readonly markers: MarkerTable;
66
+
67
+ /** Languages that use SOV word order */
68
+ readonly sovLanguages: ReadonlySet<string>;
69
+
70
+ /** Languages that use VSO word order (optional, defaults to empty) */
71
+ readonly vsoLanguages?: ReadonlySet<string>;
72
+ }
73
+
74
+ /**
75
+ * Look up a command keyword in the target language.
76
+ * Falls back to the key itself if no translation found.
77
+ */
78
+ export function lookupKeyword(keywords: KeywordTable, action: string, language: string): string {
79
+ return keywords[action]?.[language] ?? action;
80
+ }
81
+
82
+ /**
83
+ * Look up a role marker in the target language.
84
+ * Falls back to the key itself if no translation found.
85
+ */
86
+ export function lookupMarker(markers: MarkerTable, marker: string, language: string): string {
87
+ return markers[marker]?.[language] ?? marker;
88
+ }
89
+
90
+ /**
91
+ * Build a phrase from parts, filtering empty strings and joining with space.
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * buildPhrase('select', '', 'name', 'from', 'users')
96
+ * // → 'select name from users'
97
+ * ```
98
+ */
99
+ export function buildPhrase(...parts: string[]): string {
100
+ return parts.filter(Boolean).join(' ');
101
+ }
102
+
103
+ /**
104
+ * Build keyword and marker tables from schemas and language profiles.
105
+ * Extracts translations from the profile's keyword definitions and
106
+ * role marker overrides.
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * const { keywords, markers } = buildTablesFromProfiles(schemas, profiles);
111
+ * ```
112
+ */
113
+ export function buildTablesFromProfiles(
114
+ schemas: readonly CommandSchema[],
115
+ profiles: readonly PatternGenLanguageProfile[]
116
+ ): { keywords: KeywordTable; markers: MarkerTable } {
117
+ const keywords: KeywordTable = {};
118
+ const markers: MarkerTable = {};
119
+
120
+ // Build keyword table from profiles
121
+ for (const profile of profiles) {
122
+ for (const [action, kw] of Object.entries(profile.keywords)) {
123
+ if (!keywords[action]) keywords[action] = {};
124
+ keywords[action][profile.code] = kw.primary;
125
+ }
126
+ }
127
+
128
+ // Build marker table from schema markerOverrides
129
+ for (const schema of schemas) {
130
+ for (const role of schema.roles) {
131
+ if (role.markerOverride) {
132
+ // Use role name as marker key
133
+ const markerKey = role.role;
134
+ if (!markers[markerKey]) markers[markerKey] = {};
135
+ for (const [lang, markerText] of Object.entries(role.markerOverride)) {
136
+ markers[markerKey][lang] = markerText;
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ // Also extract role markers from profiles
143
+ for (const profile of profiles) {
144
+ if (profile.roleMarkers) {
145
+ for (const [markerKey, markerDef] of Object.entries(profile.roleMarkers)) {
146
+ if (!markers[markerKey]) markers[markerKey] = {};
147
+ markers[markerKey][profile.code] = markerDef.primary;
148
+ }
149
+ }
150
+ }
151
+
152
+ return { keywords, markers };
153
+ }
154
+
155
+ /**
156
+ * Detect word order from language profiles.
157
+ * Returns sets of SOV and VSO language codes.
158
+ */
159
+ export function detectWordOrders(profiles: readonly PatternGenLanguageProfile[]): {
160
+ sovLanguages: Set<string>;
161
+ vsoLanguages: Set<string>;
162
+ } {
163
+ const sovLanguages = new Set<string>();
164
+ const vsoLanguages = new Set<string>();
165
+
166
+ for (const profile of profiles) {
167
+ if (profile.wordOrder === 'SOV') sovLanguages.add(profile.code);
168
+ if (profile.wordOrder === 'VSO') vsoLanguages.add(profile.code);
169
+ }
170
+
171
+ return { sovLanguages, vsoLanguages };
172
+ }
173
+
174
+ /**
175
+ * Create a basic renderer from schemas and profiles.
176
+ *
177
+ * This generates a renderer that handles word-order reordering automatically.
178
+ * For each command, it constructs the output by placing the keyword and
179
+ * role values in the correct order for the target language.
180
+ *
181
+ * For complex domain-specific rendering, implement `NaturalLanguageRenderer`
182
+ * directly instead.
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * const renderer = createSchemaRenderer(schemas, profiles);
187
+ * renderer.render(node, 'ja'); // → "users から name 選択"
188
+ * ```
189
+ */
190
+ export function createSchemaRenderer(
191
+ schemas: readonly CommandSchema[],
192
+ profiles: readonly PatternGenLanguageProfile[]
193
+ ): NaturalLanguageRenderer {
194
+ const { keywords, markers } = buildTablesFromProfiles(schemas, profiles);
195
+ const { sovLanguages } = detectWordOrders(profiles);
196
+ const schemaMap = new Map<string, CommandSchema>();
197
+ for (const s of schemas) schemaMap.set(s.action, s);
198
+
199
+ return {
200
+ render(node: SemanticNode, language: string): string {
201
+ const schema = schemaMap.get(node.action);
202
+ if (!schema) return node.action;
203
+
204
+ const keyword = lookupKeyword(keywords, node.action, language);
205
+ const isSOV = sovLanguages.has(language);
206
+
207
+ // Collect role parts in schema order
208
+ const roleParts: Array<{ marker?: string; value: string; role: RoleSpec }> = [];
209
+ for (const role of schema.roles) {
210
+ const value = extractRoleValue(node, role.role);
211
+ if (!value && !role.required) continue;
212
+
213
+ const markerText =
214
+ role.markerOverride?.[language] ?? markers[role.role]?.[language] ?? undefined;
215
+
216
+ roleParts.push({
217
+ ...(markerText != null && { marker: markerText }),
218
+ value: value || '',
219
+ role,
220
+ });
221
+ }
222
+
223
+ const parts: string[] = [];
224
+
225
+ if (isSOV) {
226
+ // SOV: roles (with markers after values) then keyword
227
+ for (const rp of roleParts) {
228
+ if (rp.value) parts.push(rp.value);
229
+ if (rp.marker) parts.push(rp.marker);
230
+ }
231
+ parts.push(keyword);
232
+ } else {
233
+ // SVO/VSO: keyword then roles (with markers before values)
234
+ parts.push(keyword);
235
+ for (const rp of roleParts) {
236
+ if (rp.marker) parts.push(rp.marker);
237
+ if (rp.value) parts.push(rp.value);
238
+ }
239
+ }
240
+
241
+ return buildPhrase(...parts);
242
+ },
243
+ };
244
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Grammar transformation utilities for multilingual DSLs
3
+ *
4
+ * Core utilities for handling word order transformation (SVO/SOV/VSO),
5
+ * grammatical marker insertion, and role-based reordering.
6
+ */
7
+
8
+ export * from './types';
9
+ export * from './transformer';
10
+
11
+ // Re-export key transformation functions
12
+ export { reorderRoles, insertMarkers, joinTokens } from './types';
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Grammar Transformer - Dependency Injection Architecture
3
+ *
4
+ * Transforms statements between languages using injected dependencies.
5
+ * This makes the transformer completely domain-agnostic.
6
+ */
7
+
8
+ import type { LanguageProfile, ParsedElement, SemanticRole } from './types';
9
+ import { reorderRoles, insertMarkers, joinTokens } from './types';
10
+ import type { Dictionary } from '../interfaces/dictionary';
11
+ import type { ProfileProvider } from '../interfaces/profile-provider';
12
+
13
+ /**
14
+ * Configuration for grammar transformer.
15
+ */
16
+ export interface TransformerConfig {
17
+ /** Dictionary for keyword translation */
18
+ readonly dictionary: Dictionary;
19
+
20
+ /** Profile provider for grammar rules */
21
+ readonly profileProvider: ProfileProvider;
22
+ }
23
+
24
+ /**
25
+ * Generic grammar transformer using dependency injection.
26
+ *
27
+ * @example
28
+ * const transformer = new GrammarTransformer({
29
+ * dictionary: new InMemoryDictionary(translations),
30
+ * profileProvider: new InMemoryProfileProvider(profiles)
31
+ * });
32
+ *
33
+ * const japanese = transformer.transform('select name from users', 'en', 'ja');
34
+ * // → "users から name を 選択"
35
+ */
36
+ export class GrammarTransformer {
37
+ private dictionary: Dictionary;
38
+ private profileProvider: ProfileProvider;
39
+
40
+ constructor(config: TransformerConfig) {
41
+ this.dictionary = config.dictionary;
42
+ this.profileProvider = config.profileProvider;
43
+ }
44
+
45
+ /**
46
+ * Transform a statement from one language to another.
47
+ *
48
+ * @param input - Statement in source language
49
+ * @param fromLanguage - Source language code
50
+ * @param toLanguage - Target language code
51
+ * @returns Transformed statement in target language
52
+ *
53
+ * @throws Error if profiles not found or translation fails
54
+ */
55
+ transform(input: string, fromLanguage: string, toLanguage: string): string {
56
+ // Get language profiles
57
+ const fromProfile = this.profileProvider.getProfile(fromLanguage);
58
+ const toProfile = this.profileProvider.getProfile(toLanguage);
59
+
60
+ if (!fromProfile) {
61
+ throw new Error(`No profile found for language: ${fromLanguage}`);
62
+ }
63
+ if (!toProfile) {
64
+ throw new Error(`No profile found for language: ${toLanguage}`);
65
+ }
66
+
67
+ // Parse statement to semantic roles
68
+ const parsed = this.parseStatement(input, fromLanguage, fromProfile);
69
+
70
+ // Translate each word
71
+ const translated = this.translateRoles(parsed, fromLanguage, toLanguage);
72
+
73
+ // Reorder and insert markers
74
+ const reordered = reorderRoles(translated, toProfile.canonicalOrder);
75
+
76
+ const withMarkers = insertMarkers(reordered, toProfile.markers, toProfile.adpositionType);
77
+
78
+ return joinTokens(withMarkers);
79
+ }
80
+
81
+ /**
82
+ * Parse statement into semantic roles.
83
+ * This is a simplified generic parser - DSLs can provide custom parsers.
84
+ */
85
+ private parseStatement(
86
+ input: string,
87
+ language: string,
88
+ profile: LanguageProfile
89
+ ): Map<SemanticRole, string> {
90
+ const roles = new Map<SemanticRole, string>();
91
+ const tokens = input.split(/\s+/);
92
+
93
+ // Simple parsing: extract roles based on markers
94
+ let i = 0;
95
+ while (i < tokens.length) {
96
+ const token = tokens[i];
97
+
98
+ // Check if token is a marker
99
+ const marker = profile.markers.find(m => m.form === token);
100
+
101
+ if (marker) {
102
+ // Next token is the value for this role
103
+ if (i + 1 < tokens.length) {
104
+ roles.set(marker.role, tokens[i + 1]);
105
+ i += 2;
106
+ } else {
107
+ i++;
108
+ }
109
+ } else {
110
+ // Look up token in dictionary to find its role
111
+ const canonical = this.dictionary.lookup(token, language);
112
+
113
+ if (canonical) {
114
+ // Assume it's the action (command keyword)
115
+ roles.set('action', canonical);
116
+ } else {
117
+ // Assume it's a value (selector, identifier, etc.)
118
+ // Try to guess the role based on syntax
119
+ if (token.startsWith('#') || token.startsWith('.')) {
120
+ roles.set('patient', token);
121
+ } else {
122
+ // Default to patient for unknown values
123
+ roles.set('patient', token);
124
+ }
125
+ }
126
+
127
+ i++;
128
+ }
129
+ }
130
+
131
+ return roles;
132
+ }
133
+
134
+ /**
135
+ * Translate roles using injected dictionary.
136
+ * Returns a Map of ParsedElement objects expected by reorderRoles.
137
+ */
138
+ private translateRoles(
139
+ roles: Map<SemanticRole, string>,
140
+ _fromLanguage: string,
141
+ toLanguage: string
142
+ ): Map<SemanticRole, ParsedElement> {
143
+ const result = new Map<SemanticRole, ParsedElement>();
144
+
145
+ for (const [role, value] of roles) {
146
+ // Translate using dictionary
147
+ const translated = this.dictionary.translate(value, toLanguage);
148
+
149
+ // Create ParsedElement with role, value, and translated
150
+ result.set(role, {
151
+ role,
152
+ value, // Original value
153
+ translated: translated ?? value, // Translated value or original if no translation
154
+ });
155
+ }
156
+
157
+ return result;
158
+ }
159
+ }