@lokascript/framework 2.1.0 → 2.2.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 (52) hide show
  1. package/dist/api/index.js +111 -24
  2. package/dist/api/index.js.map +1 -1
  3. package/dist/core/index.js +133 -0
  4. package/dist/core/index.js.map +1 -1
  5. package/dist/core/pattern-matching/index.js.map +1 -1
  6. package/dist/core/tokenization/index.js +133 -0
  7. package/dist/core/tokenization/index.js.map +1 -1
  8. package/dist/core/tokenization/morphology/base-normalizer.d.ts +94 -0
  9. package/dist/core/tokenization/morphology/base-normalizer.d.ts.map +1 -0
  10. package/dist/core/tokenization/morphology/index.d.ts +3 -1
  11. package/dist/core/tokenization/morphology/index.d.ts.map +1 -1
  12. package/dist/core/types.d.ts +3 -2
  13. package/dist/core/types.d.ts.map +1 -1
  14. package/dist/core/types.js.map +1 -1
  15. package/dist/generation/index.js.map +1 -1
  16. package/dist/index.cjs +564 -46
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +556 -46
  21. package/dist/index.js.map +1 -1
  22. package/dist/ir/explicit-parser.d.ts +62 -7
  23. package/dist/ir/explicit-parser.d.ts.map +1 -1
  24. package/dist/ir/explicit-renderer.d.ts +18 -1
  25. package/dist/ir/explicit-renderer.d.ts.map +1 -1
  26. package/dist/ir/index.d.ts +4 -2
  27. package/dist/ir/index.d.ts.map +1 -1
  28. package/dist/ir/index.js +423 -46
  29. package/dist/ir/index.js.map +1 -1
  30. package/dist/ir/protocol-json.d.ts.map +1 -1
  31. package/dist/ir/to-runtime-ast.d.ts +53 -0
  32. package/dist/ir/to-runtime-ast.d.ts.map +1 -0
  33. package/dist/ir/types.d.ts +14 -4
  34. package/dist/ir/types.d.ts.map +1 -1
  35. package/dist/parsing/index.js.map +1 -1
  36. package/dist/testing/index.js +8671 -8261
  37. package/dist/testing/index.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/core/tokenization/morphology/base-normalizer.ts +249 -0
  40. package/src/core/tokenization/morphology/index.ts +3 -1
  41. package/src/core/types.ts +4 -2
  42. package/src/index.ts +7 -0
  43. package/src/ir/conformance.test.ts +120 -0
  44. package/src/ir/explicit-parser.test.ts +307 -1
  45. package/src/ir/explicit-parser.ts +403 -29
  46. package/src/ir/explicit-renderer.test.ts +87 -2
  47. package/src/ir/explicit-renderer.ts +57 -5
  48. package/src/ir/index.ts +13 -2
  49. package/src/ir/protocol-json.test.ts +4 -2
  50. package/src/ir/protocol-json.ts +30 -24
  51. package/src/ir/to-runtime-ast.ts +203 -0
  52. package/src/ir/types.ts +14 -4
package/dist/api/index.js CHANGED
@@ -1696,6 +1696,8 @@ function isNestedCommand(value) {
1696
1696
  }
1697
1697
  function parseExplicit(input, options = {}) {
1698
1698
  const trimmed = input.trim();
1699
+ const collect = options.collectDiagnostics === true;
1700
+ const diagnostics = [];
1699
1701
  if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) {
1700
1702
  throw new Error("Explicit syntax must be wrapped in brackets: [command role:value ...]");
1701
1703
  }
