@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.
- package/dist/{dist-B64L7SG5.js → dist-V7SKUZF5.js} +552 -46
- package/dist/server.js +1307 -15
- package/package.json +4 -2
|
@@ -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
|
-
|
|
2049
|
-
|
|
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
|
-
|
|
2067
|
-
|
|
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
|
|
2081
|
-
|
|
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
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
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
|
|
2123
|
-
|
|
2124
|
-
|
|
2193
|
+
return attachDiagnostics(
|
|
2194
|
+
createLoopNode(command, roles, variant, loopBody, loopVariable, indexVariable, {
|
|
2195
|
+
sourceLanguage: "explicit"
|
|
2196
|
+
})
|
|
2197
|
+
);
|
|
2125
2198
|
}
|
|
2126
2199
|
default:
|
|
2127
|
-
return
|
|
2128
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
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
|
-
|
|
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 "
|
|
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: "
|
|
2939
|
-
code: "
|
|
2940
|
-
message: `diagnostics[${i}]
|
|
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,
|