@lokascript/framework 2.8.0 → 2.9.1

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 (48) hide show
  1. package/CHANGELOG.md +393 -0
  2. package/dist/api/create-dsl.d.ts +93 -1
  3. package/dist/api/create-dsl.d.ts.map +1 -1
  4. package/dist/api/domain-registry.d.ts +5 -3
  5. package/dist/api/domain-registry.d.ts.map +1 -1
  6. package/dist/api/index.js +232 -18
  7. package/dist/api/index.js.map +1 -1
  8. package/dist/core/index.js +26 -6
  9. package/dist/core/index.js.map +1 -1
  10. package/dist/core/tokenization/base-tokenizer.d.ts +15 -3
  11. package/dist/core/tokenization/base-tokenizer.d.ts.map +1 -1
  12. package/dist/core/tokenization/char-classifiers.d.ts +2 -2
  13. package/dist/core/tokenization/index.js +26 -6
  14. package/dist/core/tokenization/index.js.map +1 -1
  15. package/dist/core/tokenization/token-utils.d.ts +15 -0
  16. package/dist/core/tokenization/token-utils.d.ts.map +1 -1
  17. package/dist/generation/index.js +73 -47
  18. package/dist/generation/index.js.map +1 -1
  19. package/dist/generation/pattern-generator.d.ts +8 -1
  20. package/dist/generation/pattern-generator.d.ts.map +1 -1
  21. package/dist/generation/renderer.d.ts +53 -1
  22. package/dist/generation/renderer.d.ts.map +1 -1
  23. package/dist/index.cjs +302 -115
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.ts +4 -4
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +298 -115
  28. package/dist/index.js.map +1 -1
  29. package/dist/interfaces/value-extractor.d.ts +5 -0
  30. package/dist/interfaces/value-extractor.d.ts.map +1 -1
  31. package/dist/multilingual/index.js +25 -6
  32. package/dist/multilingual/index.js.map +1 -1
  33. package/package.json +4 -3
  34. package/src/api/create-dsl.test.ts +11 -0
  35. package/src/api/create-dsl.ts +278 -9
  36. package/src/api/domain-registry.ts +15 -10
  37. package/src/api/extensions.test.ts +322 -0
  38. package/src/core/tokenization/base-tokenizer.ts +23 -8
  39. package/src/core/tokenization/char-classifiers.ts +2 -2
  40. package/src/core/tokenization/css-selector-extractor.test.ts +67 -0
  41. package/src/core/tokenization/token-utils.ts +18 -0
  42. package/src/generation/domain-renderer.test.ts +172 -0
  43. package/src/generation/pattern-generator.test.ts +102 -0
  44. package/src/generation/pattern-generator.ts +27 -19
  45. package/src/generation/renderer.test.ts +243 -4
  46. package/src/generation/renderer.ts +188 -45
  47. package/src/index.ts +9 -1
  48. package/src/interfaces/value-extractor.ts +50 -0
package/dist/api/index.js CHANGED
@@ -1282,10 +1282,12 @@ function buildTokens(schema, profile, keyword, alternatives) {
1282
1282
  addRoleWithMarker(tokens, role, profile);
1283
1283
  }