@@ -1718,10 +1720,30 @@ function parseExplicit(input, options = {}) {
1718
1720
  const enabled = token.startsWith("+");
1719
1721
  const flagName = token.slice(1);
1720
1722
  if (!flagName) {
1723
+ if (collect) {
1724
+ diagnostics.push({
1725
+ severity: "error",
1726
+ code: "EMPTY_FLAG",
1727
+ message: `Empty flag name: "${token}"`,
1728
+ source: "schema"
1729
+ });
1730
+ continue;
1731
+ }
1721
1732
  throw new Error(`Empty flag name: "${token}"`);
1722
1733
  }
1723
1734
  if (validRoleNames && !validRoleNames.has(flagName)) {
1724
1735
  const roleList = [...validRoleNames].join(", ");
1736
+ if (collect) {
1737
+ diagnostics.push({
1738
+ severity: "error",
1739
+ code: "UNKNOWN_FLAG",
1740
+ message: `Unknown flag "${flagName}" for command "${command}"`,
1741
+ source: "schema",
1742
+ suggestions: [...validRoleNames].map((r) => `+${r}`)
1743
+ });
1744
+ roles.set(flagName, createFlag(flagName, enabled));
1745
+ continue;
1746
+ }
1725
1747
  throw new Error(
1726
1748
  `Unknown flag "${flagName}" for command "${command}". Valid roles: ${roleList}`
1727
1749
  );
@@ -1731,14 +1753,33 @@ function parseExplicit(input, options = {}) {
1731
1753
  }
1732
1754
  const colonIndex = token.indexOf(":");
1733
1755
  if (colonIndex === -1) {
1756
+ if (collect) {
1757
+ diagnostics.push({
1758
+ severity: "error",
1759
+ code: "INVALID_ROLE_FORMAT",
1760
+ message: `Invalid role format: "${token}". Expected role:value or +flag`,
1761
+ source: "schema"
1762
+ });
1763
+ continue;
1764
+ }
1734
1765
  throw new Error(`Invalid role format: "${token}". Expected role:value or +flag`);
1735
1766
  }
1736
1767
  const roleName = token.slice(0, colonIndex);
1737
1768
  if (validRoleNames && !STRUCTURAL_ROLES.has(roleName) && !validRoleNames.has(roleName)) {
1738
1769
  const roleList = [...validRoleNames].join(", ");
1739
- throw new Error(
1740
- `Unknown role "${roleName}" for command "${command}". Valid roles: ${roleList}`
1741
- );
1770
+ if (collect) {
1771
+ diagnostics.push({
1772
+ severity: "error",
1773
+ code: "UNKNOWN_ROLE",
1774
+ message: `Unknown role "${roleName}" for command "${command}"`,
1775
+ source: "schema",
1776
+ suggestions: [...validRoleNames]
1777
+ });
1778
+ } else {
1779
+ throw new Error(
1780
+ `Unknown role "${roleName}" for command "${command}". Valid roles: ${roleList}`
1781
+ );
1782
+ }
1742
1783
  }
1743
1784
  const role = roleName;
1744
1785
  const valueStr = token.slice(colonIndex + 1);
@@ -1754,37 +1795,67 @@ function parseExplicit(input, options = {}) {
1754
1795
  if (schema && command !== "on") {
1755
1796
  for (const roleSpec of schema.roles) {
1756
1797
  if (roleSpec.required && !roles.has(roleSpec.role) && !roleSpec.default) {
1757
- throw new Error(
1758
- `Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`
1759
- );
1798
+ if (collect) {
1799
+ diagnostics.push({
1800
+ severity: "error",
1801
+ code: "MISSING_REQUIRED_ROLE",
1802
+ message: `Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`,
1803
+ source: "schema"
1804
+ });
1805
+ } else {
1806
+ throw new Error(
1807
+ `Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`
1808
+ );
1809
+ }
1760
1810
  }
1761
1811
  }
1762
1812
  }
1813
+ const attachDiagnostics = (node) => {
1814
+ if (diagnostics.length === 0) return node;
1815
+ return { ...node, diagnostics };
1816
+ };
1763
1817
  switch (command) {
1764
1818
  case "on": {
1765
1819
  const eventValue = roles.get("event");
1766
1820
  if (!eventValue) {
1821
+ if (collect) {
1822
+ diagnostics.push({
1823
+ severity: "error",
1824
+ code: "MISSING_EVENT_ROLE",
1825
+ message: "Event handler requires event role: [on event:click ...]",
1826
+ source: "schema"
1827
+ });
1828
+ return attachDiagnostics(
1829
+ createEventHandlerNode("on", roles, [], {
1830
+ sourceLanguage: "explicit"
1831
+ })
1832
+ );
1833
+ }
1767
1834
  throw new Error("Event handler requires event role: [on event:click ...]");
1768
1835
  }
1769
1836
  const body = extractStructuralBody(roles, "body", options);
1770
1837
  roles.delete("body");
1771
- return createEventHandlerNode("on", roles, body, {
1772
- sourceLanguage: "explicit"
1773
- });
1838
+ return attachDiagnostics(
1839
+ createEventHandlerNode("on", roles, body, {
1840
+ sourceLanguage: "explicit"
1841
+ })
1842
+ );
1774
1843
  }
1775
1844
  case "if": {
1776
1845
  const thenBranch = extractStructuralBody(roles, "then", options);
1777
1846
  const elseBranch = extractStructuralBody(roles, "else", options);
1778
1847
  roles.delete("then");
1779
1848
  roles.delete("else");
1780
- return createConditionalNode(
1781
- command,
1782
- roles,
1783
- thenBranch,
1784
- elseBranch.length > 0 ? elseBranch : void 0,
1785
- {
1786
- sourceLanguage: "explicit"
1787
- }
1849
+ return attachDiagnostics(
1850
+ createConditionalNode(
1851
+ command,
1852
+ roles,
1853
+ thenBranch,
1854
+ elseBranch.length > 0 ? elseBranch : void 0,
1855
+ {
1856
+ sourceLanguage: "explicit"
1857
+ }
1858
+ )
1788
1859
  );
1789
1860
  }
1790
1861
  case "repeat": {
@@ -1810,14 +1881,18 @@ function parseExplicit(input, options = {}) {
1810
1881
  }
1811
1882
  const loopVariable = variableValue?.type === "literal" && typeof variableValue.value === "string" ? variableValue.value : variableValue?.type === "expression" ? variableValue.raw : void 0;
1812
1883
  const indexVariable = indexVariableValue?.type === "literal" && typeof indexVariableValue.value === "string" ? indexVariableValue.value : void 0;
1813
- return createLoopNode(command, roles, variant, loopBody, loopVariable, indexVariable, {
1814
- sourceLanguage: "explicit"
1815
- });
1884
+ return attachDiagnostics(
1885
+ createLoopNode(command, roles, variant, loopBody, loopVariable, indexVariable, {
1886
+ sourceLanguage: "explicit"
1887
+ })
1888
+ );
1816
1889
  }
1817
1890
  default:
1818
- return createCommandNode(command, roles, {
1819
- sourceLanguage: "explicit"
1820
- });
1891
+ return attachDiagnostics(
1892
+ createCommandNode(command, roles, {
1893
+ sourceLanguage: "explicit"
1894
+ })
1895
+ );
1821
1896
  }
1822
1897
  }
1823
1898
  function isExplicitSyntax(input) {
@@ -1925,11 +2000,23 @@ function findMatchingBracket(str, start) {
1925
2000
 
1926
2001
  // src/ir/explicit-renderer.ts
1927
2002
  function renderExplicit(node) {
2003
+ let result;
1928
2004
  if (node.kind === "compound") {
1929
2005
  const compoundNode = node;
1930
2006
  const renderedStatements = compoundNode.statements.map((stmt) => renderExplicit(stmt));
1931
- return renderedStatements.join(` ${compoundNode.chainType} `);
2007
+ result = renderedStatements.join(` ${compoundNode.chainType} `);
2008
+ } else {
2009
+ result = renderBracketCommand(node);
2010
+ }
2011
+ if (node.annotations && node.annotations.length > 0) {
2012
+ const annParts = node.annotations.map(
2013
+ (ann) => ann.value !== void 0 ? `@${ann.name}(${ann.value})` : `@${ann.name}`
2014
+ );
2015
+ return annParts.join(" ") + " " + result;
1932
2016
  }
2017
+ return result;
2018
+ }
2019
+ function renderBracketCommand(node) {
1933
2020
  const parts = [node.action];
1934
2021
  for (const [role, value] of node.roles) {
1935
2022
  if (value.type === "flag") {