@lokascript/domain-learn 2.1.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/generators/gloss-generator.d.ts +18 -0
- package/dist/generators/learn-renderer.d.ts +13 -0
- package/dist/generators/sentence-generator.d.ts +34 -0
- package/dist/index.cjs +6116 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +441 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +6056 -0
- package/dist/index.js.map +1 -0
- package/dist/profiles/ar.d.ts +2 -0
- package/dist/profiles/de.d.ts +2 -0
- package/dist/profiles/en.d.ts +2 -0
- package/dist/profiles/es.d.ts +2 -0
- package/dist/profiles/fr.d.ts +2 -0
- package/dist/profiles/index.d.ts +20 -0
- package/dist/profiles/ja.d.ts +2 -0
- package/dist/profiles/ko.d.ts +2 -0
- package/dist/profiles/pt.d.ts +2 -0
- package/dist/profiles/tr.d.ts +2 -0
- package/dist/profiles/zh.d.ts +2 -0
- package/dist/schemas/index.d.ts +31 -0
- package/dist/tokenizers/index.d.ts +23 -0
- package/dist/types.d.ts +266 -0
- package/package.json +63 -0
- package/src/__tests__/schemas.test.ts +145 -0
- package/src/__tests__/sentence-generation.test.ts +189 -0
- package/src/generators/gloss-generator.ts +145 -0
- package/src/generators/learn-renderer.ts +291 -0
- package/src/generators/sentence-generator.ts +501 -0
- package/src/index.ts +237 -0
- package/src/profiles/ar.ts +526 -0
- package/src/profiles/de.ts +481 -0
- package/src/profiles/en.ts +181 -0
- package/src/profiles/es.ts +829 -0
- package/src/profiles/fr.ts +466 -0
- package/src/profiles/index.ts +34 -0
- package/src/profiles/ja.ts +301 -0
- package/src/profiles/ko.ts +286 -0
- package/src/profiles/pt.ts +484 -0
- package/src/profiles/tr.ts +511 -0
- package/src/profiles/zh.ts +256 -0
- package/src/schemas/index.ts +576 -0
- package/src/tokenizers/index.ts +409 -0
- package/src/types.ts +321 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learn Code Generator — The Inverted Generator
|
|
3
|
+
*
|
|
4
|
+
* Unlike domain-sql (natural language → SQL), this generator produces
|
|
5
|
+
* natural language sentences WITH morphology applied as its "compiled output".
|
|
6
|
+
*
|
|
7
|
+
* The standard CodeGenerator.generate() interface returns the commanding form
|
|
8
|
+
* in English. The extended generateForFunction() method produces sentences in
|
|
9
|
+
* any language × communicative function combination.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { SemanticNode, CodeGenerator } from '@lokascript/framework';
|
|
13
|
+
import { extractRoleValue } from '@lokascript/framework';
|
|
14
|
+
import type {
|
|
15
|
+
CommunicativeFunction,
|
|
16
|
+
RenderedSentence,
|
|
17
|
+
CommandProfile,
|
|
18
|
+
LearnLanguageProfile,
|
|
19
|
+
AnyForms,
|
|
20
|
+
CoreVerb,
|
|
21
|
+
SemanticRole,
|
|
22
|
+
} from '../types';
|
|
23
|
+
|
|
24
|
+
// ─── Command Profiles (derived from schemas) ────────────────────
|
|
25
|
+
|
|
26
|
+
const COMMAND_PROFILES: Record<string, CommandProfile> = {
|
|
27
|
+
add: { verb: 'add', valence: 'ditransitive', targetRole: 'destination', hasPatient: true },
|
|
28
|
+
remove: { verb: 'remove', valence: 'ditransitive', targetRole: 'source', hasPatient: true },
|
|
29
|
+
toggle: { verb: 'toggle', valence: 'ditransitive', targetRole: 'destination', hasPatient: true },
|
|
30
|
+
put: { verb: 'put', valence: 'ditransitive', targetRole: 'destination', hasPatient: true },
|
|
31
|
+
set: { verb: 'set', valence: 'ditransitive', targetRole: 'destination', hasPatient: true },
|
|
32
|
+
show: { verb: 'show', valence: 'transitive', targetRole: null, hasPatient: true },
|
|
33
|
+
hide: { verb: 'hide', valence: 'transitive', targetRole: null, hasPatient: true },
|
|
34
|
+
get: { verb: 'get', valence: 'transitive', targetRole: 'source', hasPatient: false },
|
|
35
|
+
wait: { verb: 'wait', valence: 'transitive', targetRole: null, hasPatient: true },
|
|
36
|
+
fetch: { verb: 'fetch', valence: 'transitive', targetRole: 'source', hasPatient: false },
|
|
37
|
+
send: { verb: 'send', valence: 'ditransitive', targetRole: 'destination', hasPatient: true },
|
|
38
|
+
go: { verb: 'go', valence: 'transitive', targetRole: 'destination', hasPatient: false },
|
|
39
|
+
increment: { verb: 'increment', valence: 'transitive', targetRole: null, hasPatient: true },
|
|
40
|
+
decrement: { verb: 'decrement', valence: 'transitive', targetRole: null, hasPatient: true },
|
|
41
|
+
take: { verb: 'take', valence: 'ditransitive', targetRole: 'source', hasPatient: true },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// ─── Marker Resolution ──────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
export interface ResolvedMarker {
|
|
47
|
+
marker: string;
|
|
48
|
+
position: 'before' | 'after';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const EMPTY_MARKER: ResolvedMarker = { marker: '', position: 'before' };
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Collapsed marker lookup — pre-resolved from the 5-tier system.
|
|
55
|
+
* Structure: MARKERS[verb][role][language] = marker string
|
|
56
|
+
*
|
|
57
|
+
* This is the flattened result of:
|
|
58
|
+
* Tier 1: renderOverride
|
|
59
|
+
* Tier 2: markerOverride
|
|
60
|
+
* Tier 3: LEARNING_OVERRIDES
|
|
61
|
+
* Tier 4: LANGUAGE_RENDERING_OVERRIDES
|
|
62
|
+
* Tier 5: profile defaults
|
|
63
|
+
*/
|
|
64
|
+
const MARKERS: Record<string, Record<string, Record<string, string>>> = {
|
|
65
|
+
add: {
|
|
66
|
+
patient: {
|
|
67
|
+
en: '',
|
|
68
|
+
ja: 'を',
|
|
69
|
+
es: '',
|
|
70
|
+
ar: '',
|
|
71
|
+
zh: '',
|
|
72
|
+
ko: '을',
|
|
73
|
+
fr: '',
|
|
74
|
+
tr: 'i',
|
|
75
|
+
de: '',
|
|
76
|
+
pt: '',
|
|
77
|
+
},
|
|
78
|
+
destination: {
|
|
79
|
+
en: 'to',
|
|
80
|
+
ja: 'に',
|
|
81
|
+
es: 'a',
|
|
82
|
+
ar: 'إلى',
|
|
83
|
+
zh: '到',
|
|
84
|
+
ko: '에',
|
|
85
|
+
fr: 'à',
|
|
86
|
+
tr: 'a',
|
|
87
|
+
de: 'zu',
|
|
88
|
+
pt: 'a',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
remove: {
|
|
92
|
+
patient: {
|
|
93
|
+
en: '',
|
|
94
|
+
ja: 'を',
|
|
95
|
+
es: '',
|
|
96
|
+
ar: '',
|
|
97
|
+
zh: '',
|
|
98
|
+
ko: '을',
|
|
99
|
+
fr: '',
|
|
100
|
+
tr: 'i',
|
|
101
|
+
de: '',
|
|
102
|
+
pt: '',
|
|
103
|
+
},
|
|
104
|
+
source: {
|
|
105
|
+
en: 'from',
|
|
106
|
+
ja: 'から',
|
|
107
|
+
es: 'de',
|
|
108
|
+
ar: 'من',
|
|
109
|
+
zh: '从',
|
|
110
|
+
ko: '에서',
|
|
111
|
+
fr: 'de',
|
|
112
|
+
tr: 'dan',
|
|
113
|
+
de: 'von',
|
|
114
|
+
pt: 'de',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
toggle: {
|
|
118
|
+
patient: {
|
|
119
|
+
en: '',
|
|
120
|
+
ja: 'を',
|
|
121
|
+
es: '',
|
|
122
|
+
ar: '',
|
|
123
|
+
zh: '',
|
|
124
|
+
ko: '을',
|
|
125
|
+
fr: '',
|
|
126
|
+
tr: 'i',
|
|
127
|
+
de: '',
|
|
128
|
+
pt: '',
|
|
129
|
+
},
|
|
130
|
+
destination: {
|
|
131
|
+
en: 'on',
|
|
132
|
+
ja: 'に',
|
|
133
|
+
es: 'en',
|
|
134
|
+
ar: 'على',
|
|
135
|
+
zh: '在',
|
|
136
|
+
ko: '에',
|
|
137
|
+
fr: 'sur',
|
|
138
|
+
tr: 'a',
|
|
139
|
+
de: 'auf',
|
|
140
|
+
pt: 'em',
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
put: {
|
|
144
|
+
patient: {
|
|
145
|
+
en: '',
|
|
146
|
+
ja: 'を',
|
|
147
|
+
es: '',
|
|
148
|
+
ar: '',
|
|
149
|
+
zh: '',
|
|
150
|
+
ko: '을',
|
|
151
|
+
fr: '',
|
|
152
|
+
tr: 'i',
|
|
153
|
+
de: '',
|
|
154
|
+
pt: '',
|
|
155
|
+
},
|
|
156
|
+
destination: {
|
|
157
|
+
en: 'into',
|
|
158
|
+
ja: 'に',
|
|
159
|
+
es: 'en',
|
|
160
|
+
ar: 'في',
|
|
161
|
+
zh: '到',
|
|
162
|
+
ko: '에',
|
|
163
|
+
fr: 'dans',
|
|
164
|
+
tr: 'a',
|
|
165
|
+
de: 'in',
|
|
166
|
+
pt: 'em',
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
set: {
|
|
170
|
+
destination: {
|
|
171
|
+
en: '',
|
|
172
|
+
ja: 'を',
|
|
173
|
+
es: 'en',
|
|
174
|
+
ar: '',
|
|
175
|
+
zh: '在',
|
|
176
|
+
ko: '를',
|
|
177
|
+
fr: 'sur',
|
|
178
|
+
tr: 'i',
|
|
179
|
+
de: 'auf',
|
|
180
|
+
pt: 'em',
|
|
181
|
+
},
|
|
182
|
+
patient: {
|
|
183
|
+
en: 'to',
|
|
184
|
+
ja: 'に',
|
|
185
|
+
es: 'a',
|
|
186
|
+
ar: 'إلى',
|
|
187
|
+
zh: '',
|
|
188
|
+
ko: '으로',
|
|
189
|
+
fr: 'à',
|
|
190
|
+
tr: 'e',
|
|
191
|
+
de: 'auf',
|
|
192
|
+
pt: 'para',
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
show: {
|
|
196
|
+
patient: {
|
|
197
|
+
en: '',
|
|
198
|
+
ja: 'を',
|
|
199
|
+
es: '',
|
|
200
|
+
ar: '',
|
|
201
|
+
zh: '',
|
|
202
|
+
ko: '을',
|
|
203
|
+
fr: '',
|
|
204
|
+
tr: 'i',
|
|
205
|
+
de: '',
|
|
206
|
+
pt: '',
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
hide: {
|
|
210
|
+
patient: {
|
|
211
|
+
en: '',
|
|
212
|
+
ja: 'を',
|
|
213
|
+
es: '',
|
|
214
|
+
ar: '',
|
|
215
|
+
zh: '',
|
|
216
|
+
ko: '을',
|
|
217
|
+
fr: '',
|
|
218
|
+
tr: 'i',
|
|
219
|
+
de: '',
|
|
220
|
+
pt: '',
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
get: {
|
|
224
|
+
source: {
|
|
225
|
+
en: '',
|
|
226
|
+
ja: 'を',
|
|
227
|
+
es: '',
|
|
228
|
+
ar: 'على',
|
|
229
|
+
zh: '',
|
|
230
|
+
ko: '를',
|
|
231
|
+
fr: '',
|
|
232
|
+
tr: 'i',
|
|
233
|
+
de: '',
|
|
234
|
+
pt: '',
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
wait: {
|
|
238
|
+
patient: {
|
|
239
|
+
en: '',
|
|
240
|
+
ja: 'を',
|
|
241
|
+
es: '',
|
|
242
|
+
ar: '',
|
|
243
|
+
zh: '',
|
|
244
|
+
ko: '을',
|
|
245
|
+
fr: '',
|
|
246
|
+
tr: 'i',
|
|
247
|
+
de: '',
|
|
248
|
+
pt: '',
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
fetch: {
|
|
252
|
+
source: {
|
|
253
|
+
en: '',
|
|
254
|
+
ja: 'から',
|
|
255
|
+
es: 'de',
|
|
256
|
+
ar: 'من',
|
|
257
|
+
zh: '从',
|
|
258
|
+
ko: '에서',
|
|
259
|
+
fr: 'de',
|
|
260
|
+
tr: 'dan',
|
|
261
|
+
de: 'von',
|
|
262
|
+
pt: 'de',
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
send: {
|
|
266
|
+
patient: {
|
|
267
|
+
en: '',
|
|
268
|
+
ja: 'を',
|
|
269
|
+
es: '',
|
|
270
|
+
ar: '',
|
|
271
|
+
zh: '',
|
|
272
|
+
ko: '을',
|
|
273
|
+
fr: '',
|
|
274
|
+
tr: 'i',
|
|
275
|
+
de: '',
|
|
276
|
+
pt: '',
|
|
277
|
+
},
|
|
278
|
+
destination: {
|
|
279
|
+
en: 'to',
|
|
280
|
+
ja: 'に',
|
|
281
|
+
es: 'a',
|
|
282
|
+
ar: 'إلى',
|
|
283
|
+
zh: '到',
|
|
284
|
+
ko: '에게',
|
|
285
|
+
fr: 'à',
|
|
286
|
+
tr: '-e',
|
|
287
|
+
de: 'an',
|
|
288
|
+
pt: 'para',
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
go: {
|
|
292
|
+
destination: {
|
|
293
|
+
en: '',
|
|
294
|
+
ja: 'に',
|
|
295
|
+
es: 'a',
|
|
296
|
+
ar: 'إلى',
|
|
297
|
+
zh: '',
|
|
298
|
+
ko: '에',
|
|
299
|
+
fr: 'à',
|
|
300
|
+
tr: 'a',
|
|
301
|
+
de: 'zu',
|
|
302
|
+
pt: 'para',
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
increment: {
|
|
306
|
+
patient: {
|
|
307
|
+
en: '',
|
|
308
|
+
ja: 'を',
|
|
309
|
+
es: '',
|
|
310
|
+
ar: '',
|
|
311
|
+
zh: '',
|
|
312
|
+
ko: '을',
|
|
313
|
+
fr: '',
|
|
314
|
+
tr: 'i',
|
|
315
|
+
de: '',
|
|
316
|
+
pt: '',
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
decrement: {
|
|
320
|
+
patient: {
|
|
321
|
+
en: '',
|
|
322
|
+
ja: 'を',
|
|
323
|
+
es: '',
|
|
324
|
+
ar: '',
|
|
325
|
+
zh: '',
|
|
326
|
+
ko: '을',
|
|
327
|
+
fr: '',
|
|
328
|
+
tr: 'i',
|
|
329
|
+
de: '',
|
|
330
|
+
pt: '',
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
take: {
|
|
334
|
+
patient: {
|
|
335
|
+
en: '',
|
|
336
|
+
ja: 'を',
|
|
337
|
+
es: '',
|
|
338
|
+
ar: '',
|
|
339
|
+
zh: '',
|
|
340
|
+
ko: '을',
|
|
341
|
+
fr: '',
|
|
342
|
+
tr: 'i',
|
|
343
|
+
de: '',
|
|
344
|
+
pt: '',
|
|
345
|
+
},
|
|
346
|
+
source: {
|
|
347
|
+
en: 'from',
|
|
348
|
+
ja: 'から',
|
|
349
|
+
es: 'de',
|
|
350
|
+
ar: 'من',
|
|
351
|
+
zh: '从',
|
|
352
|
+
ko: '에서',
|
|
353
|
+
fr: 'de',
|
|
354
|
+
tr: 'dan',
|
|
355
|
+
de: 'von',
|
|
356
|
+
pt: 'de',
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
/** Languages where particles follow nouns (no space) — postpositional */
|
|
362
|
+
const POSTPOSITIONAL = new Set(['ja', 'ko', 'tr']);
|
|
363
|
+
|
|
364
|
+
export function resolveMarker(verb: string, role: string, language: string): ResolvedMarker {
|
|
365
|
+
const marker = MARKERS[verb]?.[role]?.[language];
|
|
366
|
+
if (marker === undefined) return EMPTY_MARKER;
|
|
367
|
+
const position = POSTPOSITIONAL.has(language) ? 'after' : 'before';
|
|
368
|
+
return { marker, position };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function attachMarker(value: string, resolved: ResolvedMarker): string {
|
|
372
|
+
if (!resolved.marker) return value;
|
|
373
|
+
if (resolved.position === 'before') return `${resolved.marker} ${value}`;
|
|
374
|
+
return `${value}${resolved.marker}`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ─── Form Resolution ────────────────────────────────────────────
|
|
378
|
+
|
|
379
|
+
function resolveFormPath(forms: AnyForms, path: string): string {
|
|
380
|
+
const parts = path.split('.');
|
|
381
|
+
let current: unknown = forms;
|
|
382
|
+
for (const part of parts) {
|
|
383
|
+
if (current == null || typeof current !== 'object') return `[${path}?]`;
|
|
384
|
+
current = (current as Record<string, unknown>)[part];
|
|
385
|
+
}
|
|
386
|
+
return typeof current === 'string' ? current : `[${path}?]`;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ─── Sentence Generator ─────────────────────────────────────────
|
|
390
|
+
|
|
391
|
+
/** Registry of language profiles — populated by registerProfile() */
|
|
392
|
+
const profileRegistry: Record<string, LearnLanguageProfile> = {};
|
|
393
|
+
|
|
394
|
+
export function registerProfile(code: string, profile: LearnLanguageProfile): void {
|
|
395
|
+
profileRegistry[code] = profile;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function getProfile(code: string): LearnLanguageProfile | undefined {
|
|
399
|
+
return profileRegistry[code];
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Generate a sentence for a SemanticNode in a specific language and
|
|
404
|
+
* communicative function.
|
|
405
|
+
*/
|
|
406
|
+
export function generateForFunction(
|
|
407
|
+
node: SemanticNode,
|
|
408
|
+
fn: CommunicativeFunction,
|
|
409
|
+
language: string
|
|
410
|
+
): RenderedSentence | null {
|
|
411
|
+
const profile = profileRegistry[language];
|
|
412
|
+
if (!profile) return null;
|
|
413
|
+
|
|
414
|
+
const verb = node.action as CoreVerb;
|
|
415
|
+
const forms = profile.morphologyTable[verb];
|
|
416
|
+
if (!forms) return null;
|
|
417
|
+
|
|
418
|
+
const frame = profile.frames.frames.find(f => f.function === fn);
|
|
419
|
+
if (!frame) return null;
|
|
420
|
+
|
|
421
|
+
const verbValue = resolveFormPath(forms, frame.verbForm);
|
|
422
|
+
const subject = profile.defaultSubject;
|
|
423
|
+
const cmdProfile = COMMAND_PROFILES[verb];
|
|
424
|
+
|
|
425
|
+
// Extract patient/target from SemanticNode roles
|
|
426
|
+
const patientValue = extractRoleValue(node, 'patient') || '';
|
|
427
|
+
const targetRole = cmdProfile?.targetRole;
|
|
428
|
+
|
|
429
|
+
// For verbs like get/fetch/go, the primary value is in source/destination
|
|
430
|
+
let targetValue = '';
|
|
431
|
+
if (targetRole) {
|
|
432
|
+
targetValue = extractRoleValue(node, targetRole) || '';
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Build patient string with marker
|
|
436
|
+
let patientStr = '';
|
|
437
|
+
if (patientValue && cmdProfile?.hasPatient) {
|
|
438
|
+
const patientMarker = resolveMarker(verb, 'patient', language);
|
|
439
|
+
patientStr = attachMarker(patientValue, patientMarker);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Build target string with marker
|
|
443
|
+
let targetStr = '';
|
|
444
|
+
if (targetValue && targetRole) {
|
|
445
|
+
const targetMarker = resolveMarker(verb, targetRole, language);
|
|
446
|
+
targetStr = attachMarker(targetValue, targetMarker);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
let sentence = frame.template
|
|
450
|
+
.replace(/\{subject\}/g, subject)
|
|
451
|
+
.replace(/\{patient\}/g, patientStr)
|
|
452
|
+
.replace(/\{target\}/g, targetStr);
|
|
453
|
+
|
|
454
|
+
// Replace verb form references: {verb.base}, {verb.present.el}, etc.
|
|
455
|
+
sentence = sentence.replace(/\{verb\.([^}]+)\}/g, (_match, path: string) => {
|
|
456
|
+
return resolveFormPath(forms, path);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// Clean up extra spaces
|
|
460
|
+
sentence = sentence.replace(/\s+/g, ' ').trim();
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
language,
|
|
464
|
+
function: fn,
|
|
465
|
+
sentence,
|
|
466
|
+
verbForm: frame.verbForm,
|
|
467
|
+
verbValue,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Standard CodeGenerator interface — returns the commanding form in English.
|
|
473
|
+
* This satisfies the framework's CodeGenerator contract.
|
|
474
|
+
*/
|
|
475
|
+
export const learnCodeGenerator: CodeGenerator = {
|
|
476
|
+
generate(node: SemanticNode): string {
|
|
477
|
+
const result = generateForFunction(node, 'commanding', 'en');
|
|
478
|
+
return result?.sentence ?? `[${node.action}]`;
|
|
479
|
+
},
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
// ─── Convenience Functions ──────────────────────────────────────
|
|
483
|
+
|
|
484
|
+
/** Render across all communicative functions for a language */
|
|
485
|
+
export function generateAllFunctions(node: SemanticNode, language: string): RenderedSentence[] {
|
|
486
|
+
const profile = profileRegistry[language];
|
|
487
|
+
if (!profile) return [];
|
|
488
|
+
return profile.frames.frames
|
|
489
|
+
.map(f => generateForFunction(node, f.function, language))
|
|
490
|
+
.filter((r): r is RenderedSentence => r !== null);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/** Render across all registered languages for a single function */
|
|
494
|
+
export function generateCrossLingual(
|
|
495
|
+
node: SemanticNode,
|
|
496
|
+
fn: CommunicativeFunction
|
|
497
|
+
): RenderedSentence[] {
|
|
498
|
+
return Object.keys(profileRegistry)
|
|
499
|
+
.map(lang => generateForFunction(node, fn, lang))
|
|
500
|
+
.filter((r): r is RenderedSentence => r !== null);
|
|
501
|
+
}
|