@lokascript/framework 2.1.0 → 2.2.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 (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/index.js CHANGED
@@ -2048,6 +2048,8 @@ function isNestedCommand(value) {
2048
2048
  }
2049
2049
  function parseExplicit(input, options = {}) {
2050
2050
  const trimmed = input.trim();
2051
+ const collect = options.collectDiagnostics === true;
2052
+ const diagnostics = [];
2051
2053
  if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) {
2052
2054
  throw new Error("Explicit syntax must be wrapped in brackets: [command role:value ...]");
2053
2055
  }
@@ -2070,10 +2072,30 @@ function parseExplicit(input, options = {}) {
2070
2072
  const enabled = token.startsWith("+");
2071
2073
  const flagName = token.slice(1);
2072
2074
  if (!flagName) {
2075
+ if (collect) {
2076
+ diagnostics.push({
2077
+ severity: "error",
2078
+ code: "EMPTY_FLAG",
2079
+ message: `Empty flag name: "${token}"`,
2080
+ source: "schema"
2081
+ });
2082
+ continue;
2083
+ }
2073
2084
  throw new Error(`Empty flag name: "${token}"`);
2074
2085
  }
2075
2086
  if (validRoleNames && !validRoleNames.has(flagName)) {
2076
2087
  const roleList = [...validRoleNames].join(", ");
2088
+ if (collect) {
2089
+ diagnostics.push({
2090
+ severity: "error",
2091
+ code: "UNKNOWN_FLAG",
2092
+ message: `Unknown flag "${flagName}" for command "${command}"`,
2093
+ source: "schema",
2094
+ suggestions: [...validRoleNames].map((r) => `+${r}`)
2095
+ });
2096
+ roles.set(flagName, createFlag(flagName, enabled));
2097
+ continue;
2098
+ }
2077
2099
  throw new Error(
2078
2100
  `Unknown flag "${flagName}" for command "${command}". Valid roles: ${roleList}`
2079
2101
  );
@@ -2083,14 +2105,33 @@ function parseExplicit(input, options = {}) {
2083
2105
  }
2084
2106
  const colonIndex = token.indexOf(":");
2085
2107
  if (colonIndex === -1) {
2108
+ if (collect) {
2109
+ diagnostics.push({
2110
+ severity: "error",
2111
+ code: "INVALID_ROLE_FORMAT",
2112
+ message: `Invalid role format: "${token}". Expected role:value or +flag`,
2113
+ source: "schema"
2114
+ });
2115
+ continue;
2116
+ }
2086
2117
  throw new Error(`Invalid role format: "${token}". Expected role:value or +flag`);
2087
2118
  }
2088
2119
  const roleName = token.slice(0, colonIndex);
2089
2120
  if (validRoleNames && !STRUCTURAL_ROLES.has(roleName) && !validRoleNames.has(roleName)) {
2090
2121
  const roleList = [...validRoleNames].join(", ");
2091
- throw new Error(
2092
- `Unknown role "${roleName}" for command "${command}". Valid roles: ${roleList}`
2093
- );
2122
+ if (collect) {
2123
+ diagnostics.push({
2124
+ severity: "error",
2125
+ code: "UNKNOWN_ROLE",
2126
+ message: `Unknown role "${roleName}" for command "${command}"`,
2127
+ source: "schema",
2128
+ suggestions: [...validRoleNames]
2129
+ });
2130
+ } else {
2131
+ throw new Error(
2132
+ `Unknown role "${roleName}" for command "${command}". Valid roles: ${roleList}`
2133
+ );
2134
+ }
2094
2135
  }
2095
2136
  const role = roleName;
2096
2137
  const valueStr = token.slice(colonIndex + 1);
@@ -2106,37 +2147,67 @@ function parseExplicit(input, options = {}) {
2106
2147
  if (schema && command !== "on") {
2107
2148
  for (const roleSpec of schema.roles) {
2108
2149
  if (roleSpec.required && !roles.has(roleSpec.role) && !roleSpec.default) {
2109
- throw new Error(
2110
- `Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`
2111
- );
2150
+ if (collect) {
2151
+ diagnostics.push({
2152
+ severity: "error",
2153
+ code: "MISSING_REQUIRED_ROLE",
2154
+ message: `Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`,
2155
+ source: "schema"
2156
+ });
2157
+ } else {
2158
+ throw new Error(
2159
+ `Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`
2160
+ );
2161
+ }
2112
2162
  }
2113
2163
  }
2114
2164
  }
2165
+ const attachDiagnostics = (node) => {
2166
+ if (diagnostics.length === 0) return node;
2167
+ return { ...node, diagnostics };
2168
+ };
2115
2169
  switch (command) {
2116
2170
  case "on": {
2117
2171
  const eventValue = roles.get("event");
2118
2172
  if (!eventValue) {
2173
+ if (collect) {
2174
+ diagnostics.push({
2175
+ severity: "error",
2176
+ code: "MISSING_EVENT_ROLE",
2177
+ message: "Event handler requires event role: [on event:click ...]",
2178
+ source: "schema"
2179
+ });
2180
+ return attachDiagnostics(
2181
+ createEventHandlerNode("on", roles, [], {
2182
+ sourceLanguage: "explicit"
2183
+ })
2184
+ );
2185
+ }
2119
2186
  throw new Error("Event handler requires event role: [on event:click ...]");
2120
2187
  }
2121
2188
  const body = extractStructuralBody(roles, "body", options);
2122
2189
  roles.delete("body");
2123
- return createEventHandlerNode("on", roles, body, {
2124
- sourceLanguage: "explicit"
2125
- });
2190
+ return attachDiagnostics(
2191
+ createEventHandlerNode("on", roles, body, {
2192
+ sourceLanguage: "explicit"
2193
+ })
2194
+ );
2126
2195
  }
2127
2196
  case "if": {
2128
2197
  const thenBranch = extractStructuralBody(roles, "then", options);
2129
2198
  const elseBranch = extractStructuralBody(roles, "else", options);
2130
2199
  roles.delete("then");
2131
2200
  roles.delete("else");
2132
- return createConditionalNode(
2133
- command,
2134
- roles,
2135
- thenBranch,
2136
- elseBranch.length > 0 ? elseBranch : void 0,
2137
- {
2138
- sourceLanguage: "explicit"
2139
- }
2201
+ return attachDiagnostics(
2202
+ createConditionalNode(
2203
+ command,
2204
+ roles,
2205
+ thenBranch,
2206
+ elseBranch.length > 0 ? elseBranch : void 0,
2207
+ {
2208
+ sourceLanguage: "explicit"
2209
+ }
2210
+ )
2140
2211
  );
2141
2212
  }
2142
2213
  case "repeat": {
@@ -2162,20 +2233,182 @@ function parseExplicit(input, options = {}) {
2162
2233
  }
2163
2234
  const loopVariable = variableValue?.type === "literal" && typeof variableValue.value === "string" ? variableValue.value : variableValue?.type === "expression" ? variableValue.raw : void 0;
2164
2235
  const indexVariable = indexVariableValue?.type === "literal" && typeof indexVariableValue.value === "string" ? indexVariableValue.value : void 0;
2165
- return createLoopNode(command, roles, variant, loopBody, loopVariable, indexVariable, {
2166
- sourceLanguage: "explicit"
2167
- });
2236
+ return attachDiagnostics(
2237
+ createLoopNode(command, roles, variant, loopBody, loopVariable, indexVariable, {
2238
+ sourceLanguage: "explicit"
2239
+ })
2240
+ );
2168
2241
  }
2169
2242
  default:
2170
- return createCommandNode(command, roles, {
2171
- sourceLanguage: "explicit"
2172
- });
2243
+ return attachDiagnostics(
2244
+ createCommandNode(command, roles, {
2245
+ sourceLanguage: "explicit"
2246
+ })
2247
+ );
2173
2248
  }
2174
2249
  }
2175
2250
  function isExplicitSyntax(input) {
2176
2251
  const trimmed = input.trim();
2177
2252
  return trimmed.startsWith("[") && trimmed.endsWith("]");
2178
2253
  }
2254
+ var CHAIN_KEYWORDS = /* @__PURE__ */ new Set(["then", "and", "async", "sequential"]);
2255
+ function splitCompoundSegments(input) {
2256
+ const segments = [];
2257
+ let chainType = null;
2258
+ let current = "";
2259
+ let bracketDepth = 0;
2260
+ let inString = false;
2261
+ let stringChar = "";
2262
+ for (let i = 0; i < input.length; i++) {
2263
+ const char = input[i];
2264
+ if (inString) {
2265
+ current += char;
2266
+ if (char === stringChar && input[i - 1] !== "\\") {
2267
+ inString = false;
2268
+ }
2269
+ continue;
2270
+ }
2271
+ if (char === '"' || char === "'") {
2272
+ inString = true;
2273
+ stringChar = char;
2274
+ current += char;
2275
+ continue;
2276
+ }
2277
+ if (char === "[") {
2278
+ bracketDepth++;
2279
+ current += char;
2280
+ continue;
2281
+ }
2282
+ if (char === "]") {
2283
+ bracketDepth--;
2284
+ current += char;
2285
+ if (bracketDepth === 0) {
2286
+ const rest = input.slice(i + 1);
2287
+ const pipeMatch = rest.match(/^\s*\|>\s*/);
2288
+ if (pipeMatch) {
2289
+ segments.push(current.trim());
2290
+ current = "";
2291
+ chainType ?? (chainType = "pipe");
2292
+ i += pipeMatch[0].length;
2293
+ continue;
2294
+ }
2295
+ const keywordMatch = rest.match(/^\s+(then|and|async|sequential)\s+/);
2296
+ if (keywordMatch && CHAIN_KEYWORDS.has(keywordMatch[1])) {
2297
+ segments.push(current.trim());
2298
+ current = "";
2299
+ chainType ?? (chainType = keywordMatch[1]);
2300
+ i += keywordMatch[0].length;
2301
+ continue;
2302
+ }
2303
+ }
2304
+ continue;
2305
+ }
2306
+ current += char;
2307
+ }
2308
+ if (current.trim()) {
2309
+ segments.push(current.trim());
2310
+ }
2311
+ return { segments, chainType };
2312
+ }
2313
+ function parseCompound(input, options) {
2314
+ const trimmed = input.trim();
2315
+ const { annotations, remainder } = extractAnnotations(trimmed);
2316
+ const { segments, chainType } = splitCompoundSegments(remainder);
2317
+ let node;
2318
+ if (segments.length <= 1) {
2319
+ node = parseExplicit(segments[0] || remainder, options);
2320
+ } else {
2321
+ const statements = segments.map((seg) => parseExplicit(seg, options));
2322
+ node = createCompoundNode(statements, chainType ?? "sequential");
2323
+ }
2324
+ if (annotations.length > 0) {
2325
+ return { ...node, annotations };
2326
+ }
2327
+ return node;
2328
+ }
2329
+ function extractAnnotations(input) {
2330
+ const annotations = [];
2331
+ let pos = 0;
2332
+ while (pos < input.length) {
2333
+ while (pos < input.length && (input[pos] === " " || input[pos] === " ")) {
2334
+ pos++;
2335
+ }
2336
+ if (pos >= input.length || input[pos] !== "@") {
2337
+ break;
2338
+ }
2339
+ pos++;
2340
+ const nameStart = pos;
2341
+ while (pos < input.length && /[A-Za-z0-9\-_]/.test(input[pos])) {
2342
+ pos++;
2343
+ }
2344
+ const name = input.slice(nameStart, pos);
2345
+ if (!name) break;
2346
+ let value;
2347
+ if (pos < input.length && input[pos] === "(") {
2348
+ pos++;
2349
+ const valueStart = pos;
2350
+ if (pos < input.length && (input[pos] === '"' || input[pos] === "'")) {
2351
+ const quote = input[pos];
2352
+ pos++;
2353
+ while (pos < input.length && input[pos] !== quote) {
2354
+ if (input[pos] === "\\") pos++;
2355
+ pos++;
2356
+ }
2357
+ pos++;
2358
+ value = input.slice(valueStart + 1, pos - 1);
2359
+ } else {
2360
+ while (pos < input.length && input[pos] !== ")") {
2361
+ pos++;
2362
+ }
2363
+ value = input.slice(valueStart, pos);
2364
+ }
2365
+ if (pos < input.length && input[pos] === ")") {
2366
+ pos++;
2367
+ }
2368
+ }
2369
+ annotations.push(value !== void 0 ? { name, value } : { name });
2370
+ }
2371
+ return { annotations, remainder: input.slice(pos).trim() };
2372
+ }
2373
+ var VERSION_HEADER_RE = /^#!lse\s+(\d+\.\d+(?:\.\d+)?)\s*$/;
2374
+ function parseDocument(input, options) {
2375
+ const lines = input.split("\n");
2376
+ let lseVersion = "1.0";
2377
+ const nodes = [];
2378
+ let startLine = 0;
2379
+ for (let i = 0; i < lines.length; i++) {
2380
+ const trimmed = lines[i].trim();
2381
+ if (!trimmed) continue;
2382
+ const versionMatch = trimmed.match(VERSION_HEADER_RE);
2383
+ if (versionMatch) {
2384
+ lseVersion = versionMatch[1];
2385
+ startLine = i + 1;
2386
+ }
2387
+ break;
2388
+ }
2389
+ for (let i = startLine; i < lines.length; i++) {
2390
+ const trimmed = lines[i].trim();
2391
+ if (!trimmed) continue;
2392
+ if (trimmed.startsWith("//") || trimmed.startsWith("#") && !trimmed.startsWith("#!")) {
2393
+ continue;
2394
+ }
2395
+ nodes.push(parseCompound(trimmed, options));
2396
+ }
2397
+ return { lseVersion, nodes };
2398
+ }
2399
+ function isCompoundSyntax(input) {
2400
+ const { segments } = splitCompoundSegments(input.trim());
2401
+ return segments.length > 1;
2402
+ }
2403
+ function isDocumentSyntax(input) {
2404
+ const trimmed = input.trim();
2405
+ if (trimmed.includes("\n")) return true;
2406
+ if (VERSION_HEADER_RE.test(trimmed)) return true;
2407
+ if (trimmed.startsWith("//") || trimmed.startsWith("#") && !trimmed.startsWith("#!")) {
2408
+ return true;
2409
+ }
2410
+ return false;
2411
+ }
2179
2412
  function extractStructuralBody(roles, roleName, options) {
2180
2413
  const value = roles.get(roleName);
2181
2414
  if (!value) return [];
@@ -2277,11 +2510,33 @@ function findMatchingBracket(str, start) {
2277
2510
 
2278
2511
  // src/ir/explicit-renderer.ts
2279
2512
  function renderExplicit(node) {
2513
+ let result;
2280
2514
  if (node.kind === "compound") {
2281
2515
  const compoundNode = node;
2282
2516
  const renderedStatements = compoundNode.statements.map((stmt) => renderExplicit(stmt));
2283
- return renderedStatements.join(` ${compoundNode.chainType} `);
2517
+ result = renderedStatements.join(` ${compoundNode.chainType} `);
2518
+ } else {
2519
+ result = renderBracketCommand(node);
2284
2520
  }
2521
+ if (node.annotations && node.annotations.length > 0) {
2522
+ const annParts = node.annotations.map(
2523
+ (ann) => ann.value !== void 0 ? `@${ann.name}(${ann.value})` : `@${ann.name}`
2524
+ );
2525
+ return annParts.join(" ") + " " + result;
2526
+ }
2527
+ return result;
2528
+ }
2529
+ function renderDocument(envelope) {
2530
+ const lines = [];
2531
+ if (envelope.lseVersion && envelope.lseVersion !== "1.0") {
2532
+ lines.push(`#!lse ${envelope.lseVersion}`);
2533
+ }
2534
+ for (const node of envelope.nodes) {
2535
+ lines.push(renderExplicit(node));
2536
+ }
2537
+ return lines.join("\n");
2538
+ }
2539
+ function renderBracketCommand(node) {
2285
2540
  const parts = [node.action];
2286
2541
  for (const [role, value] of node.roles) {
2287
2542
  if (value.type === "flag") {
@@ -2619,12 +2874,16 @@ function toProtocolJSON(node) {
2619
2874
  );
2620
2875
  }
2621
2876
  if (node.diagnostics && node.diagnostics.length > 0) {
2622
- result.diagnostics = node.diagnostics.map((d) => ({
2623
- level: d.level,
2624
- role: d.role,
2625
- message: d.message,
2626
- code: d.code
2627
- }));
2877
+ result.diagnostics = node.diagnostics.map((d) => {
2878
+ const json = {
2879
+ level: d.severity,
2880
+ message: d.message
2881
+ };
2882
+ if (d.code) json.code = d.code;
2883
+ if (d.source) json.source = d.source;
2884
+ if (d.suggestions && d.suggestions.length > 0) json.suggestions = [...d.suggestions];
2885
+ return json;
2886
+ });
2628
2887
  }
2629
2888
  return result;
2630
2889
  }
@@ -2672,10 +2931,11 @@ function fromProtocolJSON(json) {
2672
2931
  (a) => a.value !== void 0 ? { name: a.name, value: a.value } : { name: a.name }
2673
2932
  );
2674
2933
  const diagnostics = json.diagnostics?.map((d) => ({
2675
- level: d.level,
2676
- role: d.role,
2934
+ severity: d.level,
2677
2935
  message: d.message,
2678
- code: d.code
2936
+ ...d.code ? { code: d.code } : {},
2937
+ ...d.source ? { source: d.source } : {},
2938
+ ...d.suggestions && d.suggestions.length > 0 ? { suggestions: d.suggestions } : {}
2679
2939
  }));
2680
2940
  if (json.trigger) {
2681
2941
  const eventName = json.trigger.event;
@@ -2816,7 +3076,7 @@ var VALID_VALUE_TYPES2 = /* @__PURE__ */ new Set([
2816
3076
  ]);
2817
3077
  var VALID_CHAIN_TYPES = /* @__PURE__ */ new Set(["then", "and", "async", "sequential", "pipe"]);
2818
3078
  var VALID_ASYNC_VARIANTS = /* @__PURE__ */ new Set(["all", "race"]);
2819
- var VALID_DIAGNOSTIC_LEVELS = /* @__PURE__ */ new Set(["error", "warning"]);
3079
+ var VALID_DIAGNOSTIC_LEVELS = /* @__PURE__ */ new Set(["error", "warning", "info"]);
2820
3080
  function validateProtocolJSON(json) {
2821
3081
  const diagnostics = [];
2822
3082
  if (typeof json !== "object" || json === null) {
@@ -2965,14 +3225,7 @@ function validateProtocolJSON(json) {
2965
3225
  diagnostics.push({
2966
3226
  severity: "error",
2967
3227
  code: "INVALID_DIAGNOSTIC_LEVEL",
2968
- message: `diagnostics[${i}].level must be "error" or "warning"`
2969
- });
2970
- }
2971
- if (typeof d.role !== "string") {
2972
- diagnostics.push({
2973
- severity: "error",
2974
- code: "MISSING_DIAGNOSTIC_ROLE",
2975
- message: `diagnostics[${i}] missing required field: role`
3228
+ message: `diagnostics[${i}].level must be "error", "warning", or "info"`
2976
3229
  });
2977
3230
  }
2978
3231
  if (typeof d.message !== "string") {
@@ -2982,11 +3235,11 @@ function validateProtocolJSON(json) {
2982
3235
  message: `diagnostics[${i}] missing required field: message`
2983
3236
  });
2984
3237
  }
2985
- if (typeof d.code !== "string") {
3238
+ if ("code" in d && typeof d.code !== "string") {
2986
3239
  diagnostics.push({
2987
- severity: "error",
2988
- code: "MISSING_DIAGNOSTIC_CODE",
2989
- message: `diagnostics[${i}] missing required field: code`
3240
+ severity: "warning",
3241
+ code: "INVALID_DIAGNOSTIC_CODE",
3242
+ message: `diagnostics[${i}].code must be a string if present`
2990
3243
  });
2991
3244
  }
2992
3245
  }
@@ -3422,6 +3675,123 @@ function bridgeEventModifiers(mods) {
3422
3675
  };
3423
3676
  }
3424
3677
 
3678
+ // src/ir/to-runtime-ast.ts
3679
+ function semanticNodeToRuntimeAST(node) {
3680
+ switch (node.kind) {
3681
+ case "event-handler":
3682
+ return convertEventHandler(node);
3683
+ case "conditional":
3684
+ return convertConditional(node);
3685
+ case "loop":
3686
+ return convertLoop(node);
3687
+ case "compound": {
3688
+ const compound = node;
3689
+ const commands = (compound.statements ?? []).map(semanticNodeToRuntimeAST);
3690
+ return {
3691
+ type: "CommandSequence",
3692
+ commands
3693
+ };
3694
+ }
3695
+ case "command":
3696
+ default:
3697
+ return convertCommand2(node);
3698
+ }
3699
+ }
3700
+ function convertEventHandler(eh) {
3701
+ const eventValue = eh.roles.get("event");
3702
+ const eventName = eventValue && "value" in eventValue ? String(eventValue.value) : "click";
3703
+ const modifiers = {};
3704
+ if (eh.eventModifiers) {
3705
+ if (eh.eventModifiers.once) modifiers.once = true;
3706
+ if (eh.eventModifiers.debounce) modifiers.debounce = eh.eventModifiers.debounce;
3707
+ if (eh.eventModifiers.throttle) modifiers.throttle = eh.eventModifiers.throttle;
3708
+ if (eh.eventModifiers.queue) modifiers.queue = eh.eventModifiers.queue;
3709
+ }
3710
+ return {
3711
+ type: "event",
3712
+ event: eventName,
3713
+ modifiers,
3714
+ body: (eh.body ?? []).map(semanticNodeToRuntimeAST)
3715
+ };
3716
+ }
3717
+ function convertCommand2(node) {
3718
+ const roles = {};
3719
+ const args = [];
3720
+ for (const [roleName, value] of node.roles) {
3721
+ const astValue = semanticValueToAST(value);
3722
+ roles[roleName] = astValue;
3723
+ args.push(astValue);
3724
+ }
3725
+ return {
3726
+ type: "command",
3727
+ name: node.action,
3728
+ args,
3729
+ roles
3730
+ };
3731
+ }
3732
+ function convertConditional(node) {
3733
+ const conditionValue = node.roles.get("condition");
3734
+ return {
3735
+ type: "command",
3736
+ name: "if",
3737
+ args: conditionValue ? [semanticValueToAST(conditionValue)] : [],
3738
+ condition: conditionValue ? semanticValueToAST(conditionValue) : void 0,
3739
+ thenBranch: (node.thenBranch ?? []).map(semanticNodeToRuntimeAST),
3740
+ elseBranch: (node.elseBranch ?? []).map(semanticNodeToRuntimeAST)
3741
+ };
3742
+ }
3743
+ function convertLoop(node) {
3744
+ const countValue = node.roles.get("patient") || node.roles.get("count");
3745
+ return {
3746
+ type: "command",
3747
+ name: "repeat",
3748
+ args: countValue ? [semanticValueToAST(countValue)] : [],
3749
+ loopVariant: node.loopVariant ?? "forever",
3750
+ body: (node.body ?? []).map(semanticNodeToRuntimeAST),
3751
+ loopVariable: node.loopVariable,
3752
+ indexVariable: node.indexVariable
3753
+ };
3754
+ }
3755
+ function semanticValueToAST(value) {
3756
+ switch (value.type) {
3757
+ case "selector":
3758
+ return { type: "selector", value: value.value };
3759
+ case "reference":
3760
+ return { type: "identifier", value: value.value };
3761
+ case "literal": {
3762
+ const lit = value;
3763
+ if (lit.dataType === "duration") {
3764
+ const str = String(lit.value);
3765
+ const match = /^(\d+(?:\.\d+)?)(ms|s)$/.exec(str);
3766
+ if (match) {
3767
+ const ms = match[2] === "s" ? parseFloat(match[1]) * 1e3 : parseFloat(match[1]);
3768
+ return { type: "literal", value: ms };
3769
+ }
3770
+ }
3771
+ return { type: "literal", value: lit.value };
3772
+ }
3773
+ case "property-path": {
3774
+ const pp = value;
3775
+ const objectAST = semanticValueToAST(pp.object);
3776
+ return {
3777
+ type: "member",
3778
+ object: objectAST,
3779
+ property: pp.property
3780
+ };
3781
+ }
3782
+ case "expression": {
3783
+ const expr = value;
3784
+ return { type: "expression", value: expr.raw };
3785
+ }
3786
+ case "flag": {
3787
+ const flag = value;
3788
+ return { type: "literal", value: flag.enabled };
3789
+ }
3790
+ default:
3791
+ return { type: "literal", value: String(value.value ?? "") };
3792
+ }
3793
+ }
3794
+
3425
3795
  // src/api/create-dsl.ts
3426
3796
  var DSLRegistry = class {
3427
3797
  constructor(config) {
@@ -6393,6 +6763,138 @@ function normalized(stem, confidence, metadata) {
6393
6763
  return { stem, confidence };
6394
6764
  }
6395
6765
 
6766
+ // src/core/tokenization/morphology/base-normalizer.ts
6767
+ var BaseMorphologicalNormalizer = class {
6768
+ constructor(config) {
6769
+ this.language = config.language;
6770
+ this.config = {
6771
+ minWordLength: 3,
6772
+ minStemLength: 2,
6773
+ ...config
6774
+ };
6775
+ }
6776
+ /**
6777
+ * Standard normalization pipeline. Override for custom behavior.
6778
+ */
6779
+ normalize(word) {
6780
+ const lower = word.toLowerCase();
6781
+ if (this.isAlreadyNormalized(lower)) {
6782
+ return noChange(word);
6783
+ }
6784
+ if (this.config.reflexiveSuffixes) {
6785
+ const reflexive = this.tryReflexiveNormalization(lower);
6786
+ if (reflexive) return reflexive;
6787
+ }
6788
+ if (this.config.endings) {
6789
+ const conjugation = this.tryConjugationEndings(lower);
6790
+ if (conjugation) return conjugation;
6791
+ }
6792
+ if (this.config.suffixRules) {
6793
+ const suffix = this.trySuffixRules(lower);
6794
+ if (suffix) return suffix;
6795
+ }
6796
+ if (this.config.prefixRules) {
6797
+ const prefix = this.tryPrefixRules(lower);
6798
+ if (prefix) return prefix;
6799
+ }
6800
+ return noChange(word);
6801
+ }
6802
+ /**
6803
+ * Check if word is already in dictionary form (e.g., ends in -ar/-er/-ir).
6804
+ * Override for language-specific checks.
6805
+ */
6806
+ isAlreadyNormalized(word) {
6807
+ if (this.config.infinitiveEndings) {
6808
+ return this.config.infinitiveEndings.some((e) => word.endsWith(e));
6809
+ }
6810
+ return false;
6811
+ }
6812
+ /**
6813
+ * Try to strip reflexive suffixes and normalize the remainder.
6814
+ * Common in Romance languages (Spanish, Portuguese, French).
6815
+ */
6816
+ tryReflexiveNormalization(word) {
6817
+ const suffixes = this.config.reflexiveSuffixes;
6818
+ if (!suffixes) return null;
6819
+ for (const suffix of suffixes) {
6820
+ if (!word.endsWith(suffix)) continue;
6821
+ const remainder = word.slice(0, -suffix.length);
6822
+ if (this.isAlreadyNormalized(remainder)) {
6823
+ return normalized(remainder, 0.88, {
6824
+ removedSuffixes: [suffix],
6825
+ conjugationType: "reflexive"
6826
+ });
6827
+ }
6828
+ const inner = this.tryConjugationEndings(remainder) || this.trySuffixRules(remainder);
6829
+ if (inner && inner.stem !== remainder) {
6830
+ return normalized(inner.stem, inner.confidence * 0.95, {
6831
+ removedSuffixes: [suffix, ...inner.metadata?.removedSuffixes || []],
6832
+ conjugationType: "reflexive"
6833
+ });
6834
+ }
6835
+ }
6836
+ return null;
6837
+ }
6838
+ /**
6839
+ * Try conjugation endings (verb class endings like -ar/-er/-ir patterns).
6840
+ * Endings must be pre-sorted longest-first.
6841
+ */
6842
+ tryConjugationEndings(word) {
6843
+ const endings = this.config.endings;
6844
+ if (!endings) return null;
6845
+ const minStem = this.config.minStemLength ?? 2;
6846
+ for (const rule of endings) {
6847
+ if (!word.endsWith(rule.ending)) continue;
6848
+ const stemBase = word.slice(0, -rule.ending.length);
6849
+ if (stemBase.length < minStem) continue;
6850
+ const infinitive = stemBase + rule.stem;
6851
+ return normalized(infinitive, rule.confidence, {
6852
+ removedSuffixes: [rule.ending],
6853
+ conjugationType: rule.type
6854
+ });
6855
+ }
6856
+ return null;
6857
+ }
6858
+ /**
6859
+ * Try SuffixRule-style normalization.
6860
+ * Rules must be pre-sorted longest-first.
6861
+ */
6862
+ trySuffixRules(word) {
6863
+ const rules = this.config.suffixRules;
6864
+ if (!rules) return null;
6865
+ const defaultMinStem = this.config.minStemLength ?? 2;
6866
+ for (const rule of rules) {
6867
+ if (!word.endsWith(rule.pattern)) continue;
6868
+ const stem = word.slice(0, -rule.pattern.length);
6869
+ const minStem = rule.minStemLength ?? defaultMinStem;
6870
+ if (stem.length < minStem) continue;
6871
+ const result = stem + (rule.replacement || "");
6872
+ return normalized(result, rule.confidence, {
6873
+ removedSuffixes: [rule.pattern],
6874
+ ...rule.conjugationType && { conjugationType: rule.conjugationType }
6875
+ });
6876
+ }
6877
+ return null;
6878
+ }
6879
+ /**
6880
+ * Try PrefixRule-style normalization.
6881
+ */
6882
+ tryPrefixRules(word) {
6883
+ const rules = this.config.prefixRules;
6884
+ if (!rules) return null;
6885
+ for (const rule of rules) {
6886
+ if (!word.startsWith(rule.pattern)) continue;
6887
+ const remainder = word.slice(rule.pattern.length);
6888
+ const minRemaining = rule.minRemaining ?? this.config.minStemLength ?? 2;
6889
+ if (remainder.length < minRemaining) continue;
6890
+ return normalized(remainder, 1 - rule.confidencePenalty, {
6891
+ removedPrefixes: [rule.pattern]
6892
+ });
6893
+ }
6894
+ return null;
6895
+ }
6896
+ };
6897
+
6396
6898
  // src/training/jsonl-writer.ts
6397
6899
  function toJSONL(pairs) {
6398
6900
  return pairs.map((pair) => JSON.stringify(toJSONLRow(pair))).join("\n");
@@ -6789,6 +7291,7 @@ function accumulateIndented(statements, config) {
6789
7291
  }
6790
7292
  export {
6791
7293
  AOTOrchestrator,
7294
+ BaseMorphologicalNormalizer,
6792
7295
  BaseTokenizer,
6793
7296
  CrossDomainDispatcher,
6794
7297
  DEFAULT_OPERATORS,
@@ -6871,8 +7374,10 @@ export {
6871
7374
  isCSSPropertyRef,
6872
7375
  isCSSSelector,
6873
7376
  isClassName,
7377
+ isCompoundSyntax,
6874
7378
  isContextAwareExtractor,
6875
7379
  isDigit,
7380
+ isDocumentSyntax,
6876
7381
  isEnvelope,
6877
7382
  isExplicitSyntax,
6878
7383
  isIdSelector,
@@ -6896,14 +7401,19 @@ export {
6896
7401
  needsDisambiguation,
6897
7402
  noChange,
6898
7403
  normalized,
7404
+ parseCompound,
7405
+ parseDocument,
6899
7406
  parseExplicit,
6900
7407
  parseJSONL,
6901
7408
  patternMatcher,
6902
7409
  registryToAOTBackends,
7410
+ renderDocument,
6903
7411
  renderExplicit,
6904
7412
  renderExpr,
6905
7413
  reorderRoles,
6906
7414
  semanticNodeToJSON,
7415
+ semanticNodeToRuntimeAST,
7416
+ semanticValueToAST,
6907
7417
  synthesizeFromSchemas,
6908
7418
  toEnvelopeJSON,
6909
7419
  toJSONL,