@lokascript/language-server 2.8.0 → 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-6KATWM5A.js → dist-LIFKYPGW.js} +324 -113
- package/dist/server.js +1 -1
- package/package.json +7 -6
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",
|
|
@@ -2645,10 +2673,12 @@ function buildTokens(schema, profile, keyword, alternatives) {
|
|
|
2645
2673
|
addRoleWithMarker(tokens, role, profile);
|
|
2646
2674
|
}
|
|
2647
2675
|
} else if (profile.wordOrder === "SOV") {
|
|
2676
|
+
const preVerb = [];
|
|
2677
|
+
const postVerb = [];
|
|
2648
2678
|
for (const role of sortedRoles) {
|
|
2649
|
-
addRoleWithMarker(
|
|
2679
|
+
addRoleWithMarker(role.sovSlot === "postVerb" ? postVerb : preVerb, role, profile);
|
|
2650
2680
|
}
|
|
2651
|
-
tokens.push(keywordToken);
|
|
2681
|
+
tokens.push(...preVerb, keywordToken, ...postVerb);
|
|
2652
2682
|
} else if (profile.wordOrder === "VSO") {
|
|
2653
2683
|
tokens.push(keywordToken);
|
|
2654
2684
|
for (const role of sortedRoles) {
|
|
@@ -2731,35 +2761,145 @@ function buildExtractionRules(schema, profile) {
|
|
|
2731
2761
|
return rules;
|
|
2732
2762
|
}
|
|
2733
2763
|
function buildFormatString(schema, profile, keyword) {
|
|
2734
|
-
const
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
}
|
|
2764
|
+
const isSOV = profile.wordOrder === "SOV";
|
|
2765
|
+
const preVerb = [];
|
|
2766
|
+
const postVerb = [];
|
|
2738
2767
|
const sortedRoles = sortRolesByWordOrder(schema.roles, profile.wordOrder);
|
|
2739
2768
|
for (const role of sortedRoles) {
|
|
2740
2769
|
const marker = getMarkerForRole(role, profile);
|
|
2741
2770
|
const roleName = `{${role.role}}`;
|
|
2771
|
+
const bucket = isSOV && role.sovSlot === "postVerb" ? postVerb : preVerb;
|
|
2742
2772
|
if (marker) {
|
|
2743
2773
|
const markerInfo = profile.roleMarkers?.[role.role];
|
|
2744
2774
|
if (markerInfo?.position === "after") {
|
|
2745
|
-
|
|
2775
|
+
bucket.push(`${roleName} ${marker}`);
|
|
2746
2776
|
} else {
|
|
2747
|
-
|
|
2777
|
+
bucket.push(`${marker} ${roleName}`);
|
|
2748
2778
|
}
|
|
2749
2779
|
} else {
|
|
2750
|
-
|
|
2780
|
+
bucket.push(roleName);
|
|
2751
2781
|
}
|
|
2752
2782
|
}
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
return parts.join(" ");
|
|
2783
|
+
return (isSOV ? [...preVerb, keyword, ...postVerb] : [keyword, ...preVerb, ...postVerb]).join(
|
|
2784
|
+
" "
|
|
2785
|
+
);
|
|
2757
2786
|
}
|
|
2758
2787
|
function generatePatternVariants(schema, profile, config = defaultConfig) {
|
|
2759
2788
|
const patterns = [];
|
|
2760
2789
|
patterns.push(generatePattern(schema, profile, config));
|
|
2761
2790
|
return patterns;
|
|
2762
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
|
+
}
|
|
2763
2903
|
function reorderRoles(roles, targetOrder) {
|
|
2764
2904
|
const result = [];
|
|
2765
2905
|
const usedRoles = /* @__PURE__ */ new Set();
|
|
@@ -3187,6 +3327,31 @@ var LatinExtendedIdentifierExtractor = class {
|
|
|
3187
3327
|
return { value: input.slice(position, end), length: end - position };
|
|
3188
3328
|
}
|
|
3189
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
|
+
};
|
|
3190
3355
|
var WhitespaceExtractor = class {
|
|
3191
3356
|
constructor() {
|
|
3192
3357
|
this.name = "whitespace";
|
|
@@ -3906,10 +4071,17 @@ function semanticValueToAST(value) {
|
|
|
3906
4071
|
}
|
|
3907
4072
|
}
|
|
3908
4073
|
var DSLRegistry = class {
|
|
3909
|
-
|
|
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()) {
|
|
3910
4081
|
this.patterns = /* @__PURE__ */ new Map();
|
|
3911
4082
|
this.tokenizers = /* @__PURE__ */ new Map();
|
|
3912
4083
|
this.schemas = config.schemas;
|
|
4084
|
+
this.partiallyTranslatedActions = partiallyTranslatedActions;
|
|
3913
4085
|
for (const lang of config.languages) {
|
|
3914
4086
|
this.registerLanguage(lang);
|
|
3915
4087
|
}
|
|
@@ -3918,6 +4090,9 @@ var DSLRegistry = class {
|
|
|
3918
4090
|
this.tokenizers.set(lang.code, lang.tokenizer);
|
|
3919
4091
|
const patterns = [];
|
|
3920
4092
|
for (const schema of this.schemas) {
|
|
4093
|
+
if (this.partiallyTranslatedActions.has(schema.action) && !lang.patternProfile.keywords[schema.action]) {
|
|
4094
|
+
continue;
|
|
4095
|
+
}
|
|
3921
4096
|
const pattern = generatePattern(schema, lang.patternProfile);
|
|
3922
4097
|
patterns.push(pattern);
|
|
3923
4098
|
}
|
|
@@ -3934,13 +4109,25 @@ var DSLRegistry = class {
|
|
|
3934
4109
|
}
|
|
3935
4110
|
};
|
|
3936
4111
|
var MultilingualDSLImpl = class {
|
|
3937
|
-
constructor(config, registry, transformer) {
|
|
4112
|
+
constructor(config, registry, transformer, profileProvider) {
|
|
3938
4113
|
this.registry = registry;
|
|
3939
4114
|
this.matcher = new PatternMatcher();
|
|
3940
4115
|
this.transformer = transformer;
|
|
4116
|
+
this.profileProvider = profileProvider;
|
|
3941
4117
|
if (config.codeGenerator) {
|
|
3942
4118
|
this.codeGenerator = config.codeGenerator;
|
|
3943
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
|
+
});
|
|
3944
4131
|
const schemaMap = new Map(config.schemas.map((s) => [s.action, s]));
|
|
3945
4132
|
this.schemaLookup = {
|
|
3946
4133
|
getSchema: (action) => schemaMap.get(action)
|
|
@@ -4002,6 +4189,13 @@ var MultilingualDSLImpl = class {
|
|
|
4002
4189
|
}
|
|
4003
4190
|
translate(input, fromLanguage, toLanguage) {
|
|
4004
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
|
+
}
|
|
4005
4199
|
return this.transformer.transform(input, fromLanguage, toLanguage);
|
|
4006
4200
|
}
|
|
4007
4201
|
compile(input, language) {
|
|
@@ -4030,6 +4224,18 @@ var MultilingualDSLImpl = class {
|
|
|
4030
4224
|
};
|
|
4031
4225
|
}
|
|
4032
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
|
+
}
|
|
4033
4239
|
getSupportedLanguages() {
|
|
4034
4240
|
return this.registry.getSupportedLanguages();
|
|
4035
4241
|
}
|
|
@@ -4054,15 +4260,80 @@ function createDefaultProfileProvider(config) {
|
|
|
4054
4260
|
}
|
|
4055
4261
|
return new InMemoryProfileProvider(profiles);
|
|
4056
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
|
+
}
|
|
4057
4326
|
function createMultilingualDSL(config) {
|
|
4058
|
-
const
|
|
4059
|
-
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);
|
|
4060
4331
|
const transformer = new GrammarTransformer({
|
|
4061
4332
|
dictionary,
|
|
4062
4333
|
profileProvider
|
|
4063
4334
|
});
|
|
4064
|
-
const registry = new DSLRegistry(
|
|
4065
|
-
return new MultilingualDSLImpl(
|
|
4335
|
+
const registry = new DSLRegistry(effectiveConfig, extensionActions);
|
|
4336
|
+
return new MultilingualDSLImpl(effectiveConfig, registry, transformer, profileProvider);
|
|
4066
4337
|
}
|
|
4067
4338
|
function generatePrompt(config) {
|
|
4068
4339
|
const outputFormat = config.outputFormat ?? "both";
|
|
@@ -5025,6 +5296,11 @@ var DomainRegistry = class {
|
|
|
5025
5296
|
rendered = typeof renderer === "function" ? renderer(node, to) : renderer.render(node, to);
|
|
5026
5297
|
} catch {
|
|
5027
5298
|
}
|
|
5299
|
+
} else if (dsl.render) {
|
|
5300
|
+
try {
|
|
5301
|
+
rendered = dsl.render(node, to);
|
|
5302
|
+
} catch {
|
|
5303
|
+
}
|
|
5028
5304
|
}
|
|
5029
5305
|
const roles = {};
|
|
5030
5306
|
for (const [key, value] of node.roles) {
|
|
@@ -5570,94 +5846,6 @@ async function registryToAOTBackends(registry) {
|
|
|
5570
5846
|
}
|
|
5571
5847
|
return backends;
|
|
5572
5848
|
}
|
|
5573
|
-
function lookupKeyword(keywords, action, language) {
|
|
5574
|
-
return keywords[action]?.[language] ?? action;
|
|
5575
|
-
}
|
|
5576
|
-
function lookupMarker(markers, marker, language) {
|
|
5577
|
-
return markers[marker]?.[language] ?? marker;
|
|
5578
|
-
}
|
|
5579
|
-
function buildPhrase(...parts) {
|
|
5580
|
-
return parts.filter(Boolean).join(" ");
|
|
5581
|
-
}
|
|
5582
|
-
function buildTablesFromProfiles(schemas, profiles) {
|
|
5583
|
-
const keywords = {};
|
|
5584
|
-
const markers = {};
|
|
5585
|
-
for (const profile of profiles) {
|
|
5586
|
-
for (const [action, kw] of Object.entries(profile.keywords)) {
|
|
5587
|
-
if (!keywords[action]) keywords[action] = {};
|
|
5588
|
-
keywords[action][profile.code] = kw.primary;
|
|
5589
|
-
}
|
|
5590
|
-
}
|
|
5591
|
-
for (const schema of schemas) {
|
|
5592
|
-
for (const role of schema.roles) {
|
|
5593
|
-
if (role.markerOverride) {
|
|
5594
|
-
const markerKey = role.role;
|
|
5595
|
-
if (!markers[markerKey]) markers[markerKey] = {};
|
|
5596
|
-
for (const [lang, markerText] of Object.entries(role.markerOverride)) {
|
|
5597
|
-
markers[markerKey][lang] = markerText;
|
|
5598
|
-
}
|
|
5599
|
-
}
|
|
5600
|
-
}
|
|
5601
|
-
}
|
|
5602
|
-
for (const profile of profiles) {
|
|
5603
|
-
if (profile.roleMarkers) {
|
|
5604
|
-
for (const [markerKey, markerDef] of Object.entries(profile.roleMarkers)) {
|
|
5605
|
-
if (!markers[markerKey]) markers[markerKey] = {};
|
|
5606
|
-
markers[markerKey][profile.code] = markerDef.primary;
|
|
5607
|
-
}
|
|
5608
|
-
}
|
|
5609
|
-
}
|
|
5610
|
-
return { keywords, markers };
|
|
5611
|
-
}
|
|
5612
|
-
function detectWordOrders(profiles) {
|
|
5613
|
-
const sovLanguages = /* @__PURE__ */ new Set();
|
|
5614
|
-
const vsoLanguages = /* @__PURE__ */ new Set();
|
|
5615
|
-
for (const profile of profiles) {
|
|
5616
|
-
if (profile.wordOrder === "SOV") sovLanguages.add(profile.code);
|
|
5617
|
-
if (profile.wordOrder === "VSO") vsoLanguages.add(profile.code);
|
|
5618
|
-
}
|
|
5619
|
-
return { sovLanguages, vsoLanguages };
|
|
5620
|
-
}
|
|
5621
|
-
function createSchemaRenderer(schemas, profiles) {
|
|
5622
|
-
const { keywords, markers } = buildTablesFromProfiles(schemas, profiles);
|
|
5623
|
-
const { sovLanguages } = detectWordOrders(profiles);
|
|
5624
|
-
const schemaMap = /* @__PURE__ */ new Map();
|
|
5625
|
-
for (const s of schemas) schemaMap.set(s.action, s);
|
|
5626
|
-
return {
|
|
5627
|
-
render(node, language) {
|
|
5628
|
-
const schema = schemaMap.get(node.action);
|
|
5629
|
-
if (!schema) return node.action;
|
|
5630
|
-
const keyword = lookupKeyword(keywords, node.action, language);
|
|
5631
|
-
const isSOV = sovLanguages.has(language);
|
|
5632
|
-
const roleParts = [];
|
|
5633
|
-
for (const role of schema.roles) {
|
|
5634
|
-
const value = extractRoleValue(node, role.role);
|
|
5635
|
-
if (!value && !role.required) continue;
|
|
5636
|
-
const markerText = role.markerOverride?.[language] ?? markers[role.role]?.[language] ?? void 0;
|
|
5637
|
-
roleParts.push({
|
|
5638
|
-
...markerText != null && { marker: markerText },
|
|
5639
|
-
value: value || "",
|
|
5640
|
-
role
|
|
5641
|
-
});
|
|
5642
|
-
}
|
|
5643
|
-
const parts = [];
|
|
5644
|
-
if (isSOV) {
|
|
5645
|
-
for (const rp of roleParts) {
|
|
5646
|
-
if (rp.value) parts.push(rp.value);
|
|
5647
|
-
if (rp.marker) parts.push(rp.marker);
|
|
5648
|
-
}
|
|
5649
|
-
parts.push(keyword);
|
|
5650
|
-
} else {
|
|
5651
|
-
parts.push(keyword);
|
|
5652
|
-
for (const rp of roleParts) {
|
|
5653
|
-
if (rp.marker) parts.push(rp.marker);
|
|
5654
|
-
if (rp.value) parts.push(rp.value);
|
|
5655
|
-
}
|
|
5656
|
-
}
|
|
5657
|
-
return buildPhrase(...parts);
|
|
5658
|
-
}
|
|
5659
|
-
};
|
|
5660
|
-
}
|
|
5661
5849
|
var TokenStreamImpl = class {
|
|
5662
5850
|
constructor(tokens, language) {
|
|
5663
5851
|
this.pos = 0;
|
|
@@ -5763,6 +5951,9 @@ function isQuote(char) {
|
|
|
5763
5951
|
function isDigit(char) {
|
|
5764
5952
|
return /\d/.test(char);
|
|
5765
5953
|
}
|
|
5954
|
+
function stripOptionalDiacritics(word) {
|
|
5955
|
+
return word.replace(/[ً-ْٰ]/g, "");
|
|
5956
|
+
}
|
|
5766
5957
|
function isAsciiLetter(char) {
|
|
5767
5958
|
return /[a-zA-Z]/.test(char);
|
|
5768
5959
|
}
|
|
@@ -6468,7 +6659,7 @@ var _BaseTokenizer = class _BaseTokenizer2 {
|
|
|
6468
6659
|
* @returns Word without diacritics
|
|
6469
6660
|
*/
|
|
6470
6661
|
removeDiacritics(word) {
|
|
6471
|
-
return word
|
|
6662
|
+
return stripOptionalDiacritics(word);
|
|
6472
6663
|
}
|
|
6473
6664
|
/**
|
|
6474
6665
|
* Try to match a keyword from profile at the current position.
|
|
@@ -6559,24 +6750,40 @@ var _BaseTokenizer = class _BaseTokenizer2 {
|
|
|
6559
6750
|
});
|
|
6560
6751
|
}
|
|
6561
6752
|
/**
|
|
6562
|
-
* Look up a keyword by native word (case-insensitive).
|
|
6753
|
+
* Look up a keyword by native word (case-insensitive, diacritic-insensitive).
|
|
6563
6754
|
* O(1) lookup using the keyword map.
|
|
6564
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
|
+
*
|
|
6565
6768
|
* @param native - Native word to look up
|
|
6566
6769
|
* @returns KeywordEntry if found, undefined otherwise
|
|
6567
6770
|
*/
|
|
6568
6771
|
lookupKeyword(native) {
|
|
6569
|
-
|
|
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());
|
|
6570
6777
|
}
|
|
6571
6778
|
/**
|
|
6572
|
-
* Check if a word is a known keyword (case-insensitive).
|
|
6573
|
-
* 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}.
|
|
6574
6781
|
*
|
|
6575
6782
|
* @param native - Native word to check
|
|
6576
6783
|
* @returns true if the word is a keyword
|
|
6577
6784
|
*/
|
|
6578
6785
|
isKeyword(native) {
|
|
6579
|
-
return this.
|
|
6786
|
+
return this.lookupKeyword(native) !== void 0;
|
|
6580
6787
|
}
|
|
6581
6788
|
/**
|
|
6582
6789
|
* Set the morphological normalizer for this tokenizer.
|
|
@@ -7527,6 +7734,7 @@ export {
|
|
|
7527
7734
|
BaseMorphologicalNormalizer,
|
|
7528
7735
|
BaseTokenizer,
|
|
7529
7736
|
CrossDomainDispatcher,
|
|
7737
|
+
CssSelectorExtractor,
|
|
7530
7738
|
DEFAULT_OPERATORS,
|
|
7531
7739
|
DEFAULT_PUNCTUATION,
|
|
7532
7740
|
DEFAULT_REFERENCES,
|
|
@@ -7563,6 +7771,7 @@ export {
|
|
|
7563
7771
|
createCompoundNode,
|
|
7564
7772
|
createConditionalNode,
|
|
7565
7773
|
createDiagnosticCollector,
|
|
7774
|
+
createDomainRenderer,
|
|
7566
7775
|
createEventHandlerNode,
|
|
7567
7776
|
createExpression,
|
|
7568
7777
|
createFlag,
|
|
@@ -7654,6 +7863,8 @@ export {
|
|
|
7654
7863
|
semanticNodeToJSON,
|
|
7655
7864
|
semanticNodeToRuntimeAST,
|
|
7656
7865
|
semanticValueToAST,
|
|
7866
|
+
sortRolesByWordOrder,
|
|
7867
|
+
stripOptionalDiacritics,
|
|
7657
7868
|
synthesizeFromSchemas,
|
|
7658
7869
|
toEnvelopeJSON,
|
|
7659
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,8 +43,8 @@
|
|
|
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",
|
|
@@ -53,7 +53,8 @@
|
|
|
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"
|