@lokascript/semantic 1.0.0 → 1.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.
Files changed (162) hide show
  1. package/dist/browser-ar.ar.global.js +2 -2
  2. package/dist/browser-core.core.global.js +2 -2
  3. package/dist/browser-de.de.global.js +2 -2
  4. package/dist/browser-east-asian.east-asian.global.js +2 -2
  5. package/dist/browser-en-tr.en-tr.global.js +2 -2
  6. package/dist/browser-en.en.global.js +2 -2
  7. package/dist/browser-es-en.es-en.global.js +2 -2
  8. package/dist/browser-es.es.global.js +2 -2
  9. package/dist/browser-fr.fr.global.js +2 -2
  10. package/dist/browser-id.id.global.js +2 -2
  11. package/dist/browser-ja.ja.global.js +2 -2
  12. package/dist/browser-ko.ko.global.js +2 -2
  13. package/dist/browser-lazy.lazy.global.js +2 -2
  14. package/dist/browser-priority.priority.global.js +2 -2
  15. package/dist/browser-pt.pt.global.js +2 -2
  16. package/dist/browser-qu.qu.global.js +2 -2
  17. package/dist/browser-sw.sw.global.js +2 -2
  18. package/dist/browser-tr.tr.global.js +2 -2
  19. package/dist/browser-western.western.global.js +2 -2
  20. package/dist/browser-zh.zh.global.js +2 -2
  21. package/dist/browser.global.js +2 -2
  22. package/dist/browser.global.js.map +1 -1
  23. package/dist/index.cjs +13042 -17462
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +49 -5
  26. package/dist/index.d.ts +49 -5
  27. package/dist/index.js +14044 -18464
  28. package/dist/index.js.map +1 -1
  29. package/dist/languages/ar.d.ts +1 -1
  30. package/dist/languages/ar.js +31 -44
  31. package/dist/languages/ar.js.map +1 -1
  32. package/dist/languages/de.d.ts +1 -1
  33. package/dist/languages/de.js +14 -2
  34. package/dist/languages/de.js.map +1 -1
  35. package/dist/languages/en.d.ts +1 -1
  36. package/dist/languages/en.js +558 -12
  37. package/dist/languages/en.js.map +1 -1
  38. package/dist/languages/es.d.ts +1 -1
  39. package/dist/languages/es.js +16 -0
  40. package/dist/languages/es.js.map +1 -1
  41. package/dist/languages/fr.d.ts +1 -1
  42. package/dist/languages/fr.js +14 -2
  43. package/dist/languages/fr.js.map +1 -1
  44. package/dist/languages/id.d.ts +1 -1
  45. package/dist/languages/id.js +14 -2
  46. package/dist/languages/id.js.map +1 -1
  47. package/dist/languages/ja.d.ts +1 -1
  48. package/dist/languages/ja.js +18 -3
  49. package/dist/languages/ja.js.map +1 -1
  50. package/dist/languages/ko.d.ts +8 -1
  51. package/dist/languages/ko.js +75 -43
  52. package/dist/languages/ko.js.map +1 -1
  53. package/dist/languages/pt.d.ts +1 -1
  54. package/dist/languages/pt.js +17 -0
  55. package/dist/languages/pt.js.map +1 -1
  56. package/dist/languages/qu.d.ts +12 -1
  57. package/dist/languages/qu.js +77 -2
  58. package/dist/languages/qu.js.map +1 -1
  59. package/dist/languages/sw.d.ts +1 -1
  60. package/dist/languages/sw.js.map +1 -1
  61. package/dist/languages/tr.d.ts +9 -1
  62. package/dist/languages/tr.js +96 -72
  63. package/dist/languages/tr.js.map +1 -1
  64. package/dist/languages/zh.d.ts +1 -1
  65. package/dist/languages/zh.js +16 -0
  66. package/dist/languages/zh.js.map +1 -1
  67. package/dist/{types-C4dcj53L.d.ts → types-BY3Id07j.d.ts} +20 -5
  68. package/package.json +20 -29
  69. package/src/generators/command-schemas.ts +21 -10
  70. package/src/generators/event-handler-generator.ts +50 -44
  71. package/src/generators/language-profiles.ts +6 -0
  72. package/src/generators/pattern-generator.ts +883 -1
  73. package/src/generators/profiles/arabic.ts +19 -3
  74. package/src/generators/profiles/bengali.ts +12 -1
  75. package/src/generators/profiles/chinese.ts +15 -0
  76. package/src/generators/profiles/french.ts +12 -1
  77. package/src/generators/profiles/german.ts +12 -1
  78. package/src/generators/profiles/hebrew.ts +148 -0
  79. package/src/generators/profiles/hindi.ts +12 -1
  80. package/src/generators/profiles/index.ts +2 -0
  81. package/src/generators/profiles/indonesian.ts +12 -1
  82. package/src/generators/profiles/italian.ts +16 -0
  83. package/src/generators/profiles/japanese.ts +11 -2
  84. package/src/generators/profiles/korean.ts +15 -1
  85. package/src/generators/profiles/polish.ts +12 -0
  86. package/src/generators/profiles/portuguese.ts +16 -0
  87. package/src/generators/profiles/russian.ts +11 -0
  88. package/src/generators/profiles/spanish.ts +15 -0
  89. package/src/generators/profiles/spanishMexico.ts +176 -0
  90. package/src/generators/profiles/thai.ts +11 -0
  91. package/src/generators/profiles/turkish.ts +49 -7
  92. package/src/generators/profiles/types.ts +21 -5
  93. package/src/generators/profiles/ukrainian.ts +11 -0
  94. package/src/generators/profiles/vietnamese.ts +11 -0
  95. package/src/language-building-schema.ts +111 -0
  96. package/src/languages/_all.ts +5 -1
  97. package/src/languages/es-MX.ts +32 -0
  98. package/src/languages/he.ts +15 -0
  99. package/src/parser/pattern-matcher.ts +10 -1
  100. package/src/parser/semantic-parser.ts +3 -0
  101. package/src/patterns/add/ar.ts +3 -59
  102. package/src/patterns/add/index.ts +5 -1
  103. package/src/patterns/add/ja.ts +3 -81
  104. package/src/patterns/add/ko.ts +3 -62
  105. package/src/patterns/add/qu.ts +69 -0
  106. package/src/patterns/add/tr.ts +3 -59
  107. package/src/patterns/builders.ts +1 -0
  108. package/src/patterns/decrement/tr.ts +3 -36
  109. package/src/patterns/event-handler/ar.ts +3 -139
  110. package/src/patterns/event-handler/he.ts +15 -0
  111. package/src/patterns/event-handler/index.ts +5 -1
  112. package/src/patterns/event-handler/ja.ts +3 -106
  113. package/src/patterns/event-handler/ko.ts +3 -121
  114. package/src/patterns/event-handler/ms.ts +45 -20
  115. package/src/patterns/event-handler/tr.ts +3 -158
  116. package/src/patterns/get/ar.ts +3 -37
  117. package/src/patterns/get/ja.ts +3 -41
  118. package/src/patterns/get/ko.ts +3 -41
  119. package/src/patterns/grammar-transformed/ja.ts +3 -1701
  120. package/src/patterns/grammar-transformed/ko.ts +3 -1299
  121. package/src/patterns/grammar-transformed/tr.ts +3 -1055
  122. package/src/patterns/hide/ar.ts +3 -55
  123. package/src/patterns/hide/ja.ts +3 -57
  124. package/src/patterns/hide/ko.ts +3 -57
  125. package/src/patterns/hide/tr.ts +3 -53
  126. package/src/patterns/increment/tr.ts +3 -40
  127. package/src/patterns/put/ar.ts +3 -62
  128. package/src/patterns/put/ja.ts +3 -63
  129. package/src/patterns/put/ko.ts +3 -55
  130. package/src/patterns/put/tr.ts +3 -55
  131. package/src/patterns/remove/ar.ts +3 -59
  132. package/src/patterns/remove/index.ts +5 -1
  133. package/src/patterns/remove/ja.ts +3 -62
  134. package/src/patterns/remove/ko.ts +3 -66
  135. package/src/patterns/remove/qu.ts +69 -0
  136. package/src/patterns/remove/tr.ts +3 -66
  137. package/src/patterns/set/ar.ts +3 -72
  138. package/src/patterns/set/ja.ts +3 -74
  139. package/src/patterns/set/ko.ts +3 -73
  140. package/src/patterns/set/tr.ts +3 -95
  141. package/src/patterns/show/ar.ts +3 -55
  142. package/src/patterns/show/ja.ts +3 -57
  143. package/src/patterns/show/ko.ts +3 -61
  144. package/src/patterns/show/tr.ts +3 -53
  145. package/src/patterns/take/ar.ts +3 -39
  146. package/src/patterns/toggle/ar.ts +3 -49
  147. package/src/patterns/toggle/index.ts +5 -1
  148. package/src/patterns/toggle/ja.ts +3 -144
  149. package/src/patterns/toggle/ko.ts +3 -101
  150. package/src/patterns/toggle/qu.ts +90 -0
  151. package/src/patterns/toggle/tr.ts +3 -76
  152. package/src/registry.ts +179 -15
  153. package/src/tokenizers/arabic.ts +13 -46
  154. package/src/tokenizers/bengali.ts +2 -16
  155. package/src/tokenizers/he.ts +542 -0
  156. package/src/tokenizers/index.ts +1 -0
  157. package/src/tokenizers/japanese.ts +3 -1
  158. package/src/tokenizers/korean.ts +104 -48
  159. package/src/tokenizers/ms.ts +3 -0
  160. package/src/tokenizers/quechua.ts +101 -2
  161. package/src/tokenizers/turkish.ts +64 -69
  162. package/src/types.ts +13 -0
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import type { LanguagePattern, PatternToken, ExtractionRule } from '../types';
10
- import type { LanguageProfile } from './language-profiles';
10
+ import type { LanguageProfile, KeywordTranslation, RoleMarker } from './language-profiles';
11
11
  import type { CommandSchema, RoleSpec } from './command-schemas';
