@lokascript/language-server 2.7.2 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/dist/{dist-MRVDB2YM.js → dist-LIFKYPGW.js} +444 -134
- package/dist/server.js +1 -1
- package/package.json +8 -7
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@lokascript/language-server` will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2026-02-05
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Go to Definition**: Jump to behavior and function definitions with Ctrl+Click or F12
|
|
13
|
+
- **Find References**: Find all usages of a symbol with Shift+F12
|
|
14
|
+
- **Document Formatting**: Format hyperscript code with consistent indentation
|
|
15
|
+
- **Single-quote support**: HTML attributes with `_='...'` now work correctly
|
|
16
|
+
- **CRLF support**: Windows line endings are handled correctly in position mapping
|
|
17
|
+
- **Error logging**: All catch blocks now log errors for easier debugging
|
|
18
|
+
|
|
19
|
+
### Improved
|
|
20
|
+
|
|
21
|
+
- HTML extraction now supports both double and single quoted attributes
|
|
22
|
+
- Position mapping correctly handles all line ending types (LF, CRLF, CR)
|
|
23
|
+
- Better error messages in diagnostics
|
|
24
|
+
- Comprehensive test coverage (138 tests)
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- Position mapping for multiline hyperscript attributes
|
|
29
|
+
- Cursor position calculation with different line endings
|
|
30
|
+
|
|
31
|
+
## [0.1.0] - Initial Release
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **Diagnostics**: Real-time error detection and warnings
|
|
36
|
+
- **Completions**: Context-aware keyword and selector suggestions
|
|
37
|
+
- **Hover**: Documentation on hover for commands and keywords
|
|
38
|
+
- **Document Symbols**: Outline view showing event handlers, behaviors, and functions
|
|
39
|
+
- **Code Actions**: Quick fixes for common issues
|
|
40
|
+
- **Multilingual support**: Works with 21 human languages via `@lokascript/semantic`
|
|
41
|
+
- **HTML support**: Extracts hyperscript from `_="..."` attributes and `<script type="text/hyperscript">` tags
|
|
@@ -184,6 +184,26 @@ function filterBySeverity(diagnostics, severity) {
|
|
|
184
184
|
return diagnostics.filter((d) => d.severity === severity);
|
|
185
185
|
}
|
|
186
186
|
function defineCommand(schema) {
|
|
187
|
+
if (typeof schema !== "object" || schema === null) {
|
|
188
|
+
throw new TypeError(`defineCommand: expected a schema object, got ${describeValue(schema)}.`);
|
|
189
|
+
}
|
|
190
|
+
if (typeof schema.action !== "string" || schema.action.length === 0) {
|
|
191
|
+
throw new TypeError(
|
|
192
|
+
`defineCommand: 'action' must be a non-empty string (got ${describeValue(schema.action)}).`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
if (!Array.isArray(schema.roles)) {
|
|
196
|
+
throw new TypeError(
|
|
197
|
+
`defineCommand("${schema.action}"): 'roles' must be an array of RoleSpec (got ${describeValue(schema.roles)}). Build entries with defineRole().`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
for (const [index, role] of schema.roles.entries()) {
|
|
201
|
+
if (typeof role !== "object" || role === null || typeof role.role !== "string") {
|
|
202
|
+
throw new TypeError(
|
|
203
|
+
`defineCommand("${schema.action}"): roles[${index}] must be a RoleSpec with a string 'role' field (got ${describeValue(role)}). Build it with defineRole().`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
187
207
|
return {
|
|
188
208
|
description: schema.description || `${schema.action} command`,
|
|
189
209
|
category: schema.category || "general",
|
|
@@ -203,8 +223,16 @@ function defineRole(role) {
|
|
|
203
223
|
};
|
|
204
224
|
}
|
|
205
225
|
function getRoleSpec(schema, role) {
|
|
226
|
+
if (!schema || !Array.isArray(schema.roles)) return void 0;
|
|
206
227
|
return schema.roles.find((r) => r.role === role);
|
|
207
228
|
}
|
|
229
|
+
function describeValue(value) {
|
|
230
|
+
if (value === null) return "null";
|
|
231
|
+
if (Array.isArray(value)) return `an array of length ${value.length}`;
|
|
232
|
+
if (typeof value === "string") return `the string ${JSON.stringify(value)}`;
|
|
233
|
+
if (typeof value === "object") return "an object";
|
|
234
|
+
return typeof value === "undefined" ? "undefined" : String(value);
|
|
235
|
+
}
|
|
208
236
|
var DEFAULT_REFERENCES = /* @__PURE__ */ new Set([
|
|
209
237
|
"me",
|
|
210
238
|
"you",
|
|
@@ -212,7 +240,14 @@ var DEFAULT_REFERENCES = /* @__PURE__ */ new Set([
|
|
|
212
240
|
"result",
|
|
213
241
|
"event",
|
|
214
242
|
"target",
|
|
215
|
-
"body"
|
|
243
|
+
"body",
|
|
244
|
+
// DOM context globals — as reference-typed as `body`, and already advertised as
|
|
245
|
+
// references by core's REFERENCE_KEYWORDS and the parser's PROPERTY_ACCESS_BASES.
|
|
246
|
+
// Without them here, a foreign surface (ja ドキュメント, es documento) lexes as a
|
|
247
|
+
// keyword but fails isValidReference, degrades to a literal, and leaks verbatim.
|
|
248
|
+
"document",
|
|
249
|
+
"window",
|
|
250
|
+
"detail"
|
|
216
251
|
]);
|
|
217
252
|
function isValidReference(value, referenceSet = DEFAULT_REFERENCES) {
|
|
218
253
|
return referenceSet.has(value);
|
|
@@ -227,16 +262,6 @@ var STRUCTURAL_ROLES = /* @__PURE__ */ new Set([
|
|
|
227
262
|
"catch",
|
|
228
263
|
"finally"
|
|
229
264
|
]);
|
|
230
|
-
function isNestedCommand(value) {
|
|
231
|
-
const inner = value.slice(1, -1);
|
|
232
|
-
let depth = 0;
|
|
233
|
-
for (const ch of inner) {
|
|
234
|
-
if (ch === "[") depth++;
|
|
235
|
-
else if (ch === "]") depth--;
|
|
236
|
-
else if (depth === 0 && (ch === " " || ch === ":")) return true;
|
|
237
|
-
}
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
240
265
|
function parseExplicit(input, options = {}) {
|
|
241
266
|
const trimmed = input.trim();
|
|
242
267
|
const collect = options.collectDiagnostics === true;
|
|
@@ -324,7 +349,7 @@ function parseExplicit(input, options = {}) {
|
|
|
324
349
|
}
|
|
325
350
|
const role = roleName;
|
|
326
351
|
const valueStr = token.slice(colonIndex + 1);
|
|
327
|
-
if (STRUCTURAL_ROLES.has(roleName) && valueStr.startsWith("[")
|
|
352
|
+
if (STRUCTURAL_ROLES.has(roleName) && valueStr.startsWith("[")) {
|
|
328
353
|
const nestedEnd = findMatchingBracket(token, colonIndex + 1);
|
|
329
354
|
const nestedSyntax = token.slice(colonIndex + 1, nestedEnd + 1);
|
|
330
355
|
roles.set(role, { type: "expression", raw: nestedSyntax });
|
|
@@ -576,26 +601,63 @@ function extractStructuralBody(roles, roleName, options) {
|
|
|
576
601
|
if (value.type === "expression") return [parseExplicit(value.raw, options)];
|
|
577
602
|
return [];
|
|
578
603
|
}
|
|
604
|
+
function hasUnquotedCombinator(value) {
|
|
605
|
+
let quote = "";
|
|
606
|
+
for (const ch of value) {
|
|
607
|
+
if (quote) {
|
|
608
|
+
if (ch === quote) quote = "";
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
if (ch === '"' || ch === "'") {
|
|
612
|
+
quote = ch;
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (ch === " " || ch === ">" || ch === "+" || ch === "~" || ch === ",") return true;
|
|
616
|
+
}
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
579
619
|
function inferSelectorKind(value) {
|
|
580
620
|
if (!value) return void 0;
|
|
621
|
+
if (hasUnquotedCombinator(value)) return "complex";
|
|
581
622
|
if (value.startsWith("#")) return "id";
|
|
582
623
|
if (value.startsWith(".")) return "class";
|
|
583
624
|
if (value.startsWith("[")) return "attribute";
|
|
584
|
-
if (value.startsWith("
|
|
585
|
-
if (/[>+~ ]/.test(value) && value.length > 1) return "complex";
|
|
625
|
+
if (value.startsWith("*")) return "element";
|
|
586
626
|
return void 0;
|
|
587
627
|
}
|
|
628
|
+
function unescapeString(s) {
|
|
629
|
+
if (!s.includes("\\")) return s;
|
|
630
|
+
let out = "";
|
|
631
|
+
for (let i = 0; i < s.length; i++) {
|
|
632
|
+
if (s[i] === "\\" && i + 1 < s.length) {
|
|
633
|
+
const next = s[i + 1];
|
|
634
|
+
const mapped = next === "\\" ? "\\" : next === '"' ? '"' : next === "'" ? "'" : next === "n" ? "\n" : next === "r" ? "\r" : next === "t" ? " " : "";
|
|
635
|
+
if (mapped) {
|
|
636
|
+
out += mapped;
|
|
637
|
+
i++;
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
out += s[i];
|
|
642
|
+
}
|
|
643
|
+
return out;
|
|
644
|
+
}
|
|
588
645
|
function tokenizeExplicit(content) {
|
|
589
646
|
const tokens = [];
|
|
590
647
|
let current = "";
|
|
591
648
|
let inString = false;
|
|
592
649
|
let stringChar = "";
|
|
650
|
+
let inSelector = false;
|
|
593
651
|
let bracketDepth = 0;
|
|
594
652
|
for (let i = 0; i < content.length; i++) {
|
|
595
653
|
const char = content[i];
|
|
596
654
|
if (inString) {
|
|
597
655
|
current += char;
|
|
598
|
-
if (char === stringChar
|
|
656
|
+
if (char === stringChar) {
|
|
657
|
+
let backslashes = 0;
|
|
658
|
+
for (let j = i - 1; j >= 0 && content[j] === "\\"; j--) backslashes++;
|
|
659
|
+
if (backslashes % 2 === 0) inString = false;
|
|
660
|
+
}
|
|
599
661
|
continue;
|
|
600
662
|
}
|
|
601
663
|
if (char === '"' || char === "'") {
|
|
@@ -604,6 +666,16 @@ function tokenizeExplicit(content) {
|
|
|
604
666
|
current += char;
|
|
605
667
|
continue;
|
|
606
668
|
}
|
|
669
|
+
if (inSelector) {
|
|
670
|
+
current += char;
|
|
671
|
+
if (char === ">" && current.endsWith("/>")) inSelector = false;
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (char === "<" && (current === "" || current.endsWith(":"))) {
|
|
675
|
+
inSelector = true;
|
|
676
|
+
current += char;
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
607
679
|
if (char === "[") {
|
|
608
680
|
bracketDepth++;
|
|
609
681
|
current += char;
|
|
@@ -627,11 +699,19 @@ function tokenizeExplicit(content) {
|
|
|
627
699
|
return tokens;
|
|
628
700
|
}
|
|
629
701
|
function parseExplicitValue(valueStr, referenceSet) {
|
|
702
|
+
if (valueStr.startsWith("<")) {
|
|
703
|
+
if (!valueStr.endsWith("/>")) {
|
|
704
|
+
throw new Error(`Unterminated selector literal: "${valueStr}". Expected a closing "/>"`);
|
|
705
|
+
}
|
|
706
|
+
const inner = valueStr.slice(1, -2);
|
|
707
|
+
if (!inner) throw new Error(`Empty selector literal: "${valueStr}"`);
|
|
708
|
+
return createSelector(inner, inferSelectorKind(inner));
|
|
709
|
+
}
|
|
630
710
|
if (valueStr.startsWith("#") || valueStr.startsWith(".") || valueStr.startsWith("[") || valueStr.startsWith("@") || valueStr.startsWith("*")) {
|
|
631
711
|
return createSelector(valueStr, inferSelectorKind(valueStr));
|
|
632
712
|
}
|
|
633
713
|
if (valueStr.startsWith('"') || valueStr.startsWith("'")) {
|
|
634
|
-
return createLiteral(valueStr.slice(1, -1), "string");
|
|
714
|
+
return createLiteral(unescapeString(valueStr.slice(1, -1)), "string");
|
|
635
715
|
}
|
|
636
716
|
if (valueStr === "true") return createLiteral(true, "boolean");
|
|
637
717
|
if (valueStr === "false") return createLiteral(false, "boolean");
|
|
@@ -730,16 +810,23 @@ function renderBracketCommand(node) {
|
|
|
730
810
|
}
|
|
731
811
|
return `[${parts.join(" ")}]`;
|
|
732
812
|
}
|
|
813
|
+
function needsSelectorWrap(value) {
|
|
814
|
+
if (value.includes(" ")) return true;
|
|
815
|
+
const first = value[0];
|
|
816
|
+
return !(first === "#" || first === "." || first === "@" || first === "*");
|
|
817
|
+
}
|
|
733
818
|
function valueToString(value) {
|
|
734
819
|
switch (value.type) {
|
|
735
820
|
case "literal":
|
|
736
821
|
if (typeof value.value === "string") {
|
|
737
|
-
if (value.dataType === "string" || /\s/.test(value.value))
|
|
822
|
+
if (value.dataType === "string" || /\s/.test(value.value))
|
|
823
|
+
return JSON.stringify(value.value);
|
|
738
824
|
return value.value;
|
|
739
825
|
}
|
|
740
826
|
return String(value.value);
|
|
741
827
|
case "selector":
|
|
742
|
-
return value.value;
|
|
828
|
+
if (!value.value) return value.value;
|
|
829
|
+
return needsSelectorWrap(value.value) ? `<${value.value}/>` : value.value;
|
|
743
830
|
case "reference":
|
|
744
831
|
return value.value;
|
|
745
832
|
case "property-path":
|
|
@@ -837,6 +924,7 @@ function toProtocolJSON(node) {
|
|
|
837
924
|
if (node.diagnostics && node.diagnostics.length > 0) {
|
|
838
925
|
result.diagnostics = node.diagnostics.map((d) => {
|
|
839
926
|
const json = { level: d.severity, message: d.message };
|
|
927
|
+
if (d.role) json.role = d.role;
|
|
840
928
|
if (d.code) json.code = d.code;
|
|
841
929
|
if (d.source) json.source = d.source;
|
|
842
930
|
if (d.suggestions && d.suggestions.length > 0) json.suggestions = [...d.suggestions];
|
|
@@ -921,6 +1009,7 @@ function fromProtocolJSON(json) {
|
|
|
921
1009
|
const diagnostics = json.diagnostics?.map((d) => ({
|
|
922
1010
|
severity: d.level,
|
|
923
1011
|
message: d.message,
|
|
1012
|
+
...d.role ? { role: d.role } : {},
|
|
924
1013
|
...d.code ? { code: d.code } : {},
|
|
925
1014
|
...d.source ? { source: d.source } : {},
|
|
926
1015
|
...d.suggestions && d.suggestions.length > 0 ? { suggestions: d.suggestions } : {}
|
|
@@ -2584,10 +2673,12 @@ function buildTokens(schema, profile, keyword, alternatives) {
|
|
|
2584
2673
|
addRoleWithMarker(tokens, role, profile);
|
|
2585
2674
|
}
|
|
2586
2675
|
} else if (profile.wordOrder === "SOV") {
|
|
2676
|
+
const preVerb = [];
|
|
2677
|
+
const postVerb = [];
|
|
2587
2678
|
for (const role of sortedRoles) {
|
|
2588
|
-
addRoleWithMarker(
|
|
2679
|
+
addRoleWithMarker(role.sovSlot === "postVerb" ? postVerb : preVerb, role, profile);
|
|
2589
2680
|
}
|
|
2590
|
-
tokens.push(keywordToken);
|
|
2681
|
+
tokens.push(...preVerb, keywordToken, ...postVerb);
|
|
2591
2682
|
} else if (profile.wordOrder === "VSO") {
|
|
2592
2683
|
tokens.push(keywordToken);
|
|
2593
2684
|
for (const role of sortedRoles) {
|
|
@@ -2670,34 +2761,145 @@ function buildExtractionRules(schema, profile) {
|
|
|
2670
2761
|
return rules;
|
|
2671
2762
|
}
|
|
2672
2763
|
function buildFormatString(schema, profile, keyword) {
|
|
2673
|
-
const
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
for (const role of
|
|
2764
|
+
const isSOV = profile.wordOrder === "SOV";
|
|
2765
|
+
const preVerb = [];
|
|
2766
|
+
const postVerb = [];
|
|
2767
|
+
const sortedRoles = sortRolesByWordOrder(schema.roles, profile.wordOrder);
|
|
2768
|
+
for (const role of sortedRoles) {
|
|
2678
2769
|
const marker = getMarkerForRole(role, profile);
|
|
2679
2770
|
const roleName = `{${role.role}}`;
|
|
2771
|
+
const bucket = isSOV && role.sovSlot === "postVerb" ? postVerb : preVerb;
|
|
2680
2772
|
if (marker) {
|
|
2681
2773
|
const markerInfo = profile.roleMarkers?.[role.role];
|
|
2682
2774
|
if (markerInfo?.position === "after") {
|
|
2683
|
-
|
|
2775
|
+
bucket.push(`${roleName} ${marker}`);
|
|
2684
2776
|
} else {
|
|
2685
|
-
|
|
2777
|
+
bucket.push(`${marker} ${roleName}`);
|
|
2686
2778
|
}
|
|
2687
2779
|
} else {
|
|
2688
|
-
|
|
2780
|
+
bucket.push(roleName);
|
|
2689
2781
|
}
|
|
2690
2782
|
}
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
return parts.join(" ");
|
|
2783
|
+
return (isSOV ? [...preVerb, keyword, ...postVerb] : [keyword, ...preVerb, ...postVerb]).join(
|
|
2784
|
+
" "
|
|
2785
|
+
);
|
|
2695
2786
|
}
|
|
2696
2787
|
function generatePatternVariants(schema, profile, config = defaultConfig) {
|
|
2697
2788
|
const patterns = [];
|
|
2698
2789
|
patterns.push(generatePattern(schema, profile, config));
|
|
2699
2790
|
return patterns;
|
|
2700
2791
|
}
|
|
2792
|
+
function lookupKeyword(keywords, action, language) {
|
|
2793
|
+
return keywords[action]?.[language] ?? action;
|
|
2794
|
+
}
|
|
2795
|
+
function lookupMarker(markers, marker, language) {
|
|
2796
|
+
return markers[marker]?.[language] ?? marker;
|
|
2797
|
+
}
|
|
2798
|
+
function buildPhrase(...parts) {
|
|
2799
|
+
return parts.filter(Boolean).join(" ");
|
|
2800
|
+
}
|
|
2801
|
+
function buildTablesFromProfiles(schemas, profiles) {
|
|
2802
|
+
const keywords = {};
|
|
2803
|
+
const markers = {};
|
|
2804
|
+
for (const profile of profiles) {
|
|
2805
|
+
for (const [action, kw] of Object.entries(profile.keywords)) {
|
|
2806
|
+
if (!keywords[action]) keywords[action] = {};
|
|
2807
|
+
keywords[action][profile.code] = kw.primary;
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
for (const schema of schemas) {
|
|
2811
|
+
for (const role of schema.roles) {
|
|
2812
|
+
if (role.markerOverride) {
|
|
2813
|
+
const markerKey = role.role;
|
|
2814
|
+
if (!markers[markerKey]) markers[markerKey] = {};
|
|
2815
|
+
for (const [lang, markerText] of Object.entries(role.markerOverride)) {
|
|
2816
|
+
markers[markerKey][lang] = markerText;
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
for (const profile of profiles) {
|
|
2822
|
+
if (profile.roleMarkers) {
|
|
2823
|
+
for (const [markerKey, markerDef] of Object.entries(profile.roleMarkers)) {
|
|
2824
|
+
if (!markers[markerKey]) markers[markerKey] = {};
|
|
2825
|
+
markers[markerKey][profile.code] = markerDef.primary;
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
return { keywords, markers };
|
|
2830
|
+
}
|
|
2831
|
+
function detectWordOrders(profiles) {
|
|
2832
|
+
const sovLanguages = /* @__PURE__ */ new Set();
|
|
2833
|
+
const vsoLanguages = /* @__PURE__ */ new Set();
|
|
2834
|
+
for (const profile of profiles) {
|
|
2835
|
+
if (profile.wordOrder === "SOV") sovLanguages.add(profile.code);
|
|
2836
|
+
if (profile.wordOrder === "VSO") vsoLanguages.add(profile.code);
|
|
2837
|
+
}
|
|
2838
|
+
return { sovLanguages, vsoLanguages };
|
|
2839
|
+
}
|
|
2840
|
+
function createSchemaRenderer(schemas, profiles) {
|
|
2841
|
+
const tables = buildRenderTables(schemas, profiles);
|
|
2842
|
+
return {
|
|
2843
|
+
render(node, language) {
|
|
2844
|
+
return renderFromSchema(node, language, tables) ?? node.action;
|
|
2845
|
+
}
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2848
|
+
function createDomainRenderer(config) {
|
|
2849
|
+
const { schemas, profiles, overrides } = config;
|
|
2850
|
+
let tables;
|
|
2851
|
+
return (node, language) => {
|
|
2852
|
+
const override = overrides?.[node.action];
|
|
2853
|
+
if (override) return override(node, language);
|
|
2854
|
+
tables ?? (tables = buildRenderTables(schemas, profiles));
|
|
2855
|
+
return renderFromSchema(node, language, tables);
|
|
2856
|
+
};
|
|
2857
|
+
}
|
|
2858
|
+
function buildRenderTables(schemas, profiles) {
|
|
2859
|
+
const { keywords, markers } = buildTablesFromProfiles(schemas, profiles);
|
|
2860
|
+
const { sovLanguages } = detectWordOrders(profiles);
|
|
2861
|
+
const markerPositions = {};
|
|
2862
|
+
for (const profile of profiles) {
|
|
2863
|
+
for (const [role, markerDef] of Object.entries(profile.roleMarkers ?? {})) {
|
|
2864
|
+
if (!markerDef.position) continue;
|
|
2865
|
+
markerPositions[role] ?? (markerPositions[role] = {});
|
|
2866
|
+
markerPositions[role][profile.code] = markerDef.position;
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
const schemaMap = /* @__PURE__ */ new Map();
|
|
2870
|
+
for (const s of schemas) schemaMap.set(s.action, s);
|
|
2871
|
+
return { keywords, markers, markerPositions, sovLanguages, schemaMap };
|
|
2872
|
+
}
|
|
2873
|
+
function renderFromSchema(node, language, tables) {
|
|
2874
|
+
const schema = tables.schemaMap.get(node.action);
|
|
2875
|
+
if (!schema) return null;
|
|
2876
|
+
const keyword = lookupKeyword(tables.keywords, node.action, language);
|
|
2877
|
+
const isSOV = tables.sovLanguages.has(language);
|
|
2878
|
+
const ordered = sortRolesByWordOrder([...schema.roles], isSOV ? "SOV" : "SVO");
|
|
2879
|
+
const preVerb = [];
|
|
2880
|
+
const postVerb = [];
|
|
2881
|
+
for (const role of ordered) {
|
|
2882
|
+
let value = extractRoleValue(node, role.role);
|
|
2883
|
+
if (!value && role.default !== void 0) {
|
|
2884
|
+
value = String(extractValue(role.default));
|
|
2885
|
+
}
|
|
2886
|
+
if (!value) continue;
|
|
2887
|
+
if (role.quoteMultiword && /\s/.test(value) && !/^(["']).*\1$/.test(value)) {
|
|
2888
|
+
value = `"${value}"`;
|
|
2889
|
+
}
|
|
2890
|
+
const markerText = role.renderOverride?.[language] ?? role.markerOverride?.[language] ?? role.renderOverride?.["*"] ?? tables.markers[role.role]?.[language];
|
|
2891
|
+
const markerPosition = role.markerPositionOverride?.[language] ?? role.markerPosition ?? tables.markerPositions[role.role]?.[language] ?? (isSOV ? "after" : "before");
|
|
2892
|
+
const bucket = isSOV && role.sovSlot === "postVerb" ? postVerb : preVerb;
|
|
2893
|
+
if (markerText && markerPosition === "before") {
|
|
2894
|
+
bucket.push(markerText, value);
|
|
2895
|
+
} else if (markerText) {
|
|
2896
|
+
bucket.push(value, markerText);
|
|
2897
|
+
} else {
|
|
2898
|
+
bucket.push(value);
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
return isSOV ? buildPhrase(...preVerb, keyword, ...postVerb) : buildPhrase(keyword, ...preVerb, ...postVerb);
|
|
2902
|
+
}
|
|
2701
2903
|
function reorderRoles(roles, targetOrder) {
|
|
2702
2904
|
const result = [];
|
|
2703
2905
|
const usedRoles = /* @__PURE__ */ new Set();
|
|
@@ -3125,6 +3327,31 @@ var LatinExtendedIdentifierExtractor = class {
|
|
|
3125
3327
|
return { value: input.slice(position, end), length: end - position };
|
|
3126
3328
|
}
|
|
3127
3329
|
};
|
|
3330
|
+
var SELECTOR_BODY_CHAR = /[\p{L}\p{N}_-]/u;
|
|
3331
|
+
var PARTICLE_SCRIPT_CHAR = /[\p{sc=Han}\p{sc=Hiragana}\p{sc=Katakana}\p{sc=Hangul}]/u;
|
|
3332
|
+
function isSelectorBodyChar(char) {
|
|
3333
|
+
return SELECTOR_BODY_CHAR.test(char) && !PARTICLE_SCRIPT_CHAR.test(char);
|
|
3334
|
+
}
|
|
3335
|
+
var CssSelectorExtractor = class {
|
|
3336
|
+
constructor() {
|
|
3337
|
+
this.name = "css-selector";
|
|
3338
|
+
}
|
|
3339
|
+
canExtract(input, position) {
|
|
3340
|
+
const char = input[position];
|
|
3341
|
+
if (char !== "#" && char !== ".") return false;
|
|
3342
|
+
const next = input[position + 1];
|
|
3343
|
+
if (next === void 0) return false;
|
|
3344
|
+
return next === "_" || next === "-" || /\p{L}/u.test(next) && !PARTICLE_SCRIPT_CHAR.test(next);
|
|
3345
|
+
}
|
|
3346
|
+
extract(input, position) {
|
|
3347
|
+
let end = position + 1;
|
|
3348
|
+
while (end < input.length && isSelectorBodyChar(input[end])) {
|
|
3349
|
+
end++;
|
|
3350
|
+
}
|
|
3351
|
+
if (end === position + 1) return null;
|
|
3352
|
+
return { value: input.slice(position, end), length: end - position };
|
|
3353
|
+
}
|
|
3354
|
+
};
|
|
3128
3355
|
var WhitespaceExtractor = class {
|
|
3129
3356
|
constructor() {
|
|
3130
3357
|
this.name = "whitespace";
|
|
@@ -3844,10 +4071,17 @@ function semanticValueToAST(value) {
|
|
|
3844
4071
|
}
|
|
3845
4072
|
}
|
|
3846
4073
|
var DSLRegistry = class {
|
|
3847
|
-
|
|
4074
|
+
/**
|
|
4075
|
+
* @param partiallyTranslatedActions Actions permitted to lack a keyword in
|
|
4076
|
+
* some configured languages — extension commands, which may be added for a
|
|
4077
|
+
* subset of the DSL's languages. A built-in command missing a keyword stays
|
|
4078
|
+
* an error, since that is a domain-authoring mistake.
|
|
4079
|
+
*/
|
|
4080
|
+
constructor(config, partiallyTranslatedActions = /* @__PURE__ */ new Set()) {
|
|
3848
4081
|
this.patterns = /* @__PURE__ */ new Map();
|
|
3849
4082
|
this.tokenizers = /* @__PURE__ */ new Map();
|
|
3850
4083
|
this.schemas = config.schemas;
|
|
4084
|
+
this.partiallyTranslatedActions = partiallyTranslatedActions;
|
|
3851
4085
|
for (const lang of config.languages) {
|
|
3852
4086
|
this.registerLanguage(lang);
|
|
3853
4087
|
}
|
|
@@ -3856,6 +4090,9 @@ var DSLRegistry = class {
|
|
|
3856
4090
|
this.tokenizers.set(lang.code, lang.tokenizer);
|
|
3857
4091
|
const patterns = [];
|
|
3858
4092
|
for (const schema of this.schemas) {
|
|
4093
|
+
if (this.partiallyTranslatedActions.has(schema.action) && !lang.patternProfile.keywords[schema.action]) {
|
|
4094
|
+
continue;
|
|
4095
|
+
}
|
|
3859
4096
|
const pattern = generatePattern(schema, lang.patternProfile);
|
|
3860
4097
|
patterns.push(pattern);
|
|
3861
4098
|
}
|
|
@@ -3872,13 +4109,25 @@ var DSLRegistry = class {
|
|
|
3872
4109
|
}
|
|
3873
4110
|
};
|
|
3874
4111
|
var MultilingualDSLImpl = class {
|
|
3875
|
-
constructor(config, registry, transformer) {
|
|
4112
|
+
constructor(config, registry, transformer, profileProvider) {
|
|
3876
4113
|
this.registry = registry;
|
|
3877
4114
|
this.matcher = new PatternMatcher();
|
|
3878
4115
|
this.transformer = transformer;
|
|
4116
|
+
this.profileProvider = profileProvider;
|
|
3879
4117
|
if (config.codeGenerator) {
|
|
3880
4118
|
this.codeGenerator = config.codeGenerator;
|
|
3881
4119
|
}
|
|
4120
|
+
if (config.renderer) {
|
|
4121
|
+
this.domainRenderer = config.renderer;
|
|
4122
|
+
}
|
|
4123
|
+
this.extensionRenderers = /* @__PURE__ */ new Map();
|
|
4124
|
+
for (const extension of config.extensions ?? []) {
|
|
4125
|
+
if (extension.render) this.extensionRenderers.set(extension.schema.action, extension.render);
|
|
4126
|
+
}
|
|
4127
|
+
this.schemaFallbackRenderer = createDomainRenderer({
|
|
4128
|
+
schemas: config.schemas,
|
|
4129
|
+
profiles: config.languages.map((l) => l.patternProfile)
|
|
4130
|
+
});
|
|
3882
4131
|
const schemaMap = new Map(config.schemas.map((s) => [s.action, s]));
|
|
3883
4132
|
this.schemaLookup = {
|
|
3884
4133
|
getSchema: (action) => schemaMap.get(action)
|
|
@@ -3940,6 +4189,13 @@ var MultilingualDSLImpl = class {
|
|
|
3940
4189
|
}
|
|
3941
4190
|
translate(input, fromLanguage, toLanguage) {
|
|
3942
4191
|
if (isExplicitSyntax(input)) return input;
|
|
4192
|
+
for (const language of [fromLanguage, toLanguage]) {
|
|
4193
|
+
if (!this.profileProvider.getProfile(language)) {
|
|
4194
|
+
throw new Error(
|
|
4195
|
+
`translate() requires a grammar profile for language "${language}", but none is configured. Set 'grammarProfile' on the LanguageConfig for "${language}" in createMultilingualDSL() (parse/validate/compile do not need it), or inject a custom 'profileProvider'.`
|
|
4196
|
+
);
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
3943
4199
|
return this.transformer.transform(input, fromLanguage, toLanguage);
|
|
3944
4200
|
}
|
|
3945
4201
|
compile(input, language) {
|
|
@@ -3968,6 +4224,18 @@ var MultilingualDSLImpl = class {
|
|
|
3968
4224
|
};
|
|
3969
4225
|
}
|
|
3970
4226
|
}
|
|
4227
|
+
render(node, language) {
|
|
4228
|
+
const extensionRenderer = this.extensionRenderers.get(node.action);
|
|
4229
|
+
if (extensionRenderer) {
|
|
4230
|
+
const rendered = extensionRenderer(node, language);
|
|
4231
|
+
if (rendered != null) return rendered;
|
|
4232
|
+
}
|
|
4233
|
+
if (this.domainRenderer) {
|
|
4234
|
+
const rendered = this.domainRenderer(node, language);
|
|
4235
|
+
if (rendered != null) return rendered;
|
|
4236
|
+
}
|
|
4237
|
+
return this.schemaFallbackRenderer(node, language);
|
|
4238
|
+
}
|
|
3971
4239
|
getSupportedLanguages() {
|
|
3972
4240
|
return this.registry.getSupportedLanguages();
|
|
3973
4241
|
}
|
|
@@ -3992,15 +4260,80 @@ function createDefaultProfileProvider(config) {
|
|
|
3992
4260
|
}
|
|
3993
4261
|
return new InMemoryProfileProvider(profiles);
|
|
3994
4262
|
}
|
|
4263
|
+
function applyExtensions(config) {
|
|
4264
|
+
const extensions = config.extensions;
|
|
4265
|
+
if (!extensions || extensions.length === 0) return config;
|
|
4266
|
+
const configuredLanguages = new Set(config.languages.map((l) => l.code));
|
|
4267
|
+
const actions = new Set(config.schemas.map((s) => s.action));
|
|
4268
|
+
for (const extension of extensions) {
|
|
4269
|
+
const { action } = extension.schema;
|
|
4270
|
+
if (actions.has(action)) {
|
|
4271
|
+
throw new Error(
|
|
4272
|
+
`Extension command "${action}" collides with a command this DSL already defines. Pick a different action name.`
|
|
4273
|
+
);
|
|
4274
|
+
}
|
|
4275
|
+
actions.add(action);
|
|
4276
|
+
for (const code of Object.keys(extension.vocabulary)) {
|
|
4277
|
+
if (!configuredLanguages.has(code)) {
|
|
4278
|
+
throw new Error(
|
|
4279
|
+
`Extension command "${action}" supplies vocabulary for language "${code}", which is not configured on this DSL. Configured languages: ${[...configuredLanguages].join(", ")}.`
|
|
4280
|
+
);
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
const schemas = [...config.schemas, ...extensions.map((e) => e.schema)];
|
|
4285
|
+
const languages = config.languages.map((lang) => {
|
|
4286
|
+
const keywords = { ...lang.patternProfile.keywords };
|
|
4287
|
+
let roleMarkers = lang.patternProfile.roleMarkers;
|
|
4288
|
+
for (const extension of extensions) {
|
|
4289
|
+
const vocabulary = extension.vocabulary[lang.code];
|
|
4290
|
+
if (!vocabulary) continue;
|
|
4291
|
+
keywords[extension.schema.action] = { ...vocabulary.keyword };
|
|
4292
|
+
if (vocabulary.roleMarkers) {
|
|
4293
|
+
roleMarkers = { ...roleMarkers, ...vocabulary.roleMarkers };
|
|
4294
|
+
}
|
|
4295
|
+
}
|
|
4296
|
+
return {
|
|
4297
|
+
...lang,
|
|
4298
|
+
patternProfile: {
|
|
4299
|
+
...lang.patternProfile,
|
|
4300
|
+
keywords,
|
|
4301
|
+
...roleMarkers !== void 0 && { roleMarkers }
|
|
4302
|
+
}
|
|
4303
|
+
};
|
|
4304
|
+
});
|
|
4305
|
+
const extensionGenerators = new Map(
|
|
4306
|
+
extensions.filter((e) => e.generate).map((e) => [e.schema.action, e.generate])
|
|
4307
|
+
);
|
|
4308
|
+
const baseGenerator = config.codeGenerator;
|
|
4309
|
+
const codeGenerator = extensionGenerators.size > 0 ? {
|
|
4310
|
+
generate(node) {
|
|
4311
|
+
const generate = extensionGenerators.get(node.action);
|
|
4312
|
+
if (generate) return generate(node);
|
|
4313
|
+
if (!baseGenerator) {
|
|
4314
|
+
throw new Error(`No code generator for action "${node.action}"`);
|
|
4315
|
+
}
|
|
4316
|
+
return baseGenerator.generate(node);
|
|
4317
|
+
}
|
|
4318
|
+
} : baseGenerator;
|
|
4319
|
+
return {
|
|
4320
|
+
...config,
|
|
4321
|
+
schemas,
|
|
4322
|
+
languages,
|
|
4323
|
+
...codeGenerator !== void 0 && { codeGenerator }
|
|
4324
|
+
};
|
|
4325
|
+
}
|
|
3995
4326
|
function createMultilingualDSL(config) {
|
|
3996
|
-
const
|
|
3997
|
-
const
|
|
4327
|
+
const effectiveConfig = applyExtensions(config);
|
|
4328
|
+
const extensionActions = new Set((config.extensions ?? []).map((e) => e.schema.action));
|
|
4329
|
+
const dictionary = effectiveConfig.dictionary ?? createDefaultDictionary(effectiveConfig);
|
|
4330
|
+
const profileProvider = effectiveConfig.profileProvider ?? createDefaultProfileProvider(effectiveConfig);
|
|
3998
4331
|
const transformer = new GrammarTransformer({
|
|
3999
4332
|
dictionary,
|
|
4000
4333
|
profileProvider
|
|
4001
4334
|
});
|
|
4002
|
-
const registry = new DSLRegistry(
|
|
4003
|
-
return new MultilingualDSLImpl(
|
|
4335
|
+
const registry = new DSLRegistry(effectiveConfig, extensionActions);
|
|
4336
|
+
return new MultilingualDSLImpl(effectiveConfig, registry, transformer, profileProvider);
|
|
4004
4337
|
}
|
|
4005
4338
|
function generatePrompt(config) {
|
|
4006
4339
|
const outputFormat = config.outputFormat ?? "both";
|
|
@@ -4085,7 +4418,9 @@ function buildProtocolSection() {
|
|
|
4085
4418
|
3. No spaces around the colon in role:value pairs
|
|
4086
4419
|
4. Strings with spaces must be quoted: \`patient:"hello world"\`
|
|
4087
4420
|
5. Selectors start with \`#\`, \`.\`, \`[\`, \`@\`, or \`*\`
|
|
4088
|
-
6.
|
|
4421
|
+
6. A selector containing a space, a combinator (\`>\` \`+\` \`~\`), or a comma must use a selector literal: \`patient:<ul > li/>\`, \`patient:<.a, .b/>\`
|
|
4422
|
+
7. Inside a structural role (\`body\`, \`then\`, \`else\`, \`condition\`, \`loop-body\`, \`variable\`, \`catch\`, \`finally\`) a \`[...]\` value is always a nested command. Write an attribute selector there as \`condition:<[data-active]/>\`
|
|
4423
|
+
8. Output must be valid bracket syntax: \`[action role:value ...]\``;
|
|
4089
4424
|
return {
|
|
4090
4425
|
id: "protocol",
|
|
4091
4426
|
title: "LSE Protocol",
|
|
@@ -4098,6 +4433,7 @@ function buildValueTypeSection() {
|
|
|
4098
4433
|
|
|
4099
4434
|
| Type | Syntax | Example |
|
|
4100
4435
|
|------|--------|---------|
|
|
4436
|
+
| Selector literal | Delimited by \`<\` and \`/>\` | \`<ul > li/>\`, \`<.a, .b/>\`, \`<[data-id]/>\` |
|
|
4101
4437
|
| Selector | Starts with \`#\` \`.\` \`[\` \`@\` \`*\` | \`#button\`, \`.active\`, \`[data-id]\` |
|
|
4102
4438
|
| String | Quoted with \`"\` or \`'\` | \`"hello world"\`, \`'json'\` |
|
|
4103
4439
|
| Boolean | Exact: \`true\` / \`false\` | \`visible:true\` |
|
|
@@ -4960,6 +5296,11 @@ var DomainRegistry = class {
|
|
|
4960
5296
|
rendered = typeof renderer === "function" ? renderer(node, to) : renderer.render(node, to);
|
|
4961
5297
|
} catch {
|
|
4962
5298
|
}
|
|
5299
|
+
} else if (dsl.render) {
|
|
5300
|
+
try {
|
|
5301
|
+
rendered = dsl.render(node, to);
|
|
5302
|
+
} catch {
|
|
5303
|
+
}
|
|
4963
5304
|
}
|
|
4964
5305
|
const roles = {};
|
|
4965
5306
|
for (const [key, value] of node.roles) {
|
|
@@ -5505,94 +5846,6 @@ async function registryToAOTBackends(registry) {
|
|
|
5505
5846
|
}
|
|
5506
5847
|
return backends;
|
|
5507
5848
|
}
|
|
5508
|
-
function lookupKeyword(keywords, action, language) {
|
|
5509
|
-
return keywords[action]?.[language] ?? action;
|
|
5510
|
-
}
|
|
5511
|
-
function lookupMarker(markers, marker, language) {
|
|
5512
|
-
return markers[marker]?.[language] ?? marker;
|
|
5513
|
-
}
|
|
5514
|
-
function buildPhrase(...parts) {
|
|
5515
|
-
return parts.filter(Boolean).join(" ");
|
|
5516
|
-
}
|
|
5517
|
-
function buildTablesFromProfiles(schemas, profiles) {
|
|
5518
|
-
const keywords = {};
|
|
5519
|
-
const markers = {};
|
|
5520
|
-
for (const profile of profiles) {
|
|
5521
|
-
for (const [action, kw] of Object.entries(profile.keywords)) {
|
|
5522
|
-
if (!keywords[action]) keywords[action] = {};
|
|
5523
|
-
keywords[action][profile.code] = kw.primary;
|
|
5524
|
-
}
|
|
5525
|
-
}
|
|
5526
|
-
for (const schema of schemas) {
|
|
5527
|
-
for (const role of schema.roles) {
|
|
5528
|
-
if (role.markerOverride) {
|
|
5529
|
-
const markerKey = role.role;
|
|
5530
|
-
if (!markers[markerKey]) markers[markerKey] = {};
|
|
5531
|
-
for (const [lang, markerText] of Object.entries(role.markerOverride)) {
|
|
5532
|
-
markers[markerKey][lang] = markerText;
|
|
5533
|
-
}
|
|
5534
|
-
}
|
|
5535
|
-
}
|
|
5536
|
-
}
|
|
5537
|
-
for (const profile of profiles) {
|
|
5538
|
-
if (profile.roleMarkers) {
|
|
5539
|
-
for (const [markerKey, markerDef] of Object.entries(profile.roleMarkers)) {
|
|
5540
|
-
if (!markers[markerKey]) markers[markerKey] = {};
|
|
5541
|
-
markers[markerKey][profile.code] = markerDef.primary;
|
|
5542
|
-
}
|
|
5543
|
-
}
|
|
5544
|
-
}
|
|
5545
|
-
return { keywords, markers };
|
|
5546
|
-
}
|
|
5547
|
-
function detectWordOrders(profiles) {
|
|
5548
|
-
const sovLanguages = /* @__PURE__ */ new Set();
|
|
5549
|
-
const vsoLanguages = /* @__PURE__ */ new Set();
|
|
5550
|
-
for (const profile of profiles) {
|
|
5551
|
-
if (profile.wordOrder === "SOV") sovLanguages.add(profile.code);
|
|
5552
|
-
if (profile.wordOrder === "VSO") vsoLanguages.add(profile.code);
|
|
5553
|
-
}
|
|
5554
|
-
return { sovLanguages, vsoLanguages };
|
|
5555
|
-
}
|
|
5556
|
-
function createSchemaRenderer(schemas, profiles) {
|
|
5557
|
-
const { keywords, markers } = buildTablesFromProfiles(schemas, profiles);
|
|
5558
|
-
const { sovLanguages } = detectWordOrders(profiles);
|
|
5559
|
-
const schemaMap = /* @__PURE__ */ new Map();
|
|
5560
|
-
for (const s of schemas) schemaMap.set(s.action, s);
|
|
5561
|
-
return {
|
|
5562
|
-
render(node, language) {
|
|
5563
|
-
const schema = schemaMap.get(node.action);
|
|
5564
|
-
if (!schema) return node.action;
|
|
5565
|
-
const keyword = lookupKeyword(keywords, node.action, language);
|
|
5566
|
-
const isSOV = sovLanguages.has(language);
|
|
5567
|
-
const roleParts = [];
|
|
5568
|
-
for (const role of schema.roles) {
|
|
5569
|
-
const value = extractRoleValue(node, role.role);
|
|
5570
|
-
if (!value && !role.required) continue;
|
|
5571
|
-
const markerText = role.markerOverride?.[language] ?? markers[role.role]?.[language] ?? void 0;
|
|
5572
|
-
roleParts.push({
|
|
5573
|
-
...markerText != null && { marker: markerText },
|
|
5574
|
-
value: value || "",
|
|
5575
|
-
role
|
|
5576
|
-
});
|
|
5577
|
-
}
|
|
5578
|
-
const parts = [];
|
|
5579
|
-
if (isSOV) {
|
|
5580
|
-
for (const rp of roleParts) {
|
|
5581
|
-
if (rp.value) parts.push(rp.value);
|
|
5582
|
-
if (rp.marker) parts.push(rp.marker);
|
|
5583
|
-
}
|
|
5584
|
-
parts.push(keyword);
|
|
5585
|
-
} else {
|
|
5586
|
-
parts.push(keyword);
|
|
5587
|
-
for (const rp of roleParts) {
|
|
5588
|
-
if (rp.marker) parts.push(rp.marker);
|
|
5589
|
-
if (rp.value) parts.push(rp.value);
|
|
5590
|
-
}
|
|
5591
|
-
}
|
|
5592
|
-
return buildPhrase(...parts);
|
|
5593
|
-
}
|
|
5594
|
-
};
|
|
5595
|
-
}
|
|
5596
5849
|
var TokenStreamImpl = class {
|
|
5597
5850
|
constructor(tokens, language) {
|
|
5598
5851
|
this.pos = 0;
|
|
@@ -5698,6 +5951,9 @@ function isQuote(char) {
|
|
|
5698
5951
|
function isDigit(char) {
|
|
5699
5952
|
return /\d/.test(char);
|
|
5700
5953
|
}
|
|
5954
|
+
function stripOptionalDiacritics(word) {
|
|
5955
|
+
return word.replace(/[ً-ْٰ]/g, "");
|
|
5956
|
+
}
|
|
5701
5957
|
function isAsciiLetter(char) {
|
|
5702
5958
|
return /[a-zA-Z]/.test(char);
|
|
5703
5959
|
}
|
|
@@ -6238,7 +6494,39 @@ var _BaseTokenizer = class _BaseTokenizer2 {
|
|
|
6238
6494
|
pos++;
|
|
6239
6495
|
}
|
|
6240
6496
|
}
|
|
6241
|
-
return new TokenStreamImpl(tokens, this.language);
|
|
6497
|
+
return new TokenStreamImpl(this.mergeColonQualifiedNames(tokens), this.language);
|
|
6498
|
+
}
|
|
6499
|
+
/**
|
|
6500
|
+
* Fuse `name` + `:qualifier` into ONE identifier (`draggable:start`).
|
|
6501
|
+
*
|
|
6502
|
+
* `:name` is hyperscript's local-variable sigil, but a colon IMMEDIATELY
|
|
6503
|
+
* preceded by an identifier is a qualifier (custom event namespace), not a
|
|
6504
|
+
* sigil. The English tokenizer already merges these inside
|
|
6505
|
+
* EnglishKeywordExtractor; this post-pass gives the other 23 languages the
|
|
6506
|
+
* same stream. Strict position adjacency is the discriminator: whitespace
|
|
6507
|
+
* between the tokens (`trigger :start`) breaks `end === start`, so a spaced
|
|
6508
|
+
* local-variable reference survives untouched.
|
|
6509
|
+
*
|
|
6510
|
+
* Self-gating for non-hyperscript tokenizers (domain DSLs): their extractor
|
|
6511
|
+
* sets tokenize `:` as bare punctuation (length 1), which never matches
|
|
6512
|
+
* COLON_QUALIFIER, so this pass is a no-op for them.
|
|
6513
|
+
*/
|
|
6514
|
+
mergeColonQualifiedNames(tokens) {
|
|
6515
|
+
const out = [];
|
|
6516
|
+
for (const tok of tokens) {
|
|
6517
|
+
const prev = out[out.length - 1];
|
|
6518
|
+
if (prev && _BaseTokenizer2.ASCII_WORD.test(prev.value) && _BaseTokenizer2.COLON_QUALIFIER.test(tok.value) && prev.position.end === tok.position.start) {
|
|
6519
|
+
const merged = prev.value + tok.value;
|
|
6520
|
+
out[out.length - 1] = createToken(
|
|
6521
|
+
merged,
|
|
6522
|
+
this.classifyToken(merged),
|
|
6523
|
+
createPosition(prev.position.start, tok.position.end)
|
|
6524
|
+
);
|
|
6525
|
+
continue;
|
|
6526
|
+
}
|
|
6527
|
+
out.push(tok);
|
|
6528
|
+
}
|
|
6529
|
+
return out;
|
|
6242
6530
|
}
|
|
6243
6531
|
/**
|
|
6244
6532
|
* Classify an unknown character when no extractor matches.
|
|
@@ -6371,7 +6659,7 @@ var _BaseTokenizer = class _BaseTokenizer2 {
|
|
|
6371
6659
|
* @returns Word without diacritics
|
|
6372
6660
|
*/
|
|
6373
6661
|
removeDiacritics(word) {
|
|
6374
|
-
return word
|
|
6662
|
+
return stripOptionalDiacritics(word);
|
|
6375
6663
|
}
|
|
6376
6664
|
/**
|
|
6377
6665
|
* Try to match a keyword from profile at the current position.
|
|
@@ -6462,24 +6750,40 @@ var _BaseTokenizer = class _BaseTokenizer2 {
|
|
|
6462
6750
|
});
|
|
6463
6751
|
}
|
|
6464
6752
|
/**
|
|
6465
|
-
* Look up a keyword by native word (case-insensitive).
|
|
6753
|
+
* Look up a keyword by native word (case-insensitive, diacritic-insensitive).
|
|
6466
6754
|
* O(1) lookup using the keyword map.
|
|
6467
6755
|
*
|
|
6756
|
+
* The map is INDEXED both with and without diacritics (see
|
|
6757
|
+
* `initializeKeywordsFromProfile`), so a stripped QUERY is the other half of
|
|
6758
|
+
* that: it lets a surface form carrying harakat the profile does not happen to
|
|
6759
|
+
* spell still find its entry. Only consulted after the exact lookup misses, so
|
|
6760
|
+
* every previously-matching word resolves byte-identically.
|
|
6761
|
+
*
|
|
6762
|
+
* Half-implementing this — indexing stripped but querying exact — is what made
|
|
6763
|
+
* diacritized `بَدِّل` (toggle) tokenize as `kind=particle normalized=with`:
|
|
6764
|
+
* `isKeyword` returned false, so the guard in `ArabicProcliticExtractor` that
|
|
6765
|
+
* exists to prevent exactly that handed the word on, and the single-char `ب`
|
|
6766
|
+
* bi- proclitic claimed it. A wrong CONCEPT, not a failed parse.
|
|
6767
|
+
*
|
|
6468
6768
|
* @param native - Native word to look up
|
|
6469
6769
|
* @returns KeywordEntry if found, undefined otherwise
|
|
6470
6770
|
*/
|
|
6471
6771
|
lookupKeyword(native) {
|
|
6472
|
-
|
|
6772
|
+
const exact = this.profileKeywordMap.get(native.toLowerCase());
|
|
6773
|
+
if (exact) return exact;
|
|
6774
|
+
const stripped = this.removeDiacritics(native);
|
|
6775
|
+
if (stripped === native) return void 0;
|
|
6776
|
+
return this.profileKeywordMap.get(stripped.toLowerCase());
|
|
6473
6777
|
}
|
|
6474
6778
|
/**
|
|
6475
|
-
* Check if a word is a known keyword (case-insensitive).
|
|
6476
|
-
* O(1) lookup using the keyword map.
|
|
6779
|
+
* Check if a word is a known keyword (case-insensitive, diacritic-insensitive).
|
|
6780
|
+
* O(1) lookup using the keyword map. See {@link lookupKeyword}.
|
|
6477
6781
|
*
|
|
6478
6782
|
* @param native - Native word to check
|
|
6479
6783
|
* @returns true if the word is a keyword
|
|
6480
6784
|
*/
|
|
6481
6785
|
isKeyword(native) {
|
|
6482
|
-
return this.
|
|
6786
|
+
return this.lookupKeyword(native) !== void 0;
|
|
6483
6787
|
}
|
|
6484
6788
|
/**
|
|
6485
6789
|
* Set the morphological normalizer for this tokenizer.
|
|
@@ -6744,6 +7048,8 @@ var _BaseTokenizer = class _BaseTokenizer2 {
|
|
|
6744
7048
|
return null;
|
|
6745
7049
|
}
|
|
6746
7050
|
};
|
|
7051
|
+
_BaseTokenizer.ASCII_WORD = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
7052
|
+
_BaseTokenizer.COLON_QUALIFIER = /^:[A-Za-z_][A-Za-z0-9_]*$/;
|
|
6747
7053
|
_BaseTokenizer.STANDARD_TIME_UNITS = [
|
|
6748
7054
|
{ pattern: "ms", suffix: "ms", length: 2 },
|
|
6749
7055
|
{ pattern: "s", suffix: "s", length: 1, checkBoundary: true },
|
|
@@ -7428,6 +7734,7 @@ export {
|
|
|
7428
7734
|
BaseMorphologicalNormalizer,
|
|
7429
7735
|
BaseTokenizer,
|
|
7430
7736
|
CrossDomainDispatcher,
|
|
7737
|
+
CssSelectorExtractor,
|
|
7431
7738
|
DEFAULT_OPERATORS,
|
|
7432
7739
|
DEFAULT_PUNCTUATION,
|
|
7433
7740
|
DEFAULT_REFERENCES,
|
|
@@ -7464,6 +7771,7 @@ export {
|
|
|
7464
7771
|
createCompoundNode,
|
|
7465
7772
|
createConditionalNode,
|
|
7466
7773
|
createDiagnosticCollector,
|
|
7774
|
+
createDomainRenderer,
|
|
7467
7775
|
createEventHandlerNode,
|
|
7468
7776
|
createExpression,
|
|
7469
7777
|
createFlag,
|
|
@@ -7555,6 +7863,8 @@ export {
|
|
|
7555
7863
|
semanticNodeToJSON,
|
|
7556
7864
|
semanticNodeToRuntimeAST,
|
|
7557
7865
|
semanticValueToAST,
|
|
7866
|
+
sortRolesByWordOrder,
|
|
7867
|
+
stripOptionalDiacritics,
|
|
7558
7868
|
synthesizeFromSchemas,
|
|
7559
7869
|
toEnvelopeJSON,
|
|
7560
7870
|
toJSONL,
|
package/dist/server.js
CHANGED
|
@@ -1134,7 +1134,7 @@ try {
|
|
|
1134
1134
|
}
|
|
1135
1135
|
var frameworkIR = null;
|
|
1136
1136
|
try {
|
|
1137
|
-
const fw = await import("./dist-
|
|
1137
|
+
const fw = await import("./dist-LIFKYPGW.js");
|
|
1138
1138
|
frameworkIR = { fromInterchangeNode: fw.fromInterchangeNode, renderExplicit: fw.renderExplicit };
|
|
1139
1139
|
console.error(
|
|
1140
1140
|
"[lokascript-ls] @lokascript/framework loaded \u2014 LSE bracket notation in hover enabled"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lokascript/language-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "Language Server Protocol implementation for LokaScript/hyperscript with 21 language support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/server.js",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"vscode-languageserver-textdocument": "^1.0.11"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@hyperfixi/core": "^2.
|
|
35
|
-
"@lokascript/semantic": "^2.
|
|
34
|
+
"@hyperfixi/core": "^2.9.0",
|
|
35
|
+
"@lokascript/semantic": "^2.9.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependenciesMeta": {
|
|
38
38
|
"@lokascript/semantic": {
|
|
@@ -43,17 +43,18 @@
|
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@hyperfixi/core": "^2.
|
|
47
|
-
"@lokascript/semantic": "^2.
|
|
46
|
+
"@hyperfixi/core": "^2.9.0",
|
|
47
|
+
"@lokascript/semantic": "^2.9.0",
|
|
48
48
|
"@types/node": "^20.0.0",
|
|
49
49
|
"@vitest/coverage-v8": "^4.0.0",
|
|
50
50
|
"tsup": "^8.0.0",
|
|
51
|
-
"tsx": "^4.
|
|
51
|
+
"tsx": "^4.23.1",
|
|
52
52
|
"typescript": "^5.0.0",
|
|
53
53
|
"vitest": "^4.1.5"
|
|
54
54
|
},
|
|
55
55
|
"files": [
|
|
56
|
-
"dist"
|
|
56
|
+
"dist",
|
|
57
|
+
"CHANGELOG.md"
|
|
57
58
|
],
|
|
58
59
|
"publishConfig": {
|
|
59
60
|
"access": "public"
|