@lokascript/language-server 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.
@@ -2005,6 +2005,8 @@ function isNestedCommand(value) {
2005
2005
  }
2006
2006
  function parseExplicit(input, options = {}) {
2007
2007
  const trimmed = input.trim();
2008
+ const collect = options.collectDiagnostics === true;
2009
+ const diagnostics = [];
2008
2010
  if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) {
2009
2011
  throw new Error("Explicit syntax must be wrapped in brackets: [command role:value ...]");
2010
2012
  }
@@ -2027,10 +2029,30 @@ function parseExplicit(input, options = {}) {
2027
2029
  const enabled = token.startsWith("+");
2028
2030
  const flagName = token.slice(1);
2029
2031
  if (!flagName) {
2032
+ if (collect) {
2033
+ diagnostics.push({
2034
+ severity: "error",
2035
+ code: "EMPTY_FLAG",
2036
+ message: `Empty flag name: "${token}"`,
2037
+ source: "schema"
2038
+ });
2039
+ continue;
2040
+ }
2030
2041
  throw new Error(`Empty flag name: "${token}"`);
2031
2042
  }
2032
2043
  if (validRoleNames && !validRoleNames.has(flagName)) {
2033
2044
  const roleList = [...validRoleNames].join(", ");
2045
+ if (collect) {
2046
+ diagnostics.push({
2047
+ severity: "error",
2048
+ code: "UNKNOWN_FLAG",
2049
+ message: `Unknown flag "${flagName}" for command "${command}"`,
2050
+ source: "schema",
2051
+ suggestions: [...validRoleNames].map((r) => `+${r}`)
2052
+ });
2053
+ roles.set(flagName, createFlag(flagName, enabled));
2054
+ continue;
2055
+ }
2034
2056
  throw new Error(
2035
2057
  `Unknown flag "${flagName}" for command "${command}". Valid roles: ${roleList}`
2036
2058
  );
@@ -2040,14 +2062,33 @@ function parseExplicit(input, options = {}) {
2040
2062
  }
2041
2063
  const colonIndex = token.indexOf(":");
2042
2064
  if (colonIndex === -1) {
2065
+ if (collect) {
2066
+ diagnostics.push({
2067
+ severity: "error",
2068
+ code: "INVALID_ROLE_FORMAT",
2069
+ message: `Invalid role format: "${token}". Expected role:value or +flag`,
2070
+ source: "schema"
2071
+ });
2072
+ continue;
2073
+ }
2043
2074
  throw new Error(`Invalid role format: "${token}". Expected role:value or +flag`);
2044
2075
  }
2045
2076
  const roleName = token.slice(0, colonIndex);
2046
2077
  if (validRoleNames && !STRUCTURAL_ROLES.has(roleName) && !validRoleNames.has(roleName)) {
2047
2078
  const roleList = [...validRoleNames].join(", ");
2048
- throw new Error(
2049
- `Unknown role "${roleName}" for command "${command}". Valid roles: ${roleList}`
2050
- );
2079
+ if (collect) {
2080
+ diagnostics.push({
2081
+ severity: "error",
2082
+ code: "UNKNOWN_ROLE",
2083
+ message: `Unknown role "${roleName}" for command "${command}"`,
2084
+ source: "schema",
2085
+ suggestions: [...validRoleNames]
2086
+ });
2087
+ } else {
2088
+ throw new Error(
2089
+ `Unknown role "${roleName}" for command "${command}". Valid roles: ${roleList}`
2090
+ );
2091
+ }
2051
2092
  }
2052
2093
  const role = roleName;
2053
2094
  const valueStr = token.slice(colonIndex + 1);
@@ -2063,37 +2104,67 @@ function parseExplicit(input, options = {}) {
2063
2104
  if (schema && command !== "on") {
2064
2105
  for (const roleSpec of schema.roles) {
2065
2106
  if (roleSpec.required && !roles.has(roleSpec.role) && !roleSpec.default) {
2066
- throw new Error(
2067
- `Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`
2068
- );
2107
+ if (collect) {
2108
+ diagnostics.push({
2109
+ severity: "error",
2110
+ code: "MISSING_REQUIRED_ROLE",
2111
+ message: `Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`,
2112
+ source: "schema"
2113
+ });
2114
+ } else {
2115
+ throw new Error(
2116
+ `Missing required role "${roleSpec.role}" for command "${command}": ${roleSpec.description}`
2117
+ );
2118
+ }
2069
2119
  }
2070
2120
  }
2071
2121
  }
2122
+ const attachDiagnostics = (node) => {
2123
+ if (diagnostics.length === 0) return node;
2124
+ return { ...node, diagnostics };
2125
+ };
2072
2126
  switch (command) {
2073
2127
  case "on": {
2074
2128
  const eventValue = roles.get("event");
2075
2129
  if (!eventValue) {
2130
+ if (collect) {
2131
+ diagnostics.push({
2132
+ severity: "error",
2133
+ code: "MISSING_EVENT_ROLE",
2134
+ message: "Event handler requires event role: [on event:click ...]",
2135
+ source: "schema"
2136
+ });
2137
+ return attachDiagnostics(
2138
+ createEventHandlerNode("on", roles, [], {
2139
+ sourceLanguage: "explicit"
2140
+ })
2141
+ );
2142
+ }
2076
2143
  throw new Error("Event handler requires event role: [on event:click ...]");
2077
2144
  }
2078
2145
  const body = extractStructuralBody(roles, "body", options);
2079
2146
  roles.delete("body");
2080
- return createEventHandlerNode("on", roles, body, {
2081
- sourceLanguage: "explicit"
2082
- });
2147
+ return attachDiagnostics(
2148
+ createEventHandlerNode("on", roles, body, {
2149
+ sourceLanguage: "explicit"
2150
+ })
2151
+ );
2083
2152
  }
2084
2153
  case "if": {
2085
2154
  const thenBranch = extractStructuralBody(roles, "then", options);
2086
2155
  const elseBranch = extractStructuralBody(roles, "else", options);
2087
2156
  roles.delete("then");
2088
2157
  roles.delete("else");
2089
- return createConditionalNode(
2090
- command,
2091
- roles,
2092
- thenBranch,
2093
- elseBranch.length > 0 ? elseBranch : void 0,
2094
- {
2095
- sourceLanguage: "explicit"
2096
- }
2158
+ return attachDiagnostics(
2159
+ createConditionalNode(
2160
+ command,
2161
+ roles,
2162
+ thenBranch,
2163
+ elseBranch.length > 0 ? elseBranch : void 0,
2164
+ {
2165
+ sourceLanguage: "explicit"
2166
+ }
2167
+ )
2097
2168
  );
2098
2169
  }
2099
2170
  case "repeat": {
@@ -2119,20 +2190,182 @@ function parseExplicit(input, options = {}) {
2119
2190
  }
2120
2191
  const loopVariable = variableValue?.type === "literal" && typeof variableValue.value === "string" ? variableValue.value : variableValue?.type === "expression" ? variableValue.raw : void 0;
2121
2192
  const indexVariable = indexVariableValue?.type === "literal" && typeof indexVariableValue.value === "string" ? indexVariableValue.value : void 0;
2122
- return createLoopNode(command, roles, variant, loopBody, loopVariable, indexVariable, {
2123
- sourceLanguage: "explicit"
2124
- });
2193
+ return attachDiagnostics(
2194
+ createLoopNode(command, roles, variant, loopBody, loopVariable, indexVariable, {
2195
+ sourceLanguage: "explicit"
2196
+ })
2197
+ );
2125
2198
  }
2126
2199
  default:
2127
- return createCommandNode(command, roles, {
2128
- sourceLanguage: "explicit"
2129
- });
2200
+ return attachDiagnostics(
2201
+ createCommandNode(command, roles, {
2202
+ sourceLanguage: "explicit"
2203
+ })
2204
+ );
2130
2205
  }
2131
2206
  }
2132
2207
  function isExplicitSyntax(input) {
2133
2208
  const trimmed = input.trim();
2134
2209
  return trimmed.startsWith("[") && trimmed.endsWith("]");
2135
2210
  }
2211
+ var CHAIN_KEYWORDS = /* @__PURE__ */ new Set(["then", "and", "async", "sequential"]);
2212
+ function splitCompoundSegments(input) {
2213
+ const segments = [];
2214
+ let chainType = null;
2215
+ let current = "";
2216
+ let bracketDepth = 0;
2217
+ let inString = false;
2218
+ let stringChar = "";
2219
+ for (let i = 0; i < input.length; i++) {
2220
+ const char = input[i];
2221
+ if (inString) {
2222
+ current += char;
2223
+ if (char === stringChar && input[i - 1] !== "\\") {
2224
+ inString = false;
2225
+ }
2226
+ continue;
2227
+ }
2228
+ if (char === '"' || char === "'") {
2229
+ inString = true;
2230
+ stringChar = char;
2231
+ current += char;
2232
+ continue;
2233
+ }
2234
+ if (char === "[") {
2235
+ bracketDepth++;
2236
+ current += char;
2237
+ continue;
2238
+ }
2239
+ if (char === "]") {
2240
+ bracketDepth--;
2241
+ current += char;
2242
+ if (bracketDepth === 0) {
2243
+ const rest = input.slice(i + 1);
2244
+ const pipeMatch = rest.match(/^\s*\|>\s*/);
2245
+ if (pipeMatch) {
2246
+ segments.push(current.trim());
2247
+ current = "";
2248
+ chainType ?? (chainType = "pipe");
2249
+ i += pipeMatch[0].length;
2250
+ continue;
2251
+ }
2252
+ const keywordMatch = rest.match(/^\s+(then|and|async|sequential)\s+/);
2253
+ if (keywordMatch && CHAIN_KEYWORDS.has(keywordMatch[1])) {
2254
+ segments.push(current.trim());
2255
+ current = "";
2256
+ chainType ?? (chainType = keywordMatch[1]);
2257
+ i += keywordMatch[0].length;
2258
+ continue;
2259
+ }
2260
+ }
2261
+ continue;
2262
+ }
2263
+ current += char;
2264
+ }
2265
+ if (current.trim()) {
2266
+ segments.push(current.trim());
2267
+ }
2268
+ return { segments, chainType };
2269
+ }
2270
+ function parseCompound(input, options) {
2271
+ const trimmed = input.trim();
2272
+ const { annotations, remainder } = extractAnnotations(trimmed);
2273
+ const { segments, chainType } = splitCompoundSegments(remainder);
2274
+ let node;
2275
+ if (segments.length <= 1) {
2276
+ node = parseExplicit(segments[0] || remainder, options);
2277
+ } else {
2278
+ const statements = segments.map((seg) => parseExplicit(seg, options));
2279
+ node = createCompoundNode(statements, chainType ?? "sequential");
2280
+ }
2281
+ if (annotations.length > 0) {
2282
+ return { ...node, annotations };
2283
+ }
2284
+ return node;
2285
+ }
2286
+ function extractAnnotations(input) {
2287
+ const annotations = [];
2288
+ let pos = 0;
2289
+ while (pos < input.length) {
2290
+ while (pos < input.length && (input[pos] === " " || input[pos] === " ")) {
2291
+ pos++;
2292
+ }
2293
+ if (pos >= input.length || input[pos] !== "@") {
2294
+ break;
2295
+ }
2296
+ pos++;
2297
+ const nameStart = pos;
2298
+ while (pos < input.length && /[A-Za-z0-9\-_]/.test(input[pos])) {
2299
+ pos++;
2300
+ }
2301
+ const name = input.slice(nameStart, pos);
2302
+ if (!name) break;
2303
+ let value;
2304
+ if (pos < input.length && input[pos] === "(") {
2305
+ pos++;
2306
+ const valueStart = pos;
2307
+ if (pos < input.length && (input[pos] === '"' || input[pos] === "'")) {
2308
+ const quote = input[pos];
2309
+ pos++;
2310
+ while (pos < input.length && input[pos] !== quote) {
2311
+ if (input[pos] === "\\") pos++;
2312
+ pos++;
2313
+ }
2314
+ pos++;
2315
+ value = input.slice(valueStart + 1, pos - 1);
2316
+ } else {
2317
+ while (pos < input.length && input[pos] !== ")") {
2318
+ pos++;
2319
+ }
2320
+ value = input.slice(valueStart, pos);
2321
+ }
2322
+ if (pos < input.length && input[pos] === ")") {
2323
+ pos++;
2324
+ }
2325
+ }
2326
+ annotations.push(value !== void 0 ? { name, value } : { name });
2327
+ }
2328
+ return { annotations, remainder: input.slice(pos).trim() };
2329
+ }
2330
+ var VERSION_HEADER_RE = /^#!lse\s+(\d+\.\d+(?:\.\d+)?)\s*$/;
2331
+ function parseDocument(input, options) {
2332
+ const lines = input.split("\n");
2333
+ let lseVersion = "1.0";
2334
+ const nodes = [];
2335
+ let startLine = 0;
2336
+ for (let i = 0; i < lines.length; i++) {
2337
+ const trimmed = lines[i].trim();
2338
+ if (!trimmed) continue;
2339
+ const versionMatch = trimmed.match(VERSION_HEADER_RE);
2340
+ if (versionMatch) {
2341
+ lseVersion = versionMatch[1];
2342
+ startLine = i + 1;
2343
+ }
2344
+ break;
2345
+ }
2346
+ for (let i = startLine; i < lines.length; i++) {
2347
+ const trimmed = lines[i].trim();
2348
+ if (!trimmed) continue;
2349
+ if (trimmed.startsWith("//") || trimmed.startsWith("#") && !trimmed.startsWith("#!")) {
2350
+ continue;
2351
+ }
2352
+ nodes.push(parseCompound(trimmed, options));
2353
+ }
2354
+ return { lseVersion, nodes };
2355
+ }
2356
+ function isCompoundSyntax(input) {
2357
+ const { segments } = splitCompoundSegments(input.trim());
2358
+ return segments.length > 1;
2359
+ }
2360
+ function isDocumentSyntax(input) {
2361
+ const trimmed = input.trim();
2362
+ if (trimmed.includes("\n")) return true;
2363
+ if (VERSION_HEADER_RE.test(trimmed)) return true;
2364
+ if (trimmed.startsWith("//") || trimmed.startsWith("#") && !trimmed.startsWith("#!")) {
2365
+ return true;
2366
+ }
2367
+ return false;
2368
+ }
2136
2369
  function extractStructuralBody(roles, roleName, options) {
2137
2370
  const value = roles.get(roleName);
2138
2371
  if (!value) return [];
@@ -2232,11 +2465,33 @@ function findMatchingBracket(str, start) {
2232
2465
  return str.length - 1;
2233
2466
  }
2234
2467
  function renderExplicit(node) {
2468
+ let result;
2235
2469
  if (node.kind === "compound") {
2236
2470
  const compoundNode = node;
2237
2471
  const renderedStatements = compoundNode.statements.map((stmt) => renderExplicit(stmt));
2238
- return renderedStatements.join(` ${compoundNode.chainType} `);
2472
+ result = renderedStatements.join(` ${compoundNode.chainType} `);
2473
+ } else {
2474
+ result = renderBracketCommand(node);
2475
+ }
2476
+ if (node.annotations && node.annotations.length > 0) {
2477
+ const annParts = node.annotations.map(
2478
+ (ann) => ann.value !== void 0 ? `@${ann.name}(${ann.value})` : `@${ann.name}`
2479
+ );
2480
+ return annParts.join(" ") + " " + result;
2239
2481
  }
2482
+ return result;
2483
+ }
2484
+ function renderDocument(envelope) {
2485
+ const lines = [];
2486
+ if (envelope.lseVersion && envelope.lseVersion !== "1.0") {
2487
+ lines.push(`#!lse ${envelope.lseVersion}`);
2488
+ }
2489
+ for (const node of envelope.nodes) {
2490
+ lines.push(renderExplicit(node));
2491
+ }
2492
+ return lines.join("\n");
2493
+ }
2494
+ function renderBracketCommand(node) {
2240
2495
  const parts = [node.action];
2241
2496
  for (const [role, value] of node.roles) {
2242
2497
  if (value.type === "flag") {
@@ -2570,12 +2825,16 @@ function toProtocolJSON(node) {
2570
2825
  );
2571
2826
  }
2572
2827
  if (node.diagnostics && node.diagnostics.length > 0) {
2573
- result.diagnostics = node.diagnostics.map((d) => ({
2574
- level: d.level,
2575
- role: d.role,
2576
- message: d.message,
2577
- code: d.code
2578
- }));
2828
+ result.diagnostics = node.diagnostics.map((d) => {
2829
+ const json = {
2830
+ level: d.severity,
2831
+ message: d.message
2832
+ };
2833
+ if (d.code) json.code = d.code;
2834
+ if (d.source) json.source = d.source;
2835
+ if (d.suggestions && d.suggestions.length > 0) json.suggestions = [...d.suggestions];
2836
+ return json;
2837
+ });
2579
2838
  }
2580
2839
  return result;
2581
2840
  }
@@ -2623,10 +2882,11 @@ function fromProtocolJSON(json) {
2623
2882
  (a) => a.value !== void 0 ? { name: a.name, value: a.value } : { name: a.name }
2624
2883
  );
2625
2884
  const diagnostics = json.diagnostics?.map((d) => ({
2626
- level: d.level,
2627
- role: d.role,
2885
+ severity: d.level,
2628
2886
  message: d.message,
2629
- code: d.code
2887
+ ...d.code ? { code: d.code } : {},
2888
+ ...d.source ? { source: d.source } : {},
2889
+ ...d.suggestions && d.suggestions.length > 0 ? { suggestions: d.suggestions } : {}
2630
2890
  }));
2631
2891
  if (json.trigger) {
2632
2892
  const eventName = json.trigger.event;
@@ -2767,7 +3027,7 @@ var VALID_VALUE_TYPES2 = /* @__PURE__ */ new Set([
2767
3027
  ]);
2768
3028
  var VALID_CHAIN_TYPES = /* @__PURE__ */ new Set(["then", "and", "async", "sequential", "pipe"]);
2769
3029
  var VALID_ASYNC_VARIANTS = /* @__PURE__ */ new Set(["all", "race"]);
2770
- var VALID_DIAGNOSTIC_LEVELS = /* @__PURE__ */ new Set(["error", "warning"]);
3030
+ var VALID_DIAGNOSTIC_LEVELS = /* @__PURE__ */ new Set(["error", "warning", "info"]);
2771
3031
  function validateProtocolJSON(json) {
2772
3032
  const diagnostics = [];
2773
3033
  if (typeof json !== "object" || json === null) {
@@ -2916,14 +3176,7 @@ function validateProtocolJSON(json) {
2916
3176
  diagnostics.push({
2917
3177
  severity: "error",
2918
3178
  code: "INVALID_DIAGNOSTIC_LEVEL",
2919
- message: `diagnostics[${i}].level must be "error" or "warning"`
2920
- });
2921
- }
2922
- if (typeof d.role !== "string") {
2923
- diagnostics.push({
2924
- severity: "error",
2925
- code: "MISSING_DIAGNOSTIC_ROLE",
2926
- message: `diagnostics[${i}] missing required field: role`
3179
+ message: `diagnostics[${i}].level must be "error", "warning", or "info"`
2927
3180
  });
2928
3181
  }
2929
3182
  if (typeof d.message !== "string") {
@@ -2933,11 +3186,11 @@ function validateProtocolJSON(json) {
2933
3186
  message: `diagnostics[${i}] missing required field: message`
2934
3187
  });
2935
3188
  }
2936
- if (typeof d.code !== "string") {
3189
+ if ("code" in d && typeof d.code !== "string") {
2937
3190
  diagnostics.push({
2938
- severity: "error",
2939
- code: "MISSING_DIAGNOSTIC_CODE",
2940
- message: `diagnostics[${i}] missing required field: code`
3191
+ severity: "warning",
3192
+ code: "INVALID_DIAGNOSTIC_CODE",
3193
+ message: `diagnostics[${i}].code must be a string if present`
2941
3194
  });
2942
3195
  }
2943
3196
  }
@@ -3370,6 +3623,121 @@ function bridgeEventModifiers(mods) {
3370
3623
  ...from !== void 0 ? { from } : {}
3371
3624
  };
3372
3625
  }
3626
+ function semanticNodeToRuntimeAST(node) {
3627
+ switch (node.kind) {
3628
+ case "event-handler":
3629
+ return convertEventHandler(node);
3630
+ case "conditional":
3631
+ return convertConditional(node);
3632
+ case "loop":
3633
+ return convertLoop(node);
3634
+ case "compound": {
3635
+ const compound = node;
3636
+ const commands = (compound.statements ?? []).map(semanticNodeToRuntimeAST);
3637
+ return {
3638
+ type: "CommandSequence",
3639
+ commands
3640
+ };
3641
+ }
3642
+ case "command":
3643
+ default:
3644
+ return convertCommand2(node);
3645
+ }
3646
+ }
3647
+ function convertEventHandler(eh) {
3648
+ const eventValue = eh.roles.get("event");
3649
+ const eventName = eventValue && "value" in eventValue ? String(eventValue.value) : "click";
3650
+ const modifiers = {};
3651
+ if (eh.eventModifiers) {
3652
+ if (eh.eventModifiers.once) modifiers.once = true;
3653
+ if (eh.eventModifiers.debounce) modifiers.debounce = eh.eventModifiers.debounce;
3654
+ if (eh.eventModifiers.throttle) modifiers.throttle = eh.eventModifiers.throttle;
3655
+ if (eh.eventModifiers.queue) modifiers.queue = eh.eventModifiers.queue;
3656
+ }
3657
+ return {
3658
+ type: "event",
3659
+ event: eventName,
3660
+ modifiers,
3661
+ body: (eh.body ?? []).map(semanticNodeToRuntimeAST)
3662
+ };
3663
+ }
3664
+ function convertCommand2(node) {
3665
+ const roles = {};
3666
+ const args = [];
3667
+ for (const [roleName, value] of node.roles) {
3668
+ const astValue = semanticValueToAST(value);
3669
+ roles[roleName] = astValue;
3670
+ args.push(astValue);
3671
+ }
3672
+ return {
3673
+ type: "command",
3674
+ name: node.action,
3675
+ args,
3676
+ roles
3677
+ };
3678
+ }
3679
+ function convertConditional(node) {
3680
+ const conditionValue = node.roles.get("condition");
3681
+ return {
3682
+ type: "command",
3683
+ name: "if",
3684
+ args: conditionValue ? [semanticValueToAST(conditionValue)] : [],
3685
+ condition: conditionValue ? semanticValueToAST(conditionValue) : void 0,
3686
+ thenBranch: (node.thenBranch ?? []).map(semanticNodeToRuntimeAST),
3687
+ elseBranch: (node.elseBranch ?? []).map(semanticNodeToRuntimeAST)
3688
+ };
3689
+ }
3690
+ function convertLoop(node) {
3691
+ const countValue = node.roles.get("patient") || node.roles.get("count");
3692
+ return {
3693
+ type: "command",
3694
+ name: "repeat",
3695
+ args: countValue ? [semanticValueToAST(countValue)] : [],
3696
+ loopVariant: node.loopVariant ?? "forever",
3697
+ body: (node.body ?? []).map(semanticNodeToRuntimeAST),
3698
+ loopVariable: node.loopVariable,
3699
+ indexVariable: node.indexVariable
3700
+ };
3701
+ }
3702
+ function semanticValueToAST(value) {
3703
+ switch (value.type) {
3704
+ case "selector":
3705
+ return { type: "selector", value: value.value };
3706
+ case "reference":
3707
+ return { type: "identifier", value: value.value };
3708
+ case "literal": {
3709
+ const lit = value;
3710
+ if (lit.dataType === "duration") {
3711
+ const str = String(lit.value);
3712
+ const match = /^(\d+(?:\.\d+)?)(ms|s)$/.exec(str);
3713
+ if (match) {
3714
+ const ms = match[2] === "s" ? parseFloat(match[1]) * 1e3 : parseFloat(match[1]);
3715
+ return { type: "literal", value: ms };
3716
+ }
3717
+ }
3718
+ return { type: "literal", value: lit.value };
3719
+ }
3720
+ case "property-path": {
3721
+ const pp = value;
3722
+ const objectAST = semanticValueToAST(pp.object);
3723
+ return {
3724
+ type: "member",
3725
+ object: objectAST,
3726
+ property: pp.property
3727
+ };
3728
+ }
3729
+ case "expression": {
3730
+ const expr = value;
3731
+ return { type: "expression", value: expr.raw };
3732
+ }
3733
+ case "flag": {
3734
+ const flag = value;
3735
+ return { type: "literal", value: flag.enabled };
3736
+ }
3737
+ default:
3738
+ return { type: "literal", value: String(value.value ?? "") };
3739
+ }
3740
+ }
3373
3741
  var DSLRegistry = class {
3374
3742
  constructor(config) {
3375
3743
  this.patterns = /* @__PURE__ */ new Map();
@@ -6291,6 +6659,136 @@ function normalized(stem, confidence, metadata) {
6291
6659
  }
6292
6660
  return { stem, confidence };
6293
6661
  }
6662
+ var BaseMorphologicalNormalizer = class {
6663
+ constructor(config) {
6664
+ this.language = config.language;
6665
+ this.config = {
6666
+ minWordLength: 3,
6667
+ minStemLength: 2,
6668
+ ...config
6669
+ };
6670
+ }
6671
+ /**
6672
+ * Standard normalization pipeline. Override for custom behavior.
6673
+ */
6674
+ normalize(word) {
6675
+ const lower = word.toLowerCase();
6676
+ if (this.isAlreadyNormalized(lower)) {
6677
+ return noChange(word);
6678
+ }
6679
+ if (this.config.reflexiveSuffixes) {
6680
+ const reflexive = this.tryReflexiveNormalization(lower);
6681
+ if (reflexive) return reflexive;
6682
+ }
6683
+ if (this.config.endings) {
6684
+ const conjugation = this.tryConjugationEndings(lower);
6685
+ if (conjugation) return conjugation;
6686
+ }
6687
+ if (this.config.suffixRules) {
6688
+ const suffix = this.trySuffixRules(lower);
6689
+ if (suffix) return suffix;
6690
+ }
6691
+ if (this.config.prefixRules) {
6692
+ const prefix = this.tryPrefixRules(lower);
6693
+ if (prefix) return prefix;
6694
+ }
6695
+ return noChange(word);
6696
+ }
6697
+ /**
6698
+ * Check if word is already in dictionary form (e.g., ends in -ar/-er/-ir).
6699
+ * Override for language-specific checks.
6700
+ */
6701
+ isAlreadyNormalized(word) {
6702
+ if (this.config.infinitiveEndings) {
6703
+ return this.config.infinitiveEndings.some((e) => word.endsWith(e));
6704
+ }
6705
+ return false;
6706
+ }
6707
+ /**
6708
+ * Try to strip reflexive suffixes and normalize the remainder.
6709
+ * Common in Romance languages (Spanish, Portuguese, French).
6710
+ */
6711
+ tryReflexiveNormalization(word) {
6712
+ const suffixes = this.config.reflexiveSuffixes;
6713
+ if (!suffixes) return null;
6714
+ for (const suffix of suffixes) {
6715
+ if (!word.endsWith(suffix)) continue;
6716
+ const remainder = word.slice(0, -suffix.length);
6717
+ if (this.isAlreadyNormalized(remainder)) {
6718
+ return normalized(remainder, 0.88, {
6719
+ removedSuffixes: [suffix],
6720
+ conjugationType: "reflexive"
6721
+ });
6722
+ }
6723
+ const inner = this.tryConjugationEndings(remainder) || this.trySuffixRules(remainder);
6724
+ if (inner && inner.stem !== remainder) {
6725
+ return normalized(inner.stem, inner.confidence * 0.95, {
6726
+ removedSuffixes: [suffix, ...inner.metadata?.removedSuffixes || []],
6727
+ conjugationType: "reflexive"
6728
+ });
6729
+ }
6730
+ }
6731
+ return null;
6732
+ }
6733
+ /**
6734
+ * Try conjugation endings (verb class endings like -ar/-er/-ir patterns).
6735
+ * Endings must be pre-sorted longest-first.
6736
+ */
6737
+ tryConjugationEndings(word) {
6738
+ const endings = this.config.endings;
6739
+ if (!endings) return null;
6740
+ const minStem = this.config.minStemLength ?? 2;
6741
+ for (const rule of endings) {
6742
+ if (!word.endsWith(rule.ending)) continue;
6743
+ const stemBase = word.slice(0, -rule.ending.length);
6744
+ if (stemBase.length < minStem) continue;
6745
+ const infinitive = stemBase + rule.stem;
6746
+ return normalized(infinitive, rule.confidence, {
6747
+ removedSuffixes: [rule.ending],
6748
+ conjugationType: rule.type
6749
+ });
6750
+ }
6751
+ return null;
6752
+ }
6753
+ /**
6754
+ * Try SuffixRule-style normalization.
6755
+ * Rules must be pre-sorted longest-first.
6756
+ */
6757
+ trySuffixRules(word) {
6758
+ const rules = this.config.suffixRules;
6759
+ if (!rules) return null;
6760
+ const defaultMinStem = this.config.minStemLength ?? 2;
6761
+ for (const rule of rules) {
6762
+ if (!word.endsWith(rule.pattern)) continue;
6763
+ const stem = word.slice(0, -rule.pattern.length);
6764
+ const minStem = rule.minStemLength ?? defaultMinStem;
6765
+ if (stem.length < minStem) continue;
6766
+ const result = stem + (rule.replacement || "");
6767
+ return normalized(result, rule.confidence, {
6768
+ removedSuffixes: [rule.pattern],
6769
+ ...rule.conjugationType && { conjugationType: rule.conjugationType }
6770
+ });
6771
+ }
6772
+ return null;
6773
+ }
6774
+ /**
6775
+ * Try PrefixRule-style normalization.
6776
+ */
6777
+ tryPrefixRules(word) {
6778
+ const rules = this.config.prefixRules;
6779
+ if (!rules) return null;
6780
+ for (const rule of rules) {
6781
+ if (!word.startsWith(rule.pattern)) continue;
6782
+ const remainder = word.slice(rule.pattern.length);
6783
+ const minRemaining = rule.minRemaining ?? this.config.minStemLength ?? 2;
6784
+ if (remainder.length < minRemaining) continue;
6785
+ return normalized(remainder, 1 - rule.confidencePenalty, {
6786
+ removedPrefixes: [rule.pattern]
6787
+ });
6788
+ }
6789
+ return null;
6790
+ }
6791
+ };
6294
6792
  function toJSONL(pairs) {
6295
6793
  return pairs.map((pair) => JSON.stringify(toJSONLRow(pair))).join("\n");
6296
6794
  }
@@ -6680,6 +7178,7 @@ function accumulateIndented(statements, config) {
6680
7178
  }
6681
7179
  export {
6682
7180
  AOTOrchestrator,
7181
+ BaseMorphologicalNormalizer,
6683
7182
  BaseTokenizer,
6684
7183
  CrossDomainDispatcher,
6685
7184
  DEFAULT_OPERATORS,
@@ -6762,8 +7261,10 @@ export {
6762
7261
  isCSSPropertyRef,
6763
7262
  isCSSSelector,
6764
7263
  isClassName,
7264
+ isCompoundSyntax,
6765
7265
  isContextAwareExtractor,
6766
7266
  isDigit,
7267
+ isDocumentSyntax,
6767
7268
  isEnvelope,
6768
7269
  isExplicitSyntax,
6769
7270
  isIdSelector,
@@ -6787,14 +7288,19 @@ export {
6787
7288
  needsDisambiguation,
6788
7289
  noChange,
6789
7290
  normalized,
7291
+ parseCompound,
7292
+ parseDocument,
6790
7293
  parseExplicit,
6791
7294
  parseJSONL,
6792
7295
  patternMatcher,
6793
7296
  registryToAOTBackends,
7297
+ renderDocument,
6794
7298
  renderExplicit,
6795
7299
  renderExpr,
6796
7300
  reorderRoles,
6797
7301
  semanticNodeToJSON,
7302
+ semanticNodeToRuntimeAST,
7303
+ semanticValueToAST,
6798
7304
  synthesizeFromSchemas,
6799
7305
  toEnvelopeJSON,
6800
7306
  toJSONL,