@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.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AOTOrchestrator: () => AOTOrchestrator,
24
+ BaseMorphologicalNormalizer: () => BaseMorphologicalNormalizer,
24
25
  BaseTokenizer: () => BaseTokenizer,
25
26
  CrossDomainDispatcher: () => CrossDomainDispatcher,
26
27
  DEFAULT_OPERATORS: () => DEFAULT_OPERATORS,
@@ -103,8 +104,10 @@ __export(index_exports, {
103
104
  isCSSPropertyRef: () => isCSSPropertyRef,
104
105
  isCSSSelector: () => isCSSSelector,
105
106
  isClassName: () => isClassName,
107
+ isCompoundSyntax: () => isCompoundSyntax,
106
108
  isContextAwareExtractor: () => isContextAwareExtractor,
107
109
  isDigit: () => isDigit,
110
+ isDocumentSyntax: () => isDocumentSyntax,
108
111
  isEnvelope: () => isEnvelope,
109
112
  isExplicitSyntax: () => isExplicitSyntax,
110
113
  isIdSelector: () => isIdSelector,
@@ -128,14 +131,19 @@ __export(index_exports, {
128
131
  needsDisambiguation: () => needsDisambiguation,
129
132
  noChange: () => noChange,
130
133
  normalized: () => normalized,
134
+ parseCompound: () => parseCompound,
135
+ parseDocument: () => parseDocument,
131
136
  parseExplicit: () => parseExplicit,
132
137
  parseJSONL: () => parseJSONL,
133
138
  patternMatcher: () => patternMatcher,
134
139
  registryToAOTBackends: () => registryToAOTBackends,
140
+ renderDocument: () => renderDocument,
135
141
  renderExplicit: () => renderExplicit,
136
142
  renderExpr: () => renderExpr,
137
143
  reorderRoles: () => reorderRoles,
138
144
  semanticNodeToJSON: () => semanticNodeToJSON,
145
+ semanticNodeToRuntimeAST: () => semanticNodeToRuntimeAST,
146
+ semanticValueToAST: () => semanticValueToAST,
139
147
  synthesizeFromSchemas: () => synthesizeFromSchemas,
140
148
  toEnvelopeJSON: () => toEnvelopeJSON,
141
149
  toJSONL: () => toJSONL,
@@ -2198,6 +2206,8 @@ function isNestedCommand(value) {
2198
2206
  }
2199
2207
  function parseExplicit(input, options = {}) {
2200
2208
  const trimmed = input.trim();
2209
+ const collect = options.collectDiagnostics === true;
2210
+ const diagnostics = [];
2201
2211
  if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) {
2202
2212
  throw new Error("Explicit syntax must be wrapped in brackets: [command role:value ...]");
2203
2213
  }
@@ -2220,10 +2230,30 @@ function parseExplicit(input, options = {}) {
2220
2230
  const enabled = token.startsWith("+");
2221
2231
  const flagName = token.slice(1);
2222
2232
  if (!flagName) {
2233
+ if (collect) {
2234
+ diagnostics.push({
2235
+ severity: "error",
2236
+ code: "EMPTY_FLAG",
2237
+ message: `Empty flag name: "${token}"`,
2238
+ source: "schema"
2239
+ });
2240
+ continue;
2241
+ }
2223
2242
  throw new Error(`Empty flag name: "${token}"`);
2224
2243
  }
2225
2244
  if (validRoleNames && !validRoleNames.has(flagName)) {
2226
2245
  const roleList = [...validRoleNames].join(", ");
2246
+ if (collect) {
2247
+ diagnostics.push({
2248
+ severity: "error",
2249
+ code: "UNKNOWN_FLAG",
2250
+ message: `Unknown flag "${flagName}" for command "${command}"`,
2251
+ source: "schema",
2252
+ suggestions: [...validRoleNames].map((r) => `+${r}`)
2253
+ });
2254
+ roles.set(flagName, createFlag(flagName, enabled));
2255
+ continue;
2256
+ }
2227
2257
  throw new Error(
2228
2258
  `Unknown flag "${flagName}" for command "${command}". Valid roles: ${roleList}`
2229
2259
  );
@@ -2233,14 +2263,33 @@ function parseExplicit(input, options = {}) {
2233
2263
  }
2234
2264
  const colonIndex = token.indexOf(":");
2235
2265
  if (colonIndex === -1) {
2266
+ if (collect) {
2267
+ diagnostics.push({
2268
+ severity: "error",
2269
+ code: "INVALID_ROLE_FORMAT",
2270
+ message: `Invalid role format: "${token}". Expected role:value or +flag`,
2271
+ source: "schema"
2272
+ });
2273
+ continue;
2274
+ }
2236
2275
  throw new Error(`Invalid role format: "${token}". Expected role:value or +flag`);
2237
2276
  }
2238
2277
  const roleName = token.slice(0, colonIndex);
2239
2278
  if (validRoleNames && !STRUCTURAL_ROLES.has(roleName) && !validRoleNames.has(roleName)) {
2240
2279
  const roleList = [...validRoleNames].join(", ");
2241
- throw new Error(
2242
- `Unknown role "${roleName}" for command "${command}". Valid roles: ${roleList}`
2243
- );
2280
+ if (collect) {
2281
+ diagnostics.push({
2282
+ severity: "error",
2283
+ code: "UNKNOWN_ROLE",
2284
+ message: `Unknown role "${roleName}" for command "${command}"`,
2285
+ source: "schema",
2286
+ suggestions: [...validRoleNames]
2287
+ });
2288
+ } else {
2289
+ throw new Error(
2290
+ `Unknown role "${roleName}" for command "${command}". Valid roles: ${roleList}`
2291
+ );
2292
+ }
2244
2293
  }
2245
2294
  const role = roleName;
2246
2295
  const valueStr = token.slice(colonIndex + 1);
@@ -2256,37 +2305,67 @@ function parseExplicit(input, options = {}) {
2256
2305
  if (schema && command !== "on") {
2257
2306
  for (const roleSpec of schema.roles) {
2258
2307
  if (roleSpec.required && !roles.has(roleSpec.role) && !roleSpec.default) {
2259
- throw new Error(
2260
- `Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`
2261
- );
2308
+ if (collect) {
2309
+ diagnostics.push({
2310
+ severity: "error",
2311
+ code: "MISSING_REQUIRED_ROLE",
2312
+ message: `Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`,
2313
+ source: "schema"
2314
+ });
2315
+ } else {
2316
+ throw new Error(
2317
+ `Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`
2318
+ );
2319
+ }
2262
2320
  }
2263
2321
  }
2264
2322
  }
2323
+ const attachDiagnostics = (node) => {
2324
+ if (diagnostics.length === 0) return node;
2325
+ return { ...node, diagnostics };
2326
+ };
2265
2327
  switch (command) {
2266
2328
  case "on": {
2267
2329
  const eventValue = roles.get("event");
2268
2330
  if (!eventValue) {
2331
+ if (collect) {
2332
+ diagnostics.push({
2333
+ severity: "error",
2334
+ code: "MISSING_EVENT_ROLE",
2335
+ message: "Event handler requires event role: [on event:click ...]",
2336
+ source: "schema"
2337
+ });
2338
+ return attachDiagnostics(
2339
+ createEventHandlerNode("on", roles, [], {
2340
+ sourceLanguage: "explicit"
2341
+ })
2342
+ );
2343
+ }
2269
2344
  throw new Error("Event handler requires event role: [on event:click ...]");
2270
2345
  }
2271
2346
  const body = extractStructuralBody(roles, "body", options);
2272
2347
  roles.delete("body");
2273
- return createEventHandlerNode("on", roles, body, {
2274
- sourceLanguage: "explicit"
2275
- });
2348
+ return attachDiagnostics(
2349
+ createEventHandlerNode("on", roles, body, {
2350
+ sourceLanguage: "explicit"
2351
+ })
2352
+ );
2276
2353
  }
2277
2354
  case "if": {
2278
2355
  const thenBranch = extractStructuralBody(roles, "then", options);
2279
2356
  const elseBranch = extractStructuralBody(roles, "else", options);
2280
2357
  roles.delete("then");
2281
2358
  roles.delete("else");
2282
- return createConditionalNode(
2283
- command,
2284
- roles,
2285
- thenBranch,
2286
- elseBranch.length > 0 ? elseBranch : void 0,
2287
- {
2288
- sourceLanguage: "explicit"
2289
- }
2359
+ return attachDiagnostics(
2360
+ createConditionalNode(
2361
+ command,
2362
+ roles,
2363
+ thenBranch,
2364
+ elseBranch.length > 0 ? elseBranch : void 0,
2365
+ {
2366
+ sourceLanguage: "explicit"
2367
+ }
2368
+ )
2290
2369
  );
2291
2370
  }
2292
2371
  case "repeat": {
@@ -2312,20 +2391,182 @@ function parseExplicit(input, options = {}) {
2312
2391
  }
2313
2392
  const loopVariable = variableValue?.type === "literal" && typeof variableValue.value === "string" ? variableValue.value : variableValue?.type === "expression" ? variableValue.raw : void 0;
2314
2393
  const indexVariable = indexVariableValue?.type === "literal" && typeof indexVariableValue.value === "string" ? indexVariableValue.value : void 0;
2315
- return createLoopNode(command, roles, variant, loopBody, loopVariable, indexVariable, {
2316
- sourceLanguage: "explicit"
2317
- });
2394
+ return attachDiagnostics(
2395
+ createLoopNode(command, roles, variant, loopBody, loopVariable, indexVariable, {
2396
+ sourceLanguage: "explicit"
2397
+ })
2398
+ );
2318
2399
  }
2319
2400
  default:
2320
- return createCommandNode(command, roles, {
2321
- sourceLanguage: "explicit"
2322
- });
2401
+ return attachDiagnostics(
2402
+ createCommandNode(command, roles, {
2403
+ sourceLanguage: "explicit"
2404
+ })
2405
+ );
2323
2406
  }
2324
2407
  }
2325
2408
  function isExplicitSyntax(input) {
2326
2409
  const trimmed = input.trim();
2327
2410
  return trimmed.startsWith("[") && trimmed.endsWith("]");
2328
2411
  }
2412
+ var CHAIN_KEYWORDS = /* @__PURE__ */ new Set(["then", "and", "async", "sequential"]);
2413
+ function splitCompoundSegments(input) {
2414
+ const segments = [];
2415
+ let chainType = null;
2416
+ let current = "";
2417
+ let bracketDepth = 0;
2418
+ let inString = false;
2419
+ let stringChar = "";
2420
+ for (let i = 0; i < input.length; i++) {
2421
+ const char = input[i];
2422
+ if (inString) {
2423
+ current += char;
2424
+ if (char === stringChar && input[i - 1] !== "\\") {
2425
+ inString = false;
2426
+ }
2427
+ continue;
2428
+ }
2429
+ if (char === '"' || char === "'") {
2430
+ inString = true;
2431
+ stringChar = char;
2432
+ current += char;
2433
+ continue;
2434
+ }
2435
+ if (char === "[") {
2436
+ bracketDepth++;
2437
+ current += char;
2438
+ continue;
2439
+ }
2440
+ if (char === "]") {
2441
+ bracketDepth--;
2442
+ current += char;
2443
+ if (bracketDepth === 0) {
2444
+ const rest = input.slice(i + 1);
2445
+ const pipeMatch = rest.match(/^\s*\|>\s*/);
2446
+ if (pipeMatch) {
2447
+ segments.push(current.trim());
2448
+ current = "";
2449
+ chainType ?? (chainType = "pipe");
2450
+ i += pipeMatch[0].length;
2451
+ continue;
2452
+ }
2453
+ const keywordMatch = rest.match(/^\s+(then|and|async|sequential)\s+/);
2454
+ if (keywordMatch && CHAIN_KEYWORDS.has(keywordMatch[1])) {
2455
+ segments.push(current.trim());
2456
+ current = "";
2457
+ chainType ?? (chainType = keywordMatch[1]);
2458
+ i += keywordMatch[0].length;
2459
+ continue;
2460
+ }
2461
+ }
2462
+ continue;
2463
+ }
2464
+ current += char;
2465
+ }
2466
+ if (current.trim()) {
2467
+ segments.push(current.trim());
2468
+ }
2469
+ return { segments, chainType };
2470
+ }
2471
+ function parseCompound(input, options) {
2472
+ const trimmed = input.trim();
2473
+ const { annotations, remainder } = extractAnnotations(trimmed);
2474
+ const { segments, chainType } = splitCompoundSegments(remainder);
2475
+ let node;
2476
+ if (segments.length <= 1) {
2477
+ node = parseExplicit(segments[0] || remainder, options);
2478
+ } else {
2479
+ const statements = segments.map((seg) => parseExplicit(seg, options));
2480
+ node = createCompoundNode(statements, chainType ?? "sequential");
2481
+ }
2482
+ if (annotations.length > 0) {
2483
+ return { ...node, annotations };
2484
+ }
2485
+ return node;
2486
+ }
2487
+ function extractAnnotations(input) {
2488
+ const annotations = [];
2489
+ let pos = 0;
2490
+ while (pos < input.length) {
2491
+ while (pos < input.length && (input[pos] === " " || input[pos] === " ")) {
2492
+ pos++;
2493
+ }
2494
+ if (pos >= input.length || input[pos] !== "@") {
2495
+ break;
2496
+ }
2497
+ pos++;
2498
+ const nameStart = pos;
2499
+ while (pos < input.length && /[A-Za-z0-9\-_]/.test(input[pos])) {
2500
+ pos++;
2501
+ }
2502
+ const name = input.slice(nameStart, pos);
2503
+ if (!name) break;
2504
+ let value;
2505
+ if (pos < input.length && input[pos] === "(") {
2506
+ pos++;
2507
+ const valueStart = pos;
2508
+ if (pos < input.length && (input[pos] === '"' || input[pos] === "'")) {
2509
+ const quote = input[pos];
2510
+ pos++;
2511
+ while (pos < input.length && input[pos] !== quote) {
2512
+ if (input[pos] === "\\") pos++;
2513
+ pos++;
2514
+ }
2515
+ pos++;
2516
+ value = input.slice(valueStart + 1, pos - 1);
2517
+ } else {
2518
+ while (pos < input.length && input[pos] !== ")") {
2519
+ pos++;
2520
+ }
2521
+ value = input.slice(valueStart, pos);
2522
+ }
2523
+ if (pos < input.length && input[pos] === ")") {
2524
+ pos++;
2525
+ }
2526
+ }
2527
+ annotations.push(value !== void 0 ? { name, value } : { name });
2528
+ }
2529
+ return { annotations, remainder: input.slice(pos).trim() };
2530
+ }
2531
+ var VERSION_HEADER_RE = /^#!lse\s+(\d+\.\d+(?:\.\d+)?)\s*$/;
2532
+ function parseDocument(input, options) {
2533
+ const lines = input.split("\n");
2534
+ let lseVersion = "1.0";
2535
+ const nodes = [];
2536
+ let startLine = 0;
2537
+ for (let i = 0; i < lines.length; i++) {
2538
+ const trimmed = lines[i].trim();
2539
+ if (!trimmed) continue;
2540
+ const versionMatch = trimmed.match(VERSION_HEADER_RE);
2541
+ if (versionMatch) {
2542
+ lseVersion = versionMatch[1];
2543
+ startLine = i + 1;
2544
+ }
2545
+ break;
2546
+ }
2547
+ for (let i = startLine; i < lines.length; i++) {
2548
+ const trimmed = lines[i].trim();
2549
+ if (!trimmed) continue;
2550
+ if (trimmed.startsWith("//") || trimmed.startsWith("#") && !trimmed.startsWith("#!")) {
2551
+ continue;
2552
+ }
2553
+ nodes.push(parseCompound(trimmed, options));
2554
+ }
2555
+ return { lseVersion, nodes };
2556
+ }
2557
+ function isCompoundSyntax(input) {
2558
+ const { segments } = splitCompoundSegments(input.trim());
2559
+ return segments.length > 1;
2560
+ }
2561
+ function isDocumentSyntax(input) {
2562
+ const trimmed = input.trim();
2563
+ if (trimmed.includes("\n")) return true;
2564
+ if (VERSION_HEADER_RE.test(trimmed)) return true;
2565
+ if (trimmed.startsWith("//") || trimmed.startsWith("#") && !trimmed.startsWith("#!")) {
2566
+ return true;
2567
+ }
2568
+ return false;
2569
+ }
2329
2570
  function extractStructuralBody(roles, roleName, options) {
2330
2571
  const value = roles.get(roleName);
2331
2572
  if (!value) return [];
@@ -2427,11 +2668,33 @@ function findMatchingBracket(str, start) {
2427
2668
 
2428
2669
  // src/ir/explicit-renderer.ts
2429
2670
  function renderExplicit(node) {
2671
+ let result;
2430
2672
  if (node.kind === "compound") {
2431
2673
  const compoundNode = node;
2432
2674
  const renderedStatements = compoundNode.statements.map((stmt) => renderExplicit(stmt));
2433
- return renderedStatements.join(` ${compoundNode.chainType} `);
2675
+ result = renderedStatements.join(` ${compoundNode.chainType} `);
2676
+ } else {
2677
+ result = renderBracketCommand(node);
2434
2678
  }
2679
+ if (node.annotations && node.annotations.length > 0) {
2680
+ const annParts = node.annotations.map(
2681
+ (ann) => ann.value !== void 0 ? `@${ann.name}(${ann.value})` : `@${ann.name}`
2682
+ );
2683
+ return annParts.join(" ") + " " + result;
2684
+ }
2685
+ return result;
2686
+ }
2687
+ function renderDocument(envelope) {
2688
+ const lines = [];
2689
+ if (envelope.lseVersion && envelope.lseVersion !== "1.0") {
2690
+ lines.push(`#!lse ${envelope.lseVersion}`);
2691
+ }
2692
+ for (const node of envelope.nodes) {
2693
+ lines.push(renderExplicit(node));
2694
+ }
2695
+ return lines.join("\n");
2696
+ }
2697
+ function renderBracketCommand(node) {
2435
2698
  const parts = [node.action];
2436
2699
  for (const [role, value] of node.roles) {
2437
2700
  if (value.type === "flag") {
@@ -2769,12 +3032,16 @@ function toProtocolJSON(node) {
2769
3032
  );
2770
3033
  }
2771
3034
  if (node.diagnostics && node.diagnostics.length > 0) {
2772
- result.diagnostics = node.diagnostics.map((d) => ({
2773
- level: d.level,
2774
- role: d.role,
2775
- message: d.message,
2776
- code: d.code
2777
- }));
3035
+ result.diagnostics = node.diagnostics.map((d) => {
3036
+ const json = {
3037
+ level: d.severity,
3038
+ message: d.message
3039
+ };
3040
+ if (d.code) json.code = d.code;
3041
+ if (d.source) json.source = d.source;
3042
+ if (d.suggestions && d.suggestions.length > 0) json.suggestions = [...d.suggestions];
3043
+ return json;
3044
+ });
2778
3045
  }
2779
3046
  return result;
2780
3047
  }
@@ -2822,10 +3089,11 @@ function fromProtocolJSON(json) {
2822
3089
  (a) => a.value !== void 0 ? { name: a.name, value: a.value } : { name: a.name }
2823
3090
  );
2824
3091
  const diagnostics = json.diagnostics?.map((d) => ({
2825
- level: d.level,
2826
- role: d.role,
3092
+ severity: d.level,
2827
3093
  message: d.message,
2828
- code: d.code
3094
+ ...d.code ? { code: d.code } : {},
3095
+ ...d.source ? { source: d.source } : {},
3096
+ ...d.suggestions && d.suggestions.length > 0 ? { suggestions: d.suggestions } : {}
2829
3097
  }));
2830
3098
  if (json.trigger) {
2831
3099
  const eventName = json.trigger.event;
@@ -2966,7 +3234,7 @@ var VALID_VALUE_TYPES2 = /* @__PURE__ */ new Set([
2966
3234
  ]);
2967
3235
  var VALID_CHAIN_TYPES = /* @__PURE__ */ new Set(["then", "and", "async", "sequential", "pipe"]);
2968
3236
  var VALID_ASYNC_VARIANTS = /* @__PURE__ */ new Set(["all", "race"]);
2969
- var VALID_DIAGNOSTIC_LEVELS = /* @__PURE__ */ new Set(["error", "warning"]);
3237
+ var VALID_DIAGNOSTIC_LEVELS = /* @__PURE__ */ new Set(["error", "warning", "info"]);
2970
3238
  function validateProtocolJSON(json) {
2971
3239
  const diagnostics = [];
2972
3240
  if (typeof json !== "object" || json === null) {
@@ -3115,14 +3383,7 @@ function validateProtocolJSON(json) {
3115
3383
  diagnostics.push({
3116
3384
  severity: "error",
3117
3385
  code: "INVALID_DIAGNOSTIC_LEVEL",
3118
- message: `diagnostics[${i}].level must be "error" or "warning"`
3119
- });
3120
- }
3121
- if (typeof d.role !== "string") {
3122
- diagnostics.push({
3123
- severity: "error",
3124
- code: "MISSING_DIAGNOSTIC_ROLE",
3125
- message: `diagnostics[${i}] missing required field: role`
3386
+ message: `diagnostics[${i}].level must be "error", "warning", or "info"`
3126
3387
  });
3127
3388
  }
3128
3389
  if (typeof d.message !== "string") {
@@ -3132,11 +3393,11 @@ function validateProtocolJSON(json) {
3132
3393
  message: `diagnostics[${i}] missing required field: message`
3133
3394
  });
3134
3395
  }
3135
- if (typeof d.code !== "string") {
3396
+ if ("code" in d && typeof d.code !== "string") {
3136
3397
  diagnostics.push({
3137
- severity: "error",
3138
- code: "MISSING_DIAGNOSTIC_CODE",
3139
- message: `diagnostics[${i}] missing required field: code`
3398
+ severity: "warning",
3399
+ code: "INVALID_DIAGNOSTIC_CODE",
3400
+ message: `diagnostics[${i}].code must be a string if present`
3140
3401
  });
3141
3402
  }
3142
3403
  }
@@ -3572,6 +3833,123 @@ function bridgeEventModifiers(mods) {
3572
3833
  };
3573
3834
  }
3574
3835
 
3836
+ // src/ir/to-runtime-ast.ts
3837
+ function semanticNodeToRuntimeAST(node) {
3838
+ switch (node.kind) {
3839
+ case "event-handler":
3840
+ return convertEventHandler(node);
3841
+ case "conditional":
3842
+ return convertConditional(node);
3843
+ case "loop":
3844
+ return convertLoop(node);
3845
+ case "compound": {
3846
+ const compound = node;
3847
+ const commands = (compound.statements ?? []).map(semanticNodeToRuntimeAST);
3848
+ return {
3849
+ type: "CommandSequence",
3850
+ commands
3851
+ };
3852
+ }
3853
+ case "command":
3854
+ default:
3855
+ return convertCommand2(node);
3856
+ }
3857
+ }
3858
+ function convertEventHandler(eh) {
3859
+ const eventValue = eh.roles.get("event");
3860
+ const eventName = eventValue && "value" in eventValue ? String(eventValue.value) : "click";
3861
+ const modifiers = {};
3862
+ if (eh.eventModifiers) {
3863
+ if (eh.eventModifiers.once) modifiers.once = true;
3864
+ if (eh.eventModifiers.debounce) modifiers.debounce = eh.eventModifiers.debounce;
3865
+ if (eh.eventModifiers.throttle) modifiers.throttle = eh.eventModifiers.throttle;
3866
+ if (eh.eventModifiers.queue) modifiers.queue = eh.eventModifiers.queue;
3867
+ }
3868
+ return {
3869
+ type: "event",
3870
+ event: eventName,
3871
+ modifiers,
3872
+ body: (eh.body ?? []).map(semanticNodeToRuntimeAST)
3873
+ };
3874
+ }
3875
+ function convertCommand2(node) {
3876
+ const roles = {};
3877
+ const args = [];
3878
+ for (const [roleName, value] of node.roles) {
3879
+ const astValue = semanticValueToAST(value);
3880
+ roles[roleName] = astValue;
3881
+ args.push(astValue);
3882
+ }
3883
+ return {
3884
+ type: "command",
3885
+ name: node.action,
3886
+ args,
3887
+ roles
3888
+ };
3889
+ }
3890
+ function convertConditional(node) {
3891
+ const conditionValue = node.roles.get("condition");
3892
+ return {
3893
+ type: "command",
3894
+ name: "if",
3895
+ args: conditionValue ? [semanticValueToAST(conditionValue)] : [],
3896
+ condition: conditionValue ? semanticValueToAST(conditionValue) : void 0,
3897
+ thenBranch: (node.thenBranch ?? []).map(semanticNodeToRuntimeAST),
3898
+ elseBranch: (node.elseBranch ?? []).map(semanticNodeToRuntimeAST)
3899
+ };
3900
+ }
3901
+ function convertLoop(node) {
3902
+ const countValue = node.roles.get("patient") || node.roles.get("count");
3903
+ return {
3904
+ type: "command",
3905
+ name: "repeat",
3906
+ args: countValue ? [semanticValueToAST(countValue)] : [],
3907
+ loopVariant: node.loopVariant ?? "forever",
3908
+ body: (node.body ?? []).map(semanticNodeToRuntimeAST),
3909
+ loopVariable: node.loopVariable,
3910
+ indexVariable: node.indexVariable
3911
+ };
3912
+ }
3913
+ function semanticValueToAST(value) {
3914
+ switch (value.type) {
3915
+ case "selector":
3916
+ return { type: "selector", value: value.value };
3917
+ case "reference":
3918
+ return { type: "identifier", value: value.value };
3919
+ case "literal": {
3920
+ const lit = value;
3921
+ if (lit.dataType === "duration") {
3922
+ const str = String(lit.value);
3923
+ const match = /^(\d+(?:\.\d+)?)(ms|s)$/.exec(str);
3924
+ if (match) {
3925
+ const ms = match[2] === "s" ? parseFloat(match[1]) * 1e3 : parseFloat(match[1]);
3926
+ return { type: "literal", value: ms };
3927
+ }
3928
+ }
3929
+ return { type: "literal", value: lit.value };
3930
+ }
3931
+ case "property-path": {
3932
+ const pp = value;
3933
+ const objectAST = semanticValueToAST(pp.object);
3934
+ return {
3935
+ type: "member",
3936
+ object: objectAST,
3937
+ property: pp.property
3938
+ };
3939
+ }
3940
+ case "expression": {
3941
+ const expr = value;
3942
+ return { type: "expression", value: expr.raw };
3943
+ }
3944
+ case "flag": {
3945
+ const flag = value;
3946
+ return { type: "literal", value: flag.enabled };
3947
+ }
3948
+ default:
3949
+ return { type: "literal", value: String(value.value ?? "") };
3950
+ }
3951
+ }
3952
+
3575
3953
  // src/api/create-dsl.ts
3576
3954
  var DSLRegistry = class {
3577
3955
  constructor(config) {
@@ -6543,6 +6921,138 @@ function normalized(stem, confidence, metadata) {
6543
6921
  return { stem, confidence };
6544
6922
  }
6545
6923
 
6924
+ // src/core/tokenization/morphology/base-normalizer.ts
6925
+ var BaseMorphologicalNormalizer = class {
6926
+ constructor(config) {
6927
+ this.language = config.language;
6928
+ this.config = {
6929
+ minWordLength: 3,
6930
+ minStemLength: 2,
6931
+ ...config
6932
+ };
6933
+ }
6934
+ /**
6935
+ * Standard normalization pipeline. Override for custom behavior.
6936
+ */
6937
+ normalize(word) {
6938
+ const lower = word.toLowerCase();
6939
+ if (this.isAlreadyNormalized(lower)) {
6940
+ return noChange(word);
6941
+ }
6942
+ if (this.config.reflexiveSuffixes) {
6943
+ const reflexive = this.tryReflexiveNormalization(lower);
6944
+ if (reflexive) return reflexive;
6945
+ }
6946
+ if (this.config.endings) {
6947
+ const conjugation = this.tryConjugationEndings(lower);
6948
+ if (conjugation) return conjugation;
6949
+ }
6950
+ if (this.config.suffixRules) {
6951
+ const suffix = this.trySuffixRules(lower);
6952
+ if (suffix) return suffix;
6953
+ }
6954
+ if (this.config.prefixRules) {
6955
+ const prefix = this.tryPrefixRules(lower);
6956
+ if (prefix) return prefix;
6957
+ }
6958
+ return noChange(word);
6959
+ }
6960
+ /**
6961
+ * Check if word is already in dictionary form (e.g., ends in -ar/-er/-ir).
6962
+ * Override for language-specific checks.
6963
+ */
6964
+ isAlreadyNormalized(word) {
6965
+ if (this.config.infinitiveEndings) {
6966
+ return this.config.infinitiveEndings.some((e) => word.endsWith(e));
6967
+ }
6968
+ return false;
6969
+ }
6970
+ /**
6971
+ * Try to strip reflexive suffixes and normalize the remainder.
6972
+ * Common in Romance languages (Spanish, Portuguese, French).
6973
+ */
6974
+ tryReflexiveNormalization(word) {
6975
+ const suffixes = this.config.reflexiveSuffixes;
6976
+ if (!suffixes) return null;
6977
+ for (const suffix of suffixes) {
6978
+ if (!word.endsWith(suffix)) continue;
6979
+ const remainder = word.slice(0, -suffix.length);
6980
+ if (this.isAlreadyNormalized(remainder)) {
6981
+ return normalized(remainder, 0.88, {
6982
+ removedSuffixes: [suffix],
6983
+ conjugationType: "reflexive"
6984
+ });
6985
+ }
6986
+ const inner = this.tryConjugationEndings(remainder) || this.trySuffixRules(remainder);
6987
+ if (inner && inner.stem !== remainder) {
6988
+ return normalized(inner.stem, inner.confidence * 0.95, {
6989
+ removedSuffixes: [suffix, ...inner.metadata?.removedSuffixes || []],
6990
+ conjugationType: "reflexive"
6991
+ });
6992
+ }
6993
+ }
6994
+ return null;
6995
+ }
6996
+ /**
6997
+ * Try conjugation endings (verb class endings like -ar/-er/-ir patterns).
6998
+ * Endings must be pre-sorted longest-first.
6999
+ */
7000
+ tryConjugationEndings(word) {
7001
+ const endings = this.config.endings;
7002
+ if (!endings) return null;
7003
+ const minStem = this.config.minStemLength ?? 2;
7004
+ for (const rule of endings) {
7005
+ if (!word.endsWith(rule.ending)) continue;
7006
+ const stemBase = word.slice(0, -rule.ending.length);
7007
+ if (stemBase.length < minStem) continue;
7008
+ const infinitive = stemBase + rule.stem;
7009
+ return normalized(infinitive, rule.confidence, {
7010
+ removedSuffixes: [rule.ending],
7011
+ conjugationType: rule.type
7012
+ });
7013
+ }
7014
+ return null;
7015
+ }
7016
+ /**
7017
+ * Try SuffixRule-style normalization.
7018
+ * Rules must be pre-sorted longest-first.
7019
+ */
7020
+ trySuffixRules(word) {
7021
+ const rules = this.config.suffixRules;
7022
+ if (!rules) return null;
7023
+ const defaultMinStem = this.config.minStemLength ?? 2;
7024
+ for (const rule of rules) {
7025
+ if (!word.endsWith(rule.pattern)) continue;
7026
+ const stem = word.slice(0, -rule.pattern.length);
7027
+ const minStem = rule.minStemLength ?? defaultMinStem;
7028
+ if (stem.length < minStem) continue;
7029
+ const result = stem + (rule.replacement || "");
7030
+ return normalized(result, rule.confidence, {
7031
+ removedSuffixes: [rule.pattern],
7032
+ ...rule.conjugationType && { conjugationType: rule.conjugationType }
7033
+ });
7034
+ }
7035
+ return null;
7036
+ }
7037
+ /**
7038
+ * Try PrefixRule-style normalization.
7039
+ */
7040
+ tryPrefixRules(word) {
7041
+ const rules = this.config.prefixRules;
7042
+ if (!rules) return null;
7043
+ for (const rule of rules) {
7044
+ if (!word.startsWith(rule.pattern)) continue;
7045
+ const remainder = word.slice(rule.pattern.length);
7046
+ const minRemaining = rule.minRemaining ?? this.config.minStemLength ?? 2;
7047
+ if (remainder.length < minRemaining) continue;
7048
+ return normalized(remainder, 1 - rule.confidencePenalty, {
7049
+ removedPrefixes: [rule.pattern]
7050
+ });
7051
+ }
7052
+ return null;
7053
+ }
7054
+ };
7055
+
6546
7056
  // src/training/jsonl-writer.ts
6547
7057
  function toJSONL(pairs) {
6548
7058
  return pairs.map((pair) => JSON.stringify(toJSONLRow(pair))).join("\n");
@@ -6940,6 +7450,7 @@ function accumulateIndented(statements, config) {
6940
7450
  // Annotate the CommonJS export names for ESM import in node:
6941
7451
  0 && (module.exports = {
6942
7452
  AOTOrchestrator,
7453
+ BaseMorphologicalNormalizer,
6943
7454
  BaseTokenizer,
6944
7455
  CrossDomainDispatcher,
6945
7456
  DEFAULT_OPERATORS,
@@ -7022,8 +7533,10 @@ function accumulateIndented(statements, config) {
7022
7533
  isCSSPropertyRef,
7023
7534
  isCSSSelector,
7024
7535
  isClassName,
7536
+ isCompoundSyntax,
7025
7537
  isContextAwareExtractor,
7026
7538
  isDigit,
7539
+ isDocumentSyntax,
7027
7540
  isEnvelope,
7028
7541
  isExplicitSyntax,
7029
7542
  isIdSelector,
@@ -7047,14 +7560,19 @@ function accumulateIndented(statements, config) {
7047
7560
  needsDisambiguation,
7048
7561
  noChange,
7049
7562
  normalized,
7563
+ parseCompound,
7564
+ parseDocument,
7050
7565
  parseExplicit,
7051
7566
  parseJSONL,
7052
7567
  patternMatcher,
7053
7568
  registryToAOTBackends,
7569
+ renderDocument,
7054
7570
  renderExplicit,
7055
7571
  renderExpr,
7056
7572
  reorderRoles,
7057
7573
  semanticNodeToJSON,
7574
+ semanticNodeToRuntimeAST,
7575
+ semanticValueToAST,
7058
7576
  synthesizeFromSchemas,
7059
7577
  toEnvelopeJSON,
7060
7578
  toJSONL,