12
12
  import { getDefinedSchemas } from './command-schemas';
13
13
 
@@ -167,8 +167,16 @@ export function generatePatternsForLanguage(
167
167
  continue;
168
168
  }
169
169
 
170
+ // Generate simple command patterns
170
171
  const variants = generatePatternVariants(schema, profile, config);
171
172
  patterns.push(...variants);
173
+
174
+ // Generate event handler patterns (on [event] [command] [patient])
175
+ // Only generate for languages with eventHandler configuration
176
+ if (profile.eventHandler?.eventMarker) {
177
+ const eventHandlerPatterns = generateEventHandlerPatterns(schema, profile, config);
178
+ patterns.push(...eventHandlerPatterns);
179
+ }
172
180
  }
173
181
 
174
182
  return patterns;
@@ -227,6 +235,880 @@ export function generateAllPatterns(
227
235
  return patterns;
228
236
  }
229
237
 
238
+ /**
239
+ * Generate event handler patterns for a command in a specific language.
240
+ *
241
+ * Creates patterns that wrap commands with event handlers (e.g., "on click toggle .active").
242
+ * Automatically handles SOV, SVO, and VSO word orders based on language profile.
243
+ *
244
+ * @param commandSchema - The command to wrap (toggle, add, remove, etc.)
245
+ * @param profile - Language profile with eventHandler configuration
246
+ * @param config - Generator configuration
247
+ * @returns Array of event handler patterns (empty if profile lacks eventHandler config)
248
+ */
249
+ export function generateEventHandlerPatterns(
250
+ commandSchema: CommandSchema,
251
+ profile: LanguageProfile,
252
+ config: GeneratorConfig = defaultConfig
253
+ ): LanguagePattern[] {
254
+ // Only generate if profile has eventHandler configuration
255
+ if (!profile.eventHandler || !profile.eventHandler.eventMarker) {
256
+ return [];
257
+ }
258
+
259
+ const patterns: LanguagePattern[] = [];
260
+ const eventMarker = profile.eventHandler.eventMarker;
261
+ const keyword = profile.keywords[commandSchema.action];
262
+
263
+ if (!keyword) {
264
+ return []; // No translation for this command
265
+ }
266
+
267
+ // Check if this is a two-role command (like put, set)
268
+ const requiredRoles = commandSchema.roles.filter(r => r.required);
269
+ const hasTwoRequiredRoles = requiredRoles.length === 2;
270
+
271
+ // Generate pattern based on word order
272
+ if (profile.wordOrder === 'SOV') {
273
+ if (hasTwoRequiredRoles) {
274
+ // Two-role SOV pattern for put/set commands
275
+ // Japanese put: 入力 で "test" を #output に 入れる
276
+ // Korean set: 변경 할 때 x 를 10 으로 설정
277
+ patterns.push(
278
+ generateSOVTwoRoleEventHandlerPattern(commandSchema, profile, keyword, eventMarker, config)
279
+ );
280
+ } else {
281
+ // SOV: [event] [eventMarker] [destination? destMarker?] [patient] [patientMarker] [verb]
282
+ // Japanese: クリック で #button の .active を 切り替え
283
+ // Korean: 클릭 할 때 #button 의 .active 를 토글
284
+ // Turkish: tıklama da #button ın .active i değiştir
285
+ patterns.push(
286
+ generateSOVEventHandlerPattern(commandSchema, profile, keyword, eventMarker, config)
287
+ );
288
+
289
+ // For multi-word event markers with no-space alternatives (Korean compact forms),
290
+ // also generate a pattern that accepts the compact form
291
+ // Example: 클릭할때 .active를토글 (할때 as single token)
292
+ const markerWords = eventMarker.primary.split(/\s+/);
293
+ const hasNoSpaceAlternative = eventMarker.alternatives?.some(
294
+ alt => !alt.includes(' ') && alt.length > 1
295
+ );
296
+ if (markerWords.length > 1 && hasNoSpaceAlternative) {
297
+ patterns.push(
298
+ generateSOVCompactEventHandlerPattern(
299
+ commandSchema,
300
+ profile,
301
+ keyword,
302
+ eventMarker,
303
+ config
304
+ )
305
+ );
306
+ }
307
+
308
+ // Add simple pattern (no patient required, defaults to 'me')
309
+ // Supports: クリック で 増加 (click on increment)
310
+ patterns.push(
311
+ generateSOVSimpleEventHandlerPattern(commandSchema, profile, keyword, eventMarker, config)
312
+ );
313
+
314
+ // Add temporal pattern if temporalMarkers defined
315
+ // Supports: クリック の 時 .active を 切り替え (click's time toggle .active)
316
+ const temporalPattern = generateSOVTemporalEventHandlerPattern(
317
+ commandSchema,
318
+ profile,
319
+ keyword,
320
+ config
321
+ );
322
+ if (temporalPattern) {
323
+ patterns.push(temporalPattern);
324
+ }
325
+ }
326
+ } else if (profile.wordOrder === 'VSO') {
327
+ if (hasTwoRequiredRoles) {
328
+ // Two-role VSO pattern for put/set commands
329
+ // Arabic put: عند الإدخال ضع "test" في #output
330
+ patterns.push(
331
+ generateVSOTwoRoleEventHandlerPattern(commandSchema, profile, keyword, eventMarker, config)
332
+ );
333
+ } else {
334
+ // VSO: [eventMarker] [event] [verb] [patient] [على destination?]
335
+ // Arabic: عند النقر بدّل .active على #button
336
+ patterns.push(
337
+ generateVSOEventHandlerPattern(commandSchema, profile, keyword, eventMarker, config)
338
+ );
339
+
340
+ // Add negated event pattern variant for languages with negation markers
341
+ // Pattern: [eventMarker] [negation] [event] [verb] [patient]
342
+ // Example: عند عدم التركيز أخف #tooltip = "on blur hide #tooltip"
343
+ if (profile.eventHandler?.negationMarker) {
344
+ patterns.push(
345
+ generateVSONegatedEventHandlerPattern(
346
+ commandSchema,
347
+ profile,
348
+ keyword,
349
+ eventMarker,
350
+ config
351
+ )
352
+ );
353
+ }
354
+
355
+ // Add proclitic-prefixed pattern variant for Arabic
356
+ // Pattern: [proclitic]? [event] [verb] [patient]
357
+ // Example: والنقر بدّل .active (and click toggle .active)
358
+ if (profile.tokenization?.prefixes) {
359
+ patterns.push(
360
+ generateVSOProcliticEventHandlerPattern(commandSchema, profile, keyword, config)
361
+ );
362
+ }
363
+ }
364
+ } else {
365
+ // SVO: Use VSO pattern structure for event handlers
366
+ if (hasTwoRequiredRoles) {
367
+ patterns.push(
368
+ generateVSOTwoRoleEventHandlerPattern(commandSchema, profile, keyword, eventMarker, config)
369
+ );
370
+ } else {
371
+ patterns.push(
372
+ generateVSOEventHandlerPattern(commandSchema, profile, keyword, eventMarker, config)
373
+ );
374
+ }
375
+ }
376
+
377
+ return patterns;
378
+ }
379
+
380
+ /**
381
+ * Generate SOV event handler pattern (Japanese, Korean, Turkish).
382
+ */
383
+ function generateSOVEventHandlerPattern(
384
+ commandSchema: CommandSchema,
385
+ profile: LanguageProfile,
386
+ keyword: KeywordTranslation,
387
+ eventMarker: RoleMarker,
388
+ config: GeneratorConfig
389
+ ): LanguagePattern {
390
+ const tokens: PatternToken[] = [];
391
+
392
+ // Event role
393
+ tokens.push({ type: 'role', role: 'event', optional: false });
394
+
395
+ // Event marker (after event in SOV)
396
+ // Handle multi-word markers like Korean "할 때" by splitting into separate tokens
397
+ if (eventMarker.position === 'after') {
398
+ const markerWords = eventMarker.primary.split(/\s+/);
399
+ if (markerWords.length > 1) {
400
+ // Multi-word marker: create a token for each word
401
+ for (const word of markerWords) {
402
+ tokens.push({ type: 'literal', value: word });
403
+ }
404
+ } else {
405
+ // Single-word marker: include alternatives
406
+ const markerToken: PatternToken = eventMarker.alternatives
407
+ ? { type: 'literal', value: eventMarker.primary, alternatives: eventMarker.alternatives }
408
+ : { type: 'literal', value: eventMarker.primary };
409
+ tokens.push(markerToken);
410
+ }
411
+ }
412
+
413
+ // Optional destination with its marker
414
+ const destMarker = profile.roleMarkers.destination;
415
+ if (destMarker) {
416
+ tokens.push({
417
+ type: 'group',
418
+ optional: true,
419
+ tokens: [
420
+ { type: 'role', role: 'destination', optional: true },
421
+ destMarker.alternatives
422
+ ? { type: 'literal', value: destMarker.primary, alternatives: destMarker.alternatives }
423
+ : { type: 'literal', value: destMarker.primary },
424
+ ],
425
+ });
426
+ }
427
+
428
+ // Patient role
429
+ tokens.push({ type: 'role', role: 'patient', optional: false });
430
+
431
+ // Patient marker (postposition/particle after patient)
432
+ const patientMarker = profile.roleMarkers.patient;
433
+ if (patientMarker) {
434
+ const patMarkerToken: PatternToken = patientMarker.alternatives
435
+ ? { type: 'literal', value: patientMarker.primary, alternatives: patientMarker.alternatives }
436
+ : { type: 'literal', value: patientMarker.primary };
437
+ tokens.push(patMarkerToken);
438
+ }
439
+
440
+ // Command verb at end (SOV)
441
+ const verbToken: PatternToken = keyword.alternatives
442
+ ? { type: 'literal', value: keyword.primary, alternatives: keyword.alternatives }
443
+ : { type: 'literal', value: keyword.primary };
444
+ tokens.push(verbToken);
445
+
446
+ return {
447
+ id: `${commandSchema.action}-event-${profile.code}-sov`,
448
+ language: profile.code,
449
+ command: 'on', // This is an event handler pattern
450
+ priority: (config.basePriority ?? 100) + 50, // Higher priority than simple commands
451
+ template: {
452
+ format: `{event} ${eventMarker.primary} {destination?} {patient} ${patientMarker?.primary || ''} ${keyword.primary}`,
453
+ tokens,
454
+ },
455
+ extraction: {
456
+ action: { value: commandSchema.action }, // Extract the wrapped command
457
+ event: { fromRole: 'event' },
458
+ patient: { fromRole: 'patient' },
459
+ destination: { fromRole: 'destination', default: { type: 'reference', value: 'me' } },
460
+ },
461
+ };
462
+ }
463
+
464
+ /**
465
+ * Generate SOV compact event handler pattern for languages with no-space forms.
466
+ *
467
+ * This handles Korean compact forms where the event marker is attached directly
468
+ * to the event word without a space:
469
+ * - 클릭할때 .active를토글 (click+when toggle .active)
470
+ *
471
+ * The pattern uses a single token for the no-space marker alternatives.
472
+ */
473
+ function generateSOVCompactEventHandlerPattern(
474
+ commandSchema: CommandSchema,
475
+ profile: LanguageProfile,
476
+ keyword: KeywordTranslation,
477
+ eventMarker: RoleMarker,
478
+ config: GeneratorConfig
479
+ ): LanguagePattern {
480
+ const tokens: PatternToken[] = [];
481
+
482
+ // Event role
483
+ tokens.push({ type: 'role', role: 'event', optional: false });
484
+
485
+ // Event marker as single token (using no-space alternatives)
486
+ // Filter alternatives to only include no-space versions
487
+ const noSpaceAlternatives =
488
+ eventMarker.alternatives?.filter(alt => !alt.includes(' ') && alt.length > 1) || [];
489
+
490
+ if (noSpaceAlternatives.length > 0) {
491
+ tokens.push({
492
+ type: 'literal',
493
+ value: noSpaceAlternatives[0],
494
+ alternatives: noSpaceAlternatives.slice(1),
495
+ });
496
+ }
497
+
498
+ // Optional destination with its marker
499
+ const destMarker = profile.roleMarkers.destination;
500
+ if (destMarker) {
501
+ tokens.push({
502
+ type: 'group',
503
+ optional: true,
504
+ tokens: [
505
+ { type: 'role', role: 'destination', optional: true },
506
+ destMarker.alternatives
507
+ ? { type: 'literal', value: destMarker.primary, alternatives: destMarker.alternatives }
508
+ : { type: 'literal', value: destMarker.primary },
509
+ ],
510
+ });
511
+ }
512
+
513
+ // Patient role
514
+ tokens.push({ type: 'role', role: 'patient', optional: false });
515
+
516
+ // Patient marker (postposition/particle after patient)
517
+ const patientMarker = profile.roleMarkers.patient;
518
+ if (patientMarker) {
519
+ const patMarkerToken: PatternToken = patientMarker.alternatives
520
+ ? { type: 'literal', value: patientMarker.primary, alternatives: patientMarker.alternatives }
521
+ : { type: 'literal', value: patientMarker.primary };
522
+ tokens.push(patMarkerToken);
523
+ }
524
+
525
+ // Command verb at end (SOV)
526
+ const verbToken: PatternToken = keyword.alternatives
527
+ ? { type: 'literal', value: keyword.primary, alternatives: keyword.alternatives }
528
+ : { type: 'literal', value: keyword.primary };
529
+ tokens.push(verbToken);
530
+
531
+ return {
532
+ id: `${commandSchema.action}-event-${profile.code}-sov-compact`,
533
+ language: profile.code,
534
+ command: 'on', // This is an event handler pattern
535
+ priority: (config.basePriority ?? 100) + 52, // Slightly higher priority for compact forms
536
+ template: {
537
+ format: `{event}${noSpaceAlternatives[0] || ''} {patient} ${keyword.primary}`,
538
+ tokens,
539
+ },
540
+ extraction: {
541
+ action: { value: commandSchema.action }, // Extract the wrapped command
542
+ event: { fromRole: 'event' },
543
+ patient: { fromRole: 'patient' },
544
+ destination: { fromRole: 'destination', default: { type: 'reference', value: 'me' } },
545
+ },
546
+ };
547
+ }
548
+
549
+ /**
550
+ * Generate SOV simple event handler pattern (patient optional, defaults to 'me').
551
+ *
552
+ * Supports patterns like:
553
+ * - Japanese: クリック で 増加 (click on increment)
554
+ * - Korean: 클릭 할 때 증가 (click when increment)
555
+ * - Turkish: tıklama da artır (click on increment)
556
+ *
557
+ * The patient is not explicitly specified - it defaults to 'me' (current element).
558
+ */
559
+ function generateSOVSimpleEventHandlerPattern(
560
+ commandSchema: CommandSchema,
561
+ profile: LanguageProfile,
562
+ keyword: KeywordTranslation,
563
+ eventMarker: RoleMarker,
564
+ config: GeneratorConfig
565
+ ): LanguagePattern {
566
+ const tokens: PatternToken[] = [];
567
+
568
+ // Event role
569
+ tokens.push({ type: 'role', role: 'event', optional: false });
570
+
571
+ // Event marker (after event in SOV)
572
+ if (eventMarker.position === 'after') {
573
+ const markerWords = eventMarker.primary.split(/\s+/);
574
+ if (markerWords.length > 1) {
575
+ // Multi-word marker: create a token for each word
576
+ for (const word of markerWords) {
577
+ tokens.push({ type: 'literal', value: word });
578
+ }
579
+ } else {
580
+ const markerToken: PatternToken = eventMarker.alternatives
581
+ ? { type: 'literal', value: eventMarker.primary, alternatives: eventMarker.alternatives }
582
+ : { type: 'literal', value: eventMarker.primary };
583
+ tokens.push(markerToken);
584
+ }
585
+ }
586
+
587
+ // Command verb at end (SOV) - no patient required
588
+ const verbToken: PatternToken = keyword.alternatives
589
+ ? { type: 'literal', value: keyword.primary, alternatives: keyword.alternatives }
590
+ : { type: 'literal', value: keyword.primary };
591
+ tokens.push(verbToken);
592
+
593
+ return {
594
+ id: `${commandSchema.action}-event-${profile.code}-sov-simple`,
595
+ language: profile.code,
596
+ command: 'on',
597
+ priority: (config.basePriority ?? 100) + 48, // Lower than full pattern (50) but higher than base
598
+ template: {
599
+ format: `{event} ${eventMarker.primary} ${keyword.primary}`,
600
+ tokens,
601
+ },
602
+ extraction: {
603
+ action: { value: commandSchema.action },
604
+ event: { fromRole: 'event' },
605
+ patient: { default: { type: 'reference', value: 'me' } }, // Default to 'me'
606
+ },
607
+ };
608
+ }
609
+
610
+ /**
611
+ * Generate SOV temporal event handler pattern.
612
+ *
613
+ * Supports patterns with temporal markers like:
614
+ * - Japanese: クリック 時 .active を 切り替え (click time toggle .active)
615
+ * - Japanese: クリック の 時 .active を 切り替え (click's time toggle .active)
616
+ *
617
+ * Uses profile.eventHandler.temporalMarkers for language-specific temporal words.
618
+ */
619
+ function generateSOVTemporalEventHandlerPattern(
620
+ commandSchema: CommandSchema,
621
+ profile: LanguageProfile,
622
+ keyword: KeywordTranslation,
623
+ config: GeneratorConfig
624
+ ): LanguagePattern | null {
625
+ const temporalMarkers = profile.eventHandler?.temporalMarkers;
626
+ if (!temporalMarkers || temporalMarkers.length === 0) return null;
627
+
628
+ const tokens: PatternToken[] = [];
629
+
630
+ // Event role
631
+ tokens.push({ type: 'role', role: 'event', optional: false });
632
+
633
+ // Optional possessive marker (の in Japanese)
634
+ if (profile.possessive?.marker) {
635
+ tokens.push({
636
+ type: 'group',
637
+ optional: true,
638
+ tokens: [{ type: 'literal', value: profile.possessive.marker }],
639
+ });
640
+ }
641
+
642
+ // Temporal marker (時, とき in Japanese)
643
+ tokens.push({
644
+ type: 'literal',
645
+ value: temporalMarkers[0],
646
+ alternatives: temporalMarkers.slice(1),
647
+ });
648
+
649
+ // Patient role
650
+ tokens.push({ type: 'role', role: 'patient', optional: false });
651
+
652
+ // Patient marker
653
+ const patientMarker = profile.roleMarkers.patient;
654
+ if (patientMarker?.primary) {
655
+ const patMarkerToken: PatternToken = patientMarker.alternatives
656
+ ? { type: 'literal', value: patientMarker.primary, alternatives: patientMarker.alternatives }
657
+ : { type: 'literal', value: patientMarker.primary };
658
+ tokens.push(patMarkerToken);
659
+ }
660
+
661
+ // Command verb at end
662
+ const verbToken: PatternToken = keyword.alternatives
663
+ ? { type: 'literal', value: keyword.primary, alternatives: keyword.alternatives }
664
+ : { type: 'literal', value: keyword.primary };
665
+ tokens.push(verbToken);
666
+
667
+ return {
668
+ id: `${commandSchema.action}-event-${profile.code}-sov-temporal`,
669
+ language: profile.code,
670
+ command: 'on',
671
+ priority: (config.basePriority ?? 100) + 49, // Between simple and full pattern
672
+ template: {
673
+ format: `{event} ${temporalMarkers[0]} {patient} ${keyword.primary}`,
674
+ tokens,
675
+ },
676
+ extraction: {
677
+ action: { value: commandSchema.action },
678
+ event: { fromRole: 'event' },
679
+ patient: { fromRole: 'patient' },
680
+ },
681
+ };
682
+ }
683
+
684
+ /**
685
+ * Generate VSO event handler pattern (Arabic).
686
+ */
687
+ function generateVSOEventHandlerPattern(
688
+ commandSchema: CommandSchema,
689
+ profile: LanguageProfile,
690
+ keyword: KeywordTranslation,
691
+ eventMarker: RoleMarker,
692
+ config: GeneratorConfig
693
+ ): LanguagePattern {
694
+ const tokens: PatternToken[] = [];
695
+
696
+ // Event marker (before event in VSO)
697
+ if (eventMarker.position === 'before') {
698
+ const markerToken: PatternToken = eventMarker.alternatives
699
+ ? { type: 'literal', value: eventMarker.primary, alternatives: eventMarker.alternatives }
700
+ : { type: 'literal', value: eventMarker.primary };
701
+ tokens.push(markerToken);
702
+ }
703
+
704
+ // Event role
705
+ tokens.push({ type: 'role', role: 'event', optional: false });
706
+
707
+ // Command verb (verb comes early in VSO)
708
+ const verbToken: PatternToken = keyword.alternatives
709
+ ? { type: 'literal', value: keyword.primary, alternatives: keyword.alternatives }
710
+ : { type: 'literal', value: keyword.primary };
711
+ tokens.push(verbToken);
712
+
713
+ // Patient role
714
+ tokens.push({ type: 'role', role: 'patient', optional: false });
715
+
716
+ // Optional destination with preposition
717
+ const destMarker = profile.roleMarkers.destination;
718
+ if (destMarker) {
719
+ tokens.push({
720
+ type: 'group',
721
+ optional: true,
722
+ tokens: [
723
+ destMarker.alternatives
724
+ ? { type: 'literal', value: destMarker.primary, alternatives: destMarker.alternatives }
725
+ : { type: 'literal', value: destMarker.primary },
726
+ { type: 'role', role: 'destination', optional: true },
727
+ ],
728
+ });
729
+ }
730
+
731
+ return {
732
+ id: `${commandSchema.action}-event-${profile.code}-vso`,
733
+ language: profile.code,
734
+ command: 'on', // This is an event handler pattern
735
+ priority: (config.basePriority ?? 100) + 50, // Higher priority than simple commands
736
+ template: {
737
+ format: `${eventMarker.primary} {event} ${keyword.primary} {patient} ${destMarker?.primary || ''} {destination?}`,
738
+ tokens,
739
+ },
740
+ extraction: {
741
+ action: { value: commandSchema.action }, // Extract the wrapped command
742
+ event: { fromRole: 'event' },
743
+ patient: { fromRole: 'patient' },
744
+ destination: { fromRole: 'destination', default: { type: 'reference', value: 'me' } },
745
+ },
746
+ };
747
+ }
748
+
749
+ /**
750
+ * Generate SOV two-role event handler pattern (for put/set commands).
751
+ *
752
+ * Patterns:
753
+ * - Japanese put: 入力 で "test" を #output に 入れる
754
+ * [event] [eventMarker] [patient] [patientMarker] [destination] [destMarker] [verb]
755
+ * - Korean set: 변경 할 때 x 를 10 으로 설정
756
+ * [event] [eventMarker] [role1] [role1Marker] [role2] [role2Marker] [verb]
757
+ * - Turkish put: giriş de "test" i #output a koy
758
+ * [event] [eventMarker] [patient] [patientMarker] [destination] [destMarker] [verb]
759
+ */
760
+ function generateSOVTwoRoleEventHandlerPattern(
761
+ commandSchema: CommandSchema,
762
+ profile: LanguageProfile,
763
+ keyword: KeywordTranslation,
764
+ eventMarker: RoleMarker,
765
+ config: GeneratorConfig
766
+ ): LanguagePattern {
767
+ const tokens: PatternToken[] = [];
768
+
769
+ // Event role
770
+ tokens.push({ type: 'role', role: 'event', optional: false });
771
+
772
+ // Event marker (after event in SOV)
773
+ // Handle multi-word markers like Korean "할 때" by splitting into separate tokens
774
+ if (eventMarker.position === 'after') {
775
+ const markerWords = eventMarker.primary.split(/\s+/);
776
+ if (markerWords.length > 1) {
777
+ // Multi-word marker: create a token for each word
778
+ for (const word of markerWords) {
779
+ tokens.push({ type: 'literal', value: word });
780
+ }
781
+ } else {
782
+ // Single-word marker: include alternatives
783
+ const markerToken: PatternToken = eventMarker.alternatives
784
+ ? { type: 'literal', value: eventMarker.primary, alternatives: eventMarker.alternatives }
785
+ : { type: 'literal', value: eventMarker.primary };
786
+ tokens.push(markerToken);
787
+ }
788
+ }
789
+
790
+ // Get the two required roles from the schema
791
+ const requiredRoles = commandSchema.roles.filter(r => r.required);
792
+
793
+ // Sort by SOV position (lower number = earlier in sentence)
794
+ const sortedRoles = [...requiredRoles].sort((a, b) => {
795
+ const aPos = a.sovPosition ?? 999;
796
+ const bPos = b.sovPosition ?? 999;
797
+ return aPos - bPos;
798
+ });
799
+
800
+ // Add each role with its marker
801
+ for (const roleSpec of sortedRoles) {
802
+ // Add the role
803
+ tokens.push({ type: 'role', role: roleSpec.role, optional: false });
804
+
805
+ // Get marker for this role - check for override first
806
+ let marker: string | undefined;
807
+ let markerAlternatives: string[] | undefined;
808
+
809
+ if (roleSpec.markerOverride && roleSpec.markerOverride[profile.code]) {
810
+ // Use the override marker
811
+ marker = roleSpec.markerOverride[profile.code];
812
+ } else {
813
+ // Use default role marker from profile
814
+ const roleMarker = profile.roleMarkers[roleSpec.role];
815
+ if (roleMarker) {
816
+ marker = roleMarker.primary;
817
+ markerAlternatives = roleMarker.alternatives;
818
+ }
819
+ }
820
+
821
+ // Add the marker token
822
+ if (marker) {
823
+ const markerToken: PatternToken = markerAlternatives
824
+ ? { type: 'literal', value: marker, alternatives: markerAlternatives }
825
+ : { type: 'literal', value: marker };
826
+ tokens.push(markerToken);
827
+ }
828
+ }
829
+
830
+ // Command verb at end (SOV)
831
+ const verbToken: PatternToken = keyword.alternatives
832
+ ? { type: 'literal', value: keyword.primary, alternatives: keyword.alternatives }
833
+ : { type: 'literal', value: keyword.primary };
834
+ tokens.push(verbToken);
835
+
836
+ // Build format string
837
+ const roleNames = sortedRoles.map(r => `{${r.role}}`).join(' ');
838
+
839
+ return {
840
+ id: `${commandSchema.action}-event-${profile.code}-sov-2role`,
841
+ language: profile.code,
842
+ command: 'on', // This is an event handler pattern
843
+ priority: (config.basePriority ?? 100) + 55, // Higher priority than single-role patterns
844
+ template: {
845
+ format: `{event} ${eventMarker.primary} ${roleNames} ${keyword.primary}`,
846
+ tokens,
847
+ },
848
+ extraction: {
849
+ action: { value: commandSchema.action }, // Extract the wrapped command
850
+ event: { fromRole: 'event' },
851
+ patient: { fromRole: 'patient' },
852
+ destination: { fromRole: 'destination' },
853
+ },
854
+ };
855
+ }
856
+
857
+ /**
858
+ * Generate VSO two-role event handler pattern (for put/set commands).
859
+ *
860
+ * Patterns:
861
+ * - Arabic put: عند الإدخال ضع "test" في #output
862
+ * [eventMarker] [event] [verb] [patient] [destPrep] [destination]
863
+ * - Arabic set: عند التغيير عيّن x إلى 10
864
+ * [eventMarker] [event] [verb] [destination] [patientPrep] [patient]
865
+ */
866
+ function generateVSOTwoRoleEventHandlerPattern(
867
+ commandSchema: CommandSchema,
868
+ profile: LanguageProfile,
869
+ keyword: KeywordTranslation,
870
+ eventMarker: RoleMarker,
871
+ config: GeneratorConfig
872
+ ): LanguagePattern {
873
+ const tokens: PatternToken[] = [];
874
+
875
+ // Event marker (before event in VSO)
876
+ if (eventMarker.position === 'before') {
877
+ const markerToken: PatternToken = eventMarker.alternatives
878
+ ? { type: 'literal', value: eventMarker.primary, alternatives: eventMarker.alternatives }
879
+ : { type: 'literal', value: eventMarker.primary };
880
+ tokens.push(markerToken);
881
+ }
882
+
883
+ // Event role
884
+ tokens.push({ type: 'role', role: 'event', optional: false });
885
+
886
+ // Command verb (verb comes early in VSO)
887
+ const verbToken: PatternToken = keyword.alternatives
888
+ ? { type: 'literal', value: keyword.primary, alternatives: keyword.alternatives }
889
+ : { type: 'literal', value: keyword.primary };
890
+ tokens.push(verbToken);
891
+
892
+ // Get the two required roles from the schema
893
+ const requiredRoles = commandSchema.roles.filter(r => r.required);
894
+
895
+ // Sort by SVO position for VSO (role order is similar)
896
+ const sortedRoles = [...requiredRoles].sort((a, b) => {
897
+ const aPos = a.svoPosition ?? 999;
898
+ const bPos = b.svoPosition ?? 999;
899
+ return aPos - bPos;
900
+ });
901
+
902
+ // Add each role with its preposition/marker
903
+ for (const roleSpec of sortedRoles) {
904
+ // Get marker for this role - check for override first
905
+ let marker: string | undefined;
906
+ let markerAlternatives: string[] | undefined;
907
+
908
+ if (roleSpec.markerOverride && roleSpec.markerOverride[profile.code]) {
909
+ // Use the override marker
910
+ marker = roleSpec.markerOverride[profile.code];
911
+ } else {
912
+ // Use default role marker from profile
913
+ const roleMarker = profile.roleMarkers[roleSpec.role];
914
+ if (roleMarker) {
915
+ marker = roleMarker.primary;
916
+ markerAlternatives = roleMarker.alternatives;
917
+ }
918
+ }
919
+
920
+ // In VSO, prepositions come BEFORE the noun (prepositional languages)
921
+ if (marker) {
922
+ const markerToken: PatternToken = markerAlternatives
923
+ ? { type: 'literal', value: marker, alternatives: markerAlternatives }
924
+ : { type: 'literal', value: marker };
925
+ tokens.push(markerToken);
926
+ }
927
+
928
+ // Add the role
929
+ tokens.push({ type: 'role', role: roleSpec.role, optional: false });
930
+ }
931
+
932
+ // Build format string
933
+ const roleNames = sortedRoles.map(r => `{${r.role}}`).join(' ');
934
+
935
+ return {
936
+ id: `${commandSchema.action}-event-${profile.code}-vso-2role`,
937
+ language: profile.code,
938
+ command: 'on', // This is an event handler pattern
939
+ priority: (config.basePriority ?? 100) + 55, // Higher priority than single-role patterns
940
+ template: {
941
+ format: `${eventMarker.primary} {event} ${keyword.primary} ${roleNames}`,
942
+ tokens,
943
+ },
944
+ extraction: {
945
+ action: { value: commandSchema.action }, // Extract the wrapped command
946
+ event: { fromRole: 'event' },
947
+ patient: { fromRole: 'patient' },
948
+ destination: { fromRole: 'destination' },
949
+ },
950
+ };
951
+ }
952
+
953
+ /**
954
+ * Generate VSO negated event handler pattern.
955
+ *
956
+ * Patterns:
957
+ * - Arabic: عند عدم التركيز أخف #tooltip
958
+ * [eventMarker] [negation] [event] [verb] [patient]
959
+ *
960
+ * Used for events expressed as negation + opposite action:
961
+ * - عدم التركيز = "not focusing" = blur
962
+ */
963
+ function generateVSONegatedEventHandlerPattern(
964
+ commandSchema: CommandSchema,
965
+ profile: LanguageProfile,
966
+ keyword: KeywordTranslation,
967
+ eventMarker: RoleMarker,
968
+ config: GeneratorConfig
969
+ ): LanguagePattern {
970
+ const tokens: PatternToken[] = [];
971
+ const negationMarker = profile.eventHandler?.negationMarker;
972
+
973
+ // Event marker (before event in VSO)
974
+ if (eventMarker.position === 'before') {
975
+ const markerToken: PatternToken = eventMarker.alternatives
976
+ ? { type: 'literal', value: eventMarker.primary, alternatives: eventMarker.alternatives }
977
+ : { type: 'literal', value: eventMarker.primary };
978
+ tokens.push(markerToken);
979
+ }
980
+
981
+ // Negation marker (e.g., عدم = "not/lack of")
982
+ if (negationMarker) {
983
+ const negToken: PatternToken = negationMarker.alternatives
984
+ ? {
985
+ type: 'literal',
986
+ value: negationMarker.primary,
987
+ alternatives: negationMarker.alternatives,
988
+ }
989
+ : { type: 'literal', value: negationMarker.primary };
990
+ tokens.push(negToken);
991
+ }
992
+
993
+ // Event role (the action being negated, e.g., التركيز = "the focusing")
994
+ tokens.push({ type: 'role', role: 'event', optional: false });
995
+
996
+ // Command verb (verb comes early in VSO)
997
+ const verbToken: PatternToken = keyword.alternatives
998
+ ? { type: 'literal', value: keyword.primary, alternatives: keyword.alternatives }
999
+ : { type: 'literal', value: keyword.primary };
1000
+ tokens.push(verbToken);
1001
+
1002
+ // Patient role
1003
+ tokens.push({ type: 'role', role: 'patient', optional: false });
1004
+
1005
+ // Optional destination with preposition
1006
+ const destMarker = profile.roleMarkers.destination;
1007
+ if (destMarker) {
1008
+ tokens.push({
1009
+ type: 'group',
1010
+ optional: true,
1011
+ tokens: [
1012
+ destMarker.alternatives
1013
+ ? { type: 'literal', value: destMarker.primary, alternatives: destMarker.alternatives }
1014
+ : { type: 'literal', value: destMarker.primary },
1015
+ { type: 'role', role: 'destination', optional: true },
1016
+ ],
1017
+ });
1018
+ }
1019
+
1020
+ return {
1021
+ id: `${commandSchema.action}-event-${profile.code}-vso-negated`,
1022
+ language: profile.code,
1023
+ command: 'on', // This is an event handler pattern
1024
+ priority: (config.basePriority ?? 100) + 48, // Slightly lower priority than standard patterns
1025
+ template: {
1026
+ format: `${eventMarker.primary} ${negationMarker?.primary || ''} {event} ${keyword.primary} {patient} ${destMarker?.primary || ''} {destination?}`,
1027
+ tokens,
1028
+ },
1029
+ extraction: {
1030
+ action: { value: commandSchema.action }, // Extract the wrapped command
1031
+ event: { fromRole: 'event' },
1032
+ patient: { fromRole: 'patient' },
1033
+ destination: { fromRole: 'destination', default: { type: 'reference', value: 'me' } },
1034
+ },
1035
+ };
1036
+ }
1037
+
1038
+ /**
1039
+ * Generate VSO proclitic event handler pattern (for Arabic chained events).
1040
+ *
1041
+ * Patterns:
1042
+ * - Arabic: والنقر بدّل .active
1043
+ * [proclitic] [event] [verb] [patient]
1044
+ * - Arabic: فالتحويم أضف .highlight
1045
+ * [proclitic] [event] [verb] [patient]
1046
+ *
1047
+ * These patterns have a conjunction proclitic (و = and, ف = then) attached to
1048
+ * the event, without the عند event marker. Used for chained/consequent events.
1049
+ */
1050
+ function generateVSOProcliticEventHandlerPattern(
1051
+ commandSchema: CommandSchema,
1052
+ profile: LanguageProfile,
1053
+ keyword: KeywordTranslation,
1054
+ config: GeneratorConfig
1055
+ ): LanguagePattern {
1056
+ const tokens: PatternToken[] = [];
1057
+
1058
+ // Required conjunction token (و = and, ف = then)
1059
+ // The conjunction must be present for this pattern - it distinguishes chained events from regular commands
1060
+ // Use normalized values since proclitics are tokenized as conjunction tokens
1061
+ tokens.push({
1062
+ type: 'literal',
1063
+ value: 'and', // Matches normalized 'and' (Arabic: و)
1064
+ alternatives: ['then'], // Also matches normalized 'then' (Arabic: ف)
1065
+ });
1066
+
1067
+ // Event role (the event name, e.g., النقر = the click)
1068
+ tokens.push({ type: 'role', role: 'event', optional: false });
1069
+
1070
+ // Command verb (verb comes early in VSO)
1071
+ const verbToken: PatternToken = keyword.alternatives
1072
+ ? { type: 'literal', value: keyword.primary, alternatives: keyword.alternatives }
1073
+ : { type: 'literal', value: keyword.primary };
1074
+ tokens.push(verbToken);
1075
+
1076
+ // Patient role
1077
+ tokens.push({ type: 'role', role: 'patient', optional: false });
1078
+
1079
+ // Optional destination with preposition
1080
+ const destMarker = profile.roleMarkers.destination;
1081
+ if (destMarker) {
1082
+ tokens.push({
1083
+ type: 'group',
1084
+ optional: true,
1085
+ tokens: [
1086
+ destMarker.alternatives
1087
+ ? { type: 'literal', value: destMarker.primary, alternatives: destMarker.alternatives }
1088
+ : { type: 'literal', value: destMarker.primary },
1089
+ { type: 'role', role: 'destination', optional: true },
1090
+ ],
1091
+ });
1092
+ }
1093
+
1094
+ return {
1095
+ id: `${commandSchema.action}-event-${profile.code}-vso-proclitic`,
1096
+ language: profile.code,
1097
+ command: 'on', // This is an event handler pattern
1098
+ priority: (config.basePriority ?? 100) + 45, // Lower priority than standard patterns
1099
+ template: {
1100
+ format: `[proclitic?] {event} ${keyword.primary} {patient} ${destMarker?.primary || ''} {destination?}`,
1101
+ tokens,
1102
+ },
1103
+ extraction: {
1104
+ action: { value: commandSchema.action }, // Extract the wrapped command
1105
+ event: { fromRole: 'event' },
1106
+ patient: { fromRole: 'patient' },
1107
+ destination: { fromRole: 'destination', default: { type: 'reference', value: 'me' } },
1108
+ },
1109
+ };
1110
+ }
1111
+
230
1112
  // =============================================================================
231
1113
  // Token Building
232
1114
  // =============================================================================