@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.
- package/LICENSE +20 -0
- package/README.md +142 -0
- package/dist/aot/aot-orchestrator.d.ts +75 -0
- package/dist/aot/aot-orchestrator.d.ts.map +1 -0
- package/dist/aot/domain-scanner.d.ts +27 -0
- package/dist/aot/domain-scanner.d.ts.map +1 -0
- package/dist/aot/index.d.ts +8 -0
- package/dist/aot/index.d.ts.map +1 -0
- package/dist/aot/types.d.ts +103 -0
- package/dist/aot/types.d.ts.map +1 -0
- package/dist/api/create-dsl.d.ts +91 -0
- package/dist/api/create-dsl.d.ts.map +1 -0
- package/dist/api/dispatcher.d.ts +108 -0
- package/dist/api/dispatcher.d.ts.map +1 -0
- package/dist/api/domain-registry.d.ts +152 -0
- package/dist/api/domain-registry.d.ts.map +1 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +2082 -0
- package/dist/api/index.js.map +1 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +2674 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/logger.d.ts +32 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/pattern-matching/index.d.ts +6 -0
- package/dist/core/pattern-matching/index.d.ts.map +1 -0
- package/dist/core/pattern-matching/index.js +1239 -0
- package/dist/core/pattern-matching/index.js.map +1 -0
- package/dist/core/pattern-matching/pattern-matcher.d.ts +239 -0
- package/dist/core/pattern-matching/pattern-matcher.d.ts.map +1 -0
- package/dist/core/pattern-matching/utils/index.d.ts +6 -0
- package/dist/core/pattern-matching/utils/index.d.ts.map +1 -0
- package/dist/core/pattern-matching/utils/possessive-keywords.d.ts +38 -0
- package/dist/core/pattern-matching/utils/possessive-keywords.d.ts.map +1 -0
- package/dist/core/pattern-matching/utils/type-validation.d.ts +63 -0
- package/dist/core/pattern-matching/utils/type-validation.d.ts.map +1 -0
- package/dist/core/tokenization/base-tokenizer.d.ts +344 -0
- package/dist/core/tokenization/base-tokenizer.d.ts.map +1 -0
- package/dist/core/tokenization/char-classifiers.d.ts +56 -0
- package/dist/core/tokenization/char-classifiers.d.ts.map +1 -0
- package/dist/core/tokenization/default-extractors.d.ts +48 -0
- package/dist/core/tokenization/default-extractors.d.ts.map +1 -0
- package/dist/core/tokenization/extractors/index.d.ts +9 -0
- package/dist/core/tokenization/extractors/index.d.ts.map +1 -0
- package/dist/core/tokenization/extractors/operator.d.ts +23 -0
- package/dist/core/tokenization/extractors/operator.d.ts.map +1 -0
- package/dist/core/tokenization/extractors/punctuation.d.ts +22 -0
- package/dist/core/tokenization/extractors/punctuation.d.ts.map +1 -0
- package/dist/core/tokenization/extractors.d.ts +61 -0
- package/dist/core/tokenization/extractors.d.ts.map +1 -0
- package/dist/core/tokenization/index.d.ts +11 -0
- package/dist/core/tokenization/index.d.ts.map +1 -0
- package/dist/core/tokenization/index.js +1345 -0
- package/dist/core/tokenization/index.js.map +1 -0
- package/dist/core/tokenization/morphology/index.d.ts +5 -0
- package/dist/core/tokenization/morphology/index.d.ts.map +1 -0
- package/dist/core/tokenization/morphology/types.d.ts +110 -0
- package/dist/core/tokenization/morphology/types.d.ts.map +1 -0
- package/dist/core/tokenization/token-utils.d.ts +111 -0
- package/dist/core/tokenization/token-utils.d.ts.map +1 -0
- package/dist/core/types.d.ts +382 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +108 -0
- package/dist/core/types.js.map +1 -0
- package/dist/generation/diagnostics.d.ts +120 -0
- package/dist/generation/diagnostics.d.ts.map +1 -0
- package/dist/generation/index.d.ts +7 -0
- package/dist/generation/index.d.ts.map +1 -0
- package/dist/generation/index.js +339 -0
- package/dist/generation/index.js.map +1 -0
- package/dist/generation/pattern-generator.d.ts +48 -0
- package/dist/generation/pattern-generator.d.ts.map +1 -0
- package/dist/generation/renderer.d.ts +115 -0
- package/dist/generation/renderer.d.ts.map +1 -0
- package/dist/grammar/index.d.ts +10 -0
- package/dist/grammar/index.d.ts.map +1 -0
- package/dist/grammar/index.js +391 -0
- package/dist/grammar/index.js.map +1 -0
- package/dist/grammar/transformer.d.ts +56 -0
- package/dist/grammar/transformer.d.ts.map +1 -0
- package/dist/grammar/types.d.ts +236 -0
- package/dist/grammar/types.d.ts.map +1 -0
- package/dist/index.cjs +4454 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4336 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/dictionary.d.ts +82 -0
- package/dist/interfaces/dictionary.d.ts.map +1 -0
- package/dist/interfaces/index.d.ts +10 -0
- package/dist/interfaces/index.d.ts.map +1 -0
- package/dist/interfaces/profile-provider.d.ts +67 -0
- package/dist/interfaces/profile-provider.d.ts.map +1 -0
- package/dist/interfaces/value-extractor.d.ts +168 -0
- package/dist/interfaces/value-extractor.d.ts.map +1 -0
- package/dist/multilingual/index.d.ts +8 -0
- package/dist/multilingual/index.d.ts.map +1 -0
- package/dist/multilingual/index.js +1 -0
- package/dist/multilingual/index.js.map +1 -0
- package/dist/parsing/index.d.ts +8 -0
- package/dist/parsing/index.d.ts.map +1 -0
- package/dist/parsing/index.js +1415 -0
- package/dist/parsing/index.js.map +1 -0
- package/dist/parsing/multi-statement.d.ts +265 -0
- package/dist/parsing/multi-statement.d.ts.map +1 -0
- package/dist/schema/command-schema.d.ts +78 -0
- package/dist/schema/command-schema.d.ts.map +1 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +25 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/test-setup.d.ts +9 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/testing/index.d.ts +50 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +16969 -0
- package/dist/testing/index.js.map +1 -0
- package/package.json +122 -0
- package/src/__test__/fixtures/sql-dsl.ts +232 -0
- package/src/__test__/sql-integration.test.ts +189 -0
- package/src/__test__/test-utils.ts +260 -0
- package/src/aot/aot-orchestrator.test.ts +413 -0
- package/src/aot/aot-orchestrator.ts +238 -0
- package/src/aot/domain-scanner.ts +178 -0
- package/src/aot/index.ts +8 -0
- package/src/aot/types.ts +124 -0
- package/src/api/create-dsl.ts +367 -0
- package/src/api/dispatcher.test.ts +336 -0
- package/src/api/dispatcher.ts +222 -0
- package/src/api/domain-registry.test.ts +336 -0
- package/src/api/domain-registry.ts +500 -0
- package/src/api/index.ts +7 -0
- package/src/core/index.ts +7 -0
- package/src/core/logger.ts +130 -0
- package/src/core/pattern-matching/index.ts +6 -0
- package/src/core/pattern-matching/pattern-matcher.test.ts +900 -0
- package/src/core/pattern-matching/pattern-matcher.ts +1548 -0
- package/src/core/pattern-matching/pattern-matcher.ts.backup +1267 -0
- package/src/core/pattern-matching/utils/index.ts +6 -0
- package/src/core/pattern-matching/utils/possessive-keywords.ts +55 -0
- package/src/core/pattern-matching/utils/type-validation.test.ts +316 -0
- package/src/core/pattern-matching/utils/type-validation.ts +134 -0
- package/src/core/tokenization/base-tokenizer.ts +916 -0
- package/src/core/tokenization/char-classifiers.ts +79 -0
- package/src/core/tokenization/create-simple-tokenizer.test.ts +260 -0
- package/src/core/tokenization/default-extractors.ts +69 -0
- package/src/core/tokenization/extractors/index.ts +9 -0
- package/src/core/tokenization/extractors/operator.ts +75 -0
- package/src/core/tokenization/extractors/punctuation.ts +39 -0
- package/src/core/tokenization/extractors.ts +452 -0
- package/src/core/tokenization/index.ts +11 -0
- package/src/core/tokenization/morphology/index.ts +5 -0
- package/src/core/tokenization/morphology/types.ts +211 -0
- package/src/core/tokenization/token-utils.ts +252 -0
- package/src/core/types.ts +589 -0
- package/src/generation/diagnostics.test.ts +171 -0
- package/src/generation/diagnostics.ts +239 -0
- package/src/generation/index.ts +7 -0
- package/src/generation/pattern-generator.test.ts +430 -0
- package/src/generation/pattern-generator.ts +315 -0
- package/src/generation/renderer.test.ts +266 -0
- package/src/generation/renderer.ts +244 -0
- package/src/grammar/index.ts +12 -0
- package/src/grammar/transformer.ts +159 -0
- package/src/grammar/types.ts +630 -0
- package/src/index.ts +157 -0
- package/src/interfaces/dictionary.ts +123 -0
- package/src/interfaces/index.ts +10 -0
- package/src/interfaces/profile-provider.ts +88 -0
- package/src/interfaces/value-extractor.ts +435 -0
- package/src/multilingual/index.ts +9 -0
- package/src/parsing/index.ts +27 -0
- package/src/parsing/multi-statement.test.ts +480 -0
- package/src/parsing/multi-statement.ts +648 -0
- package/src/schema/command-schema.ts +118 -0
- package/src/schema/index.ts +5 -0
- package/src/test-setup.ts +45 -0
- 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
|
+
}
|