1284
1284
  } else if (profile.wordOrder === "SOV") {
1285
+ const preVerb = [];
1286
+ const postVerb = [];
1285
1287
  for (const role of sortedRoles) {
1286
- addRoleWithMarker(tokens, role, profile);
1288
+ addRoleWithMarker(role.sovSlot === "postVerb" ? postVerb : preVerb, role, profile);
1287
1289
  }
1288
- tokens.push(keywordToken);
1290
+ tokens.push(...preVerb, keywordToken, ...postVerb);
1289
1291
  } else if (profile.wordOrder === "VSO") {
1290
1292
  tokens.push(keywordToken);
1291
1293
  for (const role of sortedRoles) {
@@ -1368,29 +1370,130 @@ function buildExtractionRules(schema, profile) {
1368
1370
  return rules;
1369
1371
  }
1370
1372
  function buildFormatString(schema, profile, keyword) {
1371
- const parts = [];
1372
- if (profile.wordOrder === "SVO" || profile.wordOrder === "VSO") {
1373
- parts.push(keyword);
1374
- }
1373
+ const isSOV = profile.wordOrder === "SOV";
1374
+ const preVerb = [];
1375
+ const postVerb = [];
1375
1376
  const sortedRoles = sortRolesByWordOrder(schema.roles, profile.wordOrder);
1376
1377
  for (const role of sortedRoles) {
1377
1378
  const marker = getMarkerForRole(role, profile);
1378
1379
  const roleName = `{${role.role}}`;
1380
+ const bucket = isSOV && role.sovSlot === "postVerb" ? postVerb : preVerb;
1379
1381
  if (marker) {
1380
1382
  const markerInfo = profile.roleMarkers?.[role.role];
1381
1383
  if (markerInfo?.position === "after") {
1382
- parts.push(`${roleName} ${marker}`);
1384
+ bucket.push(`${roleName} ${marker}`);
1383
1385
  } else {
1384
- parts.push(`${marker} ${roleName}`);
1386
+ bucket.push(`${marker} ${roleName}`);
1385
1387
  }
1386
1388
  } else {
1387
- parts.push(roleName);
1389
+ bucket.push(roleName);
1388
1390
  }
1389
1391
  }
1390
- if (profile.wordOrder === "SOV") {
1391
- parts.push(keyword);
1392
+ return (isSOV ? [...preVerb, keyword, ...postVerb] : [keyword, ...preVerb, ...postVerb]).join(
1393
+ " "
1394
+ );
1395
+ }
1396
+
1397
+ // src/generation/renderer.ts
1398
+ function lookupKeyword(keywords, action, language) {
1399
+ return keywords[action]?.[language] ?? action;
1400
+ }
1401
+ function buildPhrase(...parts) {
1402
+ return parts.filter(Boolean).join(" ");
1403
+ }
1404
+ function buildTablesFromProfiles(schemas, profiles) {
1405
+ const keywords = {};
1406
+ const markers = {};
1407
+ for (const profile of profiles) {
1408
+ for (const [action, kw] of Object.entries(profile.keywords)) {
1409
+ if (!keywords[action]) keywords[action] = {};
1410
+ keywords[action][profile.code] = kw.primary;
1411
+ }
1392
1412
  }
1393
- return parts.join(" ");
1413
+ for (const schema of schemas) {
1414
+ for (const role of schema.roles) {
1415
+ if (role.markerOverride) {
1416
+ const markerKey = role.role;
1417
+ if (!markers[markerKey]) markers[markerKey] = {};
1418
+ for (const [lang, markerText] of Object.entries(role.markerOverride)) {
1419
+ markers[markerKey][lang] = markerText;
1420
+ }
1421
+ }
1422
+ }
1423
+ }
1424
+ for (const profile of profiles) {
1425
+ if (profile.roleMarkers) {
1426
+ for (const [markerKey, markerDef] of Object.entries(profile.roleMarkers)) {
1427
+ if (!markers[markerKey]) markers[markerKey] = {};
1428
+ markers[markerKey][profile.code] = markerDef.primary;
1429
+ }
1430
+ }
1431
+ }
1432
+ return { keywords, markers };
1433
+ }
1434
+ function detectWordOrders(profiles) {
1435
+ const sovLanguages = /* @__PURE__ */ new Set();
1436
+ const vsoLanguages = /* @__PURE__ */ new Set();
1437
+ for (const profile of profiles) {
1438
+ if (profile.wordOrder === "SOV") sovLanguages.add(profile.code);
1439
+ if (profile.wordOrder === "VSO") vsoLanguages.add(profile.code);
1440
+ }
1441
+ return { sovLanguages, vsoLanguages };
1442
+ }
1443
+ function createDomainRenderer(config) {
1444
+ const { schemas, profiles, overrides } = config;
1445
+ let tables;
1446
+ return (node, language) => {
1447
+ const override = overrides?.[node.action];
1448
+ if (override) return override(node, language);
1449
+ tables ?? (tables = buildRenderTables(schemas, profiles));
1450
+ return renderFromSchema(node, language, tables);
1451
+ };
1452
+ }
1453
+ function buildRenderTables(schemas, profiles) {
1454
+ const { keywords, markers } = buildTablesFromProfiles(schemas, profiles);
1455
+ const { sovLanguages } = detectWordOrders(profiles);
1456
+ const markerPositions = {};
1457
+ for (const profile of profiles) {
1458
+ for (const [role, markerDef] of Object.entries(profile.roleMarkers ?? {})) {
1459
+ if (!markerDef.position) continue;
1460
+ markerPositions[role] ?? (markerPositions[role] = {});
1461
+ markerPositions[role][profile.code] = markerDef.position;
1462
+ }
1463
+ }
1464
+ const schemaMap = /* @__PURE__ */ new Map();
1465
+ for (const s of schemas) schemaMap.set(s.action, s);
1466
+ return { keywords, markers, markerPositions, sovLanguages, schemaMap };
1467
+ }
1468
+ function renderFromSchema(node, language, tables) {
1469
+ const schema = tables.schemaMap.get(node.action);
1470
+ if (!schema) return null;
1471
+ const keyword = lookupKeyword(tables.keywords, node.action, language);
1472
+ const isSOV = tables.sovLanguages.has(language);
1473
+ const ordered = sortRolesByWordOrder([...schema.roles], isSOV ? "SOV" : "SVO");
1474
+ const preVerb = [];
1475
+ const postVerb = [];
1476
+ for (const role of ordered) {
1477
+ let value = extractRoleValue(node, role.role);
1478
+ if (!value && role.default !== void 0) {
1479
+ value = String(extractValue(role.default));
1480
+ }
1481
+ if (!value) continue;
1482
+ if (role.quoteMultiword && /\s/.test(value) && !/^(["']).*\1$/.test(value)) {
1483
+ value = `"${value}"`;
1484
+ }
1485
+ const markerText = role.renderOverride?.[language] ?? role.markerOverride?.[language] ?? role.renderOverride?.["*"] ?? tables.markers[role.role]?.[language];
1486
+ const markerPosition = role.markerPositionOverride?.[language] ?? role.markerPosition ?? tables.markerPositions[role.role]?.[language] ?? (isSOV ? "after" : "before");
1487
+ const bucket = isSOV && role.sovSlot === "postVerb" ? postVerb : preVerb;
1488
+ if (markerText && markerPosition === "before") {
1489
+ bucket.push(markerText, value);
1490
+ } else if (markerText) {
1491
+ bucket.push(value, markerText);
1492
+ } else {
1493
+ bucket.push(value);
1494
+ }
1495
+ }
1496
+ return isSOV ? buildPhrase(...preVerb, keyword, ...postVerb) : buildPhrase(keyword, ...preVerb, ...postVerb);
1394
1497
  }
1395
1498
 
1396
1499
  // src/grammar/types.ts
@@ -1622,10 +1725,17 @@ import { renderExplicit, renderDocument } from "@lokascript/intent";
1622
1725
 
1623
1726
  // src/api/create-dsl.ts
1624
1727
  var DSLRegistry = class {
1625
- constructor(config) {
1728
+ /**
1729
+ * @param partiallyTranslatedActions Actions permitted to lack a keyword in
1730
+ * some configured languages — extension commands, which may be added for a
1731
+ * subset of the DSL's languages. A built-in command missing a keyword stays
1732
+ * an error, since that is a domain-authoring mistake.
1733
+ */
1734
+ constructor(config, partiallyTranslatedActions = /* @__PURE__ */ new Set()) {
1626
1735
  this.patterns = /* @__PURE__ */ new Map();
1627
1736
  this.tokenizers = /* @__PURE__ */ new Map();
1628
1737
  this.schemas = config.schemas;
1738
+ this.partiallyTranslatedActions = partiallyTranslatedActions;
1629
1739
  for (const lang of config.languages) {
1630
1740
  this.registerLanguage(lang);
1631
1741
  }
@@ -1634,6 +1744,9 @@ var DSLRegistry = class {
1634
1744
  this.tokenizers.set(lang.code, lang.tokenizer);
1635
1745
  const patterns = [];
1636
1746
  for (const schema of this.schemas) {
1747
+ if (this.partiallyTranslatedActions.has(schema.action) && !lang.patternProfile.keywords[schema.action]) {
1748
+ continue;
1749
+ }
1637
1750
  const pattern = generatePattern(schema, lang.patternProfile);
1638
1751
  patterns.push(pattern);
1639
1752
  }
@@ -1650,13 +1763,25 @@ var DSLRegistry = class {
1650
1763
  }
1651
1764
  };
1652
1765
  var MultilingualDSLImpl = class {
1653
- constructor(config, registry, transformer) {
1766
+ constructor(config, registry, transformer, profileProvider) {
1654
1767
  this.registry = registry;
1655
1768
  this.matcher = new PatternMatcher();
1656
1769
  this.transformer = transformer;
1770
+ this.profileProvider = profileProvider;
1657
1771
  if (config.codeGenerator) {
1658
1772
  this.codeGenerator = config.codeGenerator;
1659
1773
  }
1774
+ if (config.renderer) {
1775
+ this.domainRenderer = config.renderer;
1776
+ }
1777
+ this.extensionRenderers = /* @__PURE__ */ new Map();
1778
+ for (const extension of config.extensions ?? []) {
1779
+ if (extension.render) this.extensionRenderers.set(extension.schema.action, extension.render);
1780
+ }
1781
+ this.schemaFallbackRenderer = createDomainRenderer({
1782
+ schemas: config.schemas,
1783
+ profiles: config.languages.map((l) => l.patternProfile)
1784
+ });
1660
1785
  const schemaMap = new Map(config.schemas.map((s) => [s.action, s]));
1661
1786
  this.schemaLookup = {
1662
1787
  getSchema: (action) => schemaMap.get(action)
@@ -1718,6 +1843,13 @@ var MultilingualDSLImpl = class {
1718
1843
  }
1719
1844
  translate(input, fromLanguage, toLanguage) {
1720
1845
  if (isExplicitSyntax(input)) return input;
1846
+ for (const language of [fromLanguage, toLanguage]) {
1847
+ if (!this.profileProvider.getProfile(language)) {
1848
+ throw new Error(
1849
+ `translate() requires a grammar profile for language "${language}", but none is configured. Set 'grammarProfile' on the LanguageConfig for "${language}" in createMultilingualDSL() (parse/validate/compile do not need it), or inject a custom 'profileProvider'.`
1850
+ );
1851
+ }
1852
+ }
1721
1853
  return this.transformer.transform(input, fromLanguage, toLanguage);
1722
1854
  }
1723
1855
  compile(input, language) {
@@ -1746,6 +1878,18 @@ var MultilingualDSLImpl = class {
1746
1878
  };
1747
1879
  }
1748
1880
  }
1881
+ render(node, language) {
1882
+ const extensionRenderer = this.extensionRenderers.get(node.action);
1883
+ if (extensionRenderer) {
1884
+ const rendered = extensionRenderer(node, language);
1885
+ if (rendered != null) return rendered;
1886
+ }
1887
+ if (this.domainRenderer) {
1888
+ const rendered = this.domainRenderer(node, language);
1889
+ if (rendered != null) return rendered;
1890
+ }
1891
+ return this.schemaFallbackRenderer(node, language);
1892
+ }
1749
1893
  getSupportedLanguages() {
1750
1894
  return this.registry.getSupportedLanguages();
1751
1895
  }
@@ -1770,15 +1914,80 @@ function createDefaultProfileProvider(config) {
1770
1914
  }
1771
1915
  return new InMemoryProfileProvider(profiles);
1772
1916
  }
1917
+ function applyExtensions(config) {
1918
+ const extensions = config.extensions;
1919
+ if (!extensions || extensions.length === 0) return config;
1920
+ const configuredLanguages = new Set(config.languages.map((l) => l.code));
1921
+ const actions = new Set(config.schemas.map((s) => s.action));
1922
+ for (const extension of extensions) {
1923
+ const { action } = extension.schema;
1924
+ if (actions.has(action)) {
1925
+ throw new Error(
1926
+ `Extension command "${action}" collides with a command this DSL already defines. Pick a different action name.`
1927
+ );
1928
+ }
1929
+ actions.add(action);
1930
+ for (const code of Object.keys(extension.vocabulary)) {
1931
+ if (!configuredLanguages.has(code)) {
1932
+ throw new Error(
1933
+ `Extension command "${action}" supplies vocabulary for language "${code}", which is not configured on this DSL. Configured languages: ${[...configuredLanguages].join(", ")}.`
1934
+ );
1935
+ }
1936
+ }
1937
+ }
1938
+ const schemas = [...config.schemas, ...extensions.map((e) => e.schema)];
1939
+ const languages = config.languages.map((lang) => {
1940
+ const keywords = { ...lang.patternProfile.keywords };
1941
+ let roleMarkers = lang.patternProfile.roleMarkers;
1942
+ for (const extension of extensions) {
1943
+ const vocabulary = extension.vocabulary[lang.code];
1944
+ if (!vocabulary) continue;
1945
+ keywords[extension.schema.action] = { ...vocabulary.keyword };
1946
+ if (vocabulary.roleMarkers) {
1947
+ roleMarkers = { ...roleMarkers, ...vocabulary.roleMarkers };
1948
+ }
1949
+ }
1950
+ return {
1951
+ ...lang,
1952
+ patternProfile: {
1953
+ ...lang.patternProfile,
1954
+ keywords,
1955
+ ...roleMarkers !== void 0 && { roleMarkers }
1956
+ }
1957
+ };
1958
+ });
1959
+ const extensionGenerators = new Map(
1960
+ extensions.filter((e) => e.generate).map((e) => [e.schema.action, e.generate])
1961
+ );
1962
+ const baseGenerator = config.codeGenerator;
1963
+ const codeGenerator = extensionGenerators.size > 0 ? {
1964
+ generate(node) {
1965
+ const generate = extensionGenerators.get(node.action);
1966
+ if (generate) return generate(node);
1967
+ if (!baseGenerator) {
1968
+ throw new Error(`No code generator for action "${node.action}"`);
1969
+ }
1970
+ return baseGenerator.generate(node);
1971
+ }
1972
+ } : baseGenerator;
1973
+ return {
1974
+ ...config,
1975
+ schemas,
1976
+ languages,
1977
+ ...codeGenerator !== void 0 && { codeGenerator }
1978
+ };
1979
+ }
1773
1980
  function createMultilingualDSL(config) {
1774
- const dictionary = config.dictionary ?? createDefaultDictionary(config);
1775
- const profileProvider = config.profileProvider ?? createDefaultProfileProvider(config);
1981
+ const effectiveConfig = applyExtensions(config);
1982
+ const extensionActions = new Set((config.extensions ?? []).map((e) => e.schema.action));
1983
+ const dictionary = effectiveConfig.dictionary ?? createDefaultDictionary(effectiveConfig);
1984
+ const profileProvider = effectiveConfig.profileProvider ?? createDefaultProfileProvider(effectiveConfig);
1776
1985
  const transformer = new GrammarTransformer({
1777
1986
  dictionary,
1778
1987
  profileProvider
1779
1988
  });
1780
- const registry = new DSLRegistry(config);
1781
- return new MultilingualDSLImpl(config, registry, transformer);
1989
+ const registry = new DSLRegistry(effectiveConfig, extensionActions);
1990
+ return new MultilingualDSLImpl(effectiveConfig, registry, transformer, profileProvider);
1782
1991
  }
1783
1992
 
1784
1993
  // src/prompts/prompt-generator.ts
@@ -2740,6 +2949,11 @@ var DomainRegistry = class {
2740
2949
  rendered = typeof renderer === "function" ? renderer(node, to) : renderer.render(node, to);
2741
2950
  } catch {
2742
2951
  }
2952
+ } else if (dsl.render) {
2953
+ try {
2954
+ rendered = dsl.render(node, to);
2955
+ } catch {
2956
+ }
2743
2957
  }
2744
2958
  const roles = {};
2745
2959
  for (const [key, value] of node.roles) {