@px-lsp/server 0.1.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/LICENSE +674 -0
- package/README.md +334 -0
- package/THIRD-PARTY-NOTICES.md +83 -0
- package/data/ck3/dataTypes.json +2195 -0
- package/data/ck3/data_types/data_types_common.txt +2040 -0
- package/data/ck3/data_types/data_types_gui.txt +5264 -0
- package/data/ck3/data_types/data_types_internalclausewitzgui.txt +14843 -0
- package/data/ck3/data_types/data_types_script.txt +4251 -0
- package/data/ck3/data_types/data_types_uncategorized.txt +109984 -0
- package/data/ck3/freqs.json +1 -0
- package/data/ck3/guiSchema.json +6344 -0
- package/data/ck3/script_docs/effects.log +16059 -0
- package/data/ck3/script_docs/event_targets.log +2098 -0
- package/data/ck3/script_docs/modifiers.log +2228 -0
- package/data/ck3/script_docs/on_actions.log +5275 -0
- package/data/ck3/script_docs/triggers.log +11991 -0
- package/data/ck3/structures.json +9743 -0
- package/data/ck3/wikidocs/ATTRIBUTION.md +18 -0
- package/data/ck3/wikidocs/Data_types.md +2568 -0
- package/data/ck3/wikidocs/Effects_list.md +1176 -0
- package/data/ck3/wikidocs/Scopes_list.md +341 -0
- package/data/ck3/wikidocs/Triggers_list.md +1097 -0
- package/data/eu5/data_types/data_types_common.txt +2087 -0
- package/data/eu5/data_types/data_types_gui.txt +6732 -0
- package/data/eu5/data_types/data_types_internalclausewitzgui.txt +19276 -0
- package/data/eu5/data_types/data_types_script.txt +5688 -0
- package/data/eu5/data_types/data_types_uncategorized.txt +135569 -0
- package/data/vic3/data_types/data_types_common.txt +2021 -0
- package/data/vic3/data_types/data_types_gui.txt +5592 -0
- package/data/vic3/data_types/data_types_internalclausewitzgui.txt +17304 -0
- package/data/vic3/data_types/data_types_script.txt +2817 -0
- package/data/vic3/data_types/data_types_uncategorized.txt +84354 -0
- package/data/vic3/freqs.json +1 -0
- package/data/vic3/guiSchema.json +5578 -0
- package/data/vic3/script_docs/effects.log +38135 -0
- package/data/vic3/script_docs/event_targets.log +2028 -0
- package/data/vic3/script_docs/modifiers.log +18954 -0
- package/data/vic3/script_docs/on_actions.log +1561 -0
- package/data/vic3/script_docs/triggers.log +15738 -0
- package/data/vic3/structures.json +10189 -0
- package/dist/server.js +63668 -0
- package/media/px-lsp.svg +12 -0
- package/package.json +50 -0
- package/src/clientMode.ts +60 -0
- package/src/coa/coa.ts +184 -0
- package/src/coa/coaParse.ts +267 -0
- package/src/context.ts +78 -0
- package/src/contextKeywords.ts +224 -0
- package/src/data/dataBindingMacros.ts +82 -0
- package/src/data/dataFnDocs.ts +152 -0
- package/src/data/dataFnUsage.ts +431 -0
- package/src/data/dataTypes.ts +279 -0
- package/src/data/defines.ts +123 -0
- package/src/data/docsParser.ts +453 -0
- package/src/data/keywordDocs.ts +98 -0
- package/src/data/modifierTemplates.ts +143 -0
- package/src/data/textFormatting.ts +165 -0
- package/src/data/wikiDocs.ts +187 -0
- package/src/dds/decoder.ts +1007 -0
- package/src/dds/encode.ts +235 -0
- package/src/dds/index.ts +58 -0
- package/src/dds/png.ts +96 -0
- package/src/dds/tga.ts +62 -0
- package/src/documents.ts +35 -0
- package/src/features/assetPaths.ts +169 -0
- package/src/features/codeActions.ts +148 -0
- package/src/features/colors.ts +244 -0
- package/src/features/completion.ts +961 -0
- package/src/features/datafunction.ts +729 -0
- package/src/features/definition.ts +84 -0
- package/src/features/diagnostics.ts +244 -0
- package/src/features/folding.ts +106 -0
- package/src/features/formatting.ts +60 -0
- package/src/features/guiLanguage.ts +366 -0
- package/src/features/guiNavigation.ts +140 -0
- package/src/features/guiTree.ts +97 -0
- package/src/features/hover.ts +817 -0
- package/src/features/hoverRender.ts +222 -0
- package/src/features/inlayHints.ts +147 -0
- package/src/features/locFormatting.ts +127 -0
- package/src/features/references.ts +70 -0
- package/src/features/rename.ts +135 -0
- package/src/features/scopeAt.ts +65 -0
- package/src/features/semanticTokens.ts +188 -0
- package/src/features/signatureHelp.ts +72 -0
- package/src/features/symbols.ts +241 -0
- package/src/features/textureHover.ts +143 -0
- package/src/features/workspaceSymbols.ts +69 -0
- package/src/games/active.ts +19 -0
- package/src/games/ck3/ambientScopes.ts +273 -0
- package/src/games/ck3/index.ts +38 -0
- package/src/games/ck3/meta.ts +28 -0
- package/src/games/ck3/modifierPlaceholders.ts +61 -0
- package/src/games/ck3/saveSchema.ts +134 -0
- package/src/games/ck3/scaffolds.ts +197 -0
- package/src/games/ck3/schema.ts +422 -0
- package/src/games/ck3/structures.ts +887 -0
- package/src/games/eu5/index.ts +75 -0
- package/src/games/eu5/meta.ts +44 -0
- package/src/games/eu5/scaffolds.ts +49 -0
- package/src/games/eu5/schema.generated.ts +1043 -0
- package/src/games/jomini/variables.ts +134 -0
- package/src/games/profile.ts +205 -0
- package/src/games/registry.ts +27 -0
- package/src/games/vic3/index.ts +52 -0
- package/src/games/vic3/meta.ts +55 -0
- package/src/games/vic3/saveSchema.ts +77 -0
- package/src/games/vic3/scaffolds.ts +135 -0
- package/src/games/vic3/schema.ts +650 -0
- package/src/games/vic3/structures.ts +33 -0
- package/src/gui/anchorSpec.ts +66 -0
- package/src/gui/declMarkers.ts +30 -0
- package/src/gui/fillGeometry.ts +101 -0
- package/src/gui/guiDefs.ts +386 -0
- package/src/gui/guiDependencies.ts +352 -0
- package/src/gui/guiLinks.ts +64 -0
- package/src/gui/layoutEngine.ts +1998 -0
- package/src/gui/layoutService.ts +221 -0
- package/src/gui/measuredMetrics.ts +21 -0
- package/src/gui/previewService.ts +89 -0
- package/src/gui/saveSchema.ts +220 -0
- package/src/gui/saveValues.ts +399 -0
- package/src/gui/saveZip.ts +60 -0
- package/src/gui/sourceEdit.ts +535 -0
- package/src/gui/sourceEditService.ts +439 -0
- package/src/gui/sourceModel.ts +603 -0
- package/src/gui/textResolve.ts +145 -0
- package/src/gui/textureInfo.ts +106 -0
- package/src/gui/vocabulary.ts +149 -0
- package/src/gui/widgetEdit.ts +52 -0
- package/src/gui/widgetInfo.ts +245 -0
- package/src/index/docComments.ts +103 -0
- package/src/index/extract.ts +252 -0
- package/src/index/indexer.ts +369 -0
- package/src/index/intern.ts +101 -0
- package/src/index/lazyRefs.ts +145 -0
- package/src/index/modOrigin.ts +69 -0
- package/src/index/references.ts +534 -0
- package/src/overview/dependencies.ts +240 -0
- package/src/overview/eventBanner.ts +95 -0
- package/src/overview/eventDetail.ts +482 -0
- package/src/overview/eventGraph.ts +617 -0
- package/src/overview/eventVocabulary.ts +214 -0
- package/src/overview/locCoverage.ts +138 -0
- package/src/overview/modOverview.ts +29 -0
- package/src/overview/overrides.ts +89 -0
- package/src/parseCache.ts +81 -0
- package/src/parser/cst.ts +257 -0
- package/src/parser/encoding.ts +106 -0
- package/src/parser/index.ts +7 -0
- package/src/parser/lexer.ts +245 -0
- package/src/parser/locParser.ts +276 -0
- package/src/parser/parser.ts +360 -0
- package/src/schema/freqs.ts +70 -0
- package/src/schema/loader.ts +113 -0
- package/src/schema/types.ts +142 -0
- package/src/scopes/inference.ts +478 -0
- package/src/scopes/model.ts +148 -0
- package/src/scopes/varTypes.ts +290 -0
- package/src/server.ts +1894 -0
- package/src/serverData.ts +98 -0
- package/src/structure.ts +56 -0
- package/src/wordAt.ts +49 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variable value-type resolution: what scope does `var:x` produce?
|
|
3
|
+
*
|
|
4
|
+
* Set-sites indexed by references.ts carry the raw value expression
|
|
5
|
+
* (`set_variable = { name = x value = <expr> }` → Definition.value = expr).
|
|
6
|
+
* This module resolves those expressions STATICALLY — literals, flags and
|
|
7
|
+
* link chains anchored at root or a global data link — and merges the types
|
|
8
|
+
* across all set-sites of a name. Anything anchored in a runtime scope
|
|
9
|
+
* (`scope:…`, `this`, bare links) stays unknown: annotate, never guess (AD-5).
|
|
10
|
+
*
|
|
11
|
+
* The full map is rebuilt lazily once per index revision (variables only —
|
|
12
|
+
* cheap even for AGOT-sized mods) and consumed by scope inference
|
|
13
|
+
* (`ctx.varTypes`), hover and completion detail.
|
|
14
|
+
*
|
|
15
|
+
* No `vscode` imports: unit-tested in plain Node.
|
|
16
|
+
*/
|
|
17
|
+
import type { Definition, Reference } from "@px-lsp/protocol/types";
|
|
18
|
+
import type { SchemaEntry } from "../schema/types";
|
|
19
|
+
import type { ServerData } from "../serverData";
|
|
20
|
+
import { resolveKeyChainScopes, type InferenceContext } from "./inference";
|
|
21
|
+
import type { Scope, ScopeModel } from "./model";
|
|
22
|
+
|
|
23
|
+
const VAR_KIND_PREFIX: Record<string, string> = {
|
|
24
|
+
variable: "var",
|
|
25
|
+
local_variable: "local_var",
|
|
26
|
+
global_variable: "global_var",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Variable-list item types, keyed `var:x`-style by the LIST's storage class. */
|
|
30
|
+
const LIST_KIND_PREFIX: Record<string, string> = {
|
|
31
|
+
variable_list: "var",
|
|
32
|
+
local_variable_list: "local_var",
|
|
33
|
+
global_variable_list: "global_var",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const NUMBER = /^-?\d+(?:\.\d+)?$/;
|
|
37
|
+
|
|
38
|
+
export interface VariableTypeInfo {
|
|
39
|
+
/** `var:x` / `local_var:x` / `global_var:x` → merged value types (null = unknown). */
|
|
40
|
+
types: Map<string, Set<Scope> | null>;
|
|
41
|
+
/** `var:x`-keyed ITEM types of variable lists (for in-list iterator hovers). */
|
|
42
|
+
listItemTypes: Map<string, Set<Scope> | null>;
|
|
43
|
+
/** Plain-named ITEM types of ad-hoc lists (add_to_list/add_to_temporary_list
|
|
44
|
+
* sites): the set-site's enclosing key chain resolved statically. */
|
|
45
|
+
adhocListItemTypes: Map<string, Set<Scope> | null>;
|
|
46
|
+
/** Saved-scope types merged across ALL indexed save sites of the workspace
|
|
47
|
+
* mods — the cross-file fallback when a scope: name is not saved in the
|
|
48
|
+
* current file. Sites carry a discriminated hint in Definition.value
|
|
49
|
+
* (index/references.ts): "chain:" key chains, "expr:" value expressions,
|
|
50
|
+
* "type:value" script-value saves. */
|
|
51
|
+
savedScopeTypes: Map<string, Set<Scope> | null>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface Cache {
|
|
55
|
+
revision: number;
|
|
56
|
+
info: VariableTypeInfo;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const cache = new WeakMap<ServerData, Cache>();
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* PdxDoc `@scope` tag of a definition (`# @scope character` above a scripted
|
|
63
|
+
* effect/trigger/value): the calling scope the author declared. Null when the
|
|
64
|
+
* definition carries no tag.
|
|
65
|
+
*/
|
|
66
|
+
export function defScopeTag(data: ServerData, name: string): Set<Scope> | null {
|
|
67
|
+
for (const def of data.index.lookup(name)) {
|
|
68
|
+
const tag = def.tags?.find((t) => t.tag === "scope");
|
|
69
|
+
if (tag && tag.text.trim()) {
|
|
70
|
+
const scopes = tag.text
|
|
71
|
+
.split(/[|,\s/]+/)
|
|
72
|
+
.map((x) => x.trim().toLowerCase())
|
|
73
|
+
.filter((x) => x !== "");
|
|
74
|
+
if (scopes.length > 0) return new Set(scopes);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The one canonical InferenceContext for a file. Every feature (completion,
|
|
82
|
+
* hover, inlay hints, audit script) MUST build its context through here:
|
|
83
|
+
* getSavedScopes caches its result per document version, so callers passing
|
|
84
|
+
* divergent contexts would make hover/completion results depend on which
|
|
85
|
+
* request happened to arrive first.
|
|
86
|
+
*/
|
|
87
|
+
export function inferenceContextFor(data: ServerData, entry: SchemaEntry | null): InferenceContext {
|
|
88
|
+
const varInfo = variableTypes(data, data.rootScopesForFile);
|
|
89
|
+
return {
|
|
90
|
+
entry,
|
|
91
|
+
onActionScopes: data.onActionScopes,
|
|
92
|
+
varTypes: varInfo.types,
|
|
93
|
+
varListItemTypes: varInfo.listItemTypes,
|
|
94
|
+
adhocListItemTypes: varInfo.adhocListItemTypes,
|
|
95
|
+
savedScopeTypes: varInfo.savedScopeTypes,
|
|
96
|
+
callSiteScopes: callSiteScopes(data, data.rootScopesForFile),
|
|
97
|
+
defScopeTag: (name) => defScopeTag(data, name),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface CallSiteCache {
|
|
102
|
+
revision: string;
|
|
103
|
+
map: Map<string, Set<Scope>>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const callSiteCache = new WeakMap<ServerData, CallSiteCache>();
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Calling scopes of scripted effects/triggers/modifiers aggregated from their
|
|
110
|
+
* indexed call sites (rebuilt when either index changes). The root-scope
|
|
111
|
+
* fallback for definitions without a PdxDoc `@scope` tag.
|
|
112
|
+
*/
|
|
113
|
+
export function callSiteScopes(
|
|
114
|
+
data: ServerData,
|
|
115
|
+
rootScopesForFile: (file: string) => Set<Scope> | null
|
|
116
|
+
): Map<string, Set<Scope>> {
|
|
117
|
+
const revision = `${data.index.revision}:${data.refIndex.revision}`;
|
|
118
|
+
const cached = callSiteCache.get(data);
|
|
119
|
+
if (cached && cached.revision === revision) return cached.map;
|
|
120
|
+
const map = buildCallSiteScopes(data.refIndex.all(), data.scopeModel, rootScopesForFile);
|
|
121
|
+
callSiteCache.set(data, { revision, map });
|
|
122
|
+
return map;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Union of the statically resolved scopes at every call site of a name.
|
|
127
|
+
* Unresolved sites (unknown-root files, dynamic anchors) contribute NOTHING —
|
|
128
|
+
* unlike the variable/list maps this does not poison, because an unresolved
|
|
129
|
+
* call site carries no information about the calling scope. One level only:
|
|
130
|
+
* calls inside other untyped scripted effects stay unresolved (no transitive
|
|
131
|
+
* closure). Ranking/annotation input only, never a diagnostic (AD-5).
|
|
132
|
+
*/
|
|
133
|
+
export function buildCallSiteScopes(
|
|
134
|
+
refs: Iterable<Reference>,
|
|
135
|
+
model: ScopeModel,
|
|
136
|
+
rootScopesForFile: (file: string) => Set<Scope> | null
|
|
137
|
+
): Map<string, Set<Scope>> {
|
|
138
|
+
const map = new Map<string, Set<Scope>>();
|
|
139
|
+
for (const ref of refs) {
|
|
140
|
+
if (!ref.call || ref.chain === undefined) continue;
|
|
141
|
+
const resolved = resolveKeyChainScopes(ref.chain.split("."), model, rootScopesForFile(ref.file));
|
|
142
|
+
if (!resolved || resolved.size === 0) continue;
|
|
143
|
+
const prev = map.get(ref.name);
|
|
144
|
+
if (prev) for (const s of resolved) prev.add(s);
|
|
145
|
+
else map.set(ref.name, new Set(resolved));
|
|
146
|
+
}
|
|
147
|
+
return map;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** The variable type map for the current index revision (cached). */
|
|
151
|
+
export function variableTypes(
|
|
152
|
+
data: ServerData,
|
|
153
|
+
rootScopesForFile: (file: string) => Set<Scope> | null
|
|
154
|
+
): VariableTypeInfo {
|
|
155
|
+
const cached = cache.get(data);
|
|
156
|
+
if (cached && cached.revision === data.index.revision) return cached.info;
|
|
157
|
+
const info = buildVariableTypes(
|
|
158
|
+
data.index.entries(
|
|
159
|
+
(d) =>
|
|
160
|
+
d.kind in VAR_KIND_PREFIX ||
|
|
161
|
+
d.kind in LIST_KIND_PREFIX ||
|
|
162
|
+
d.kind === "list" ||
|
|
163
|
+
d.kind === "saved_scope"
|
|
164
|
+
),
|
|
165
|
+
data.scopeModel,
|
|
166
|
+
rootScopesForFile
|
|
167
|
+
);
|
|
168
|
+
cache.set(data, { revision: data.index.revision, info });
|
|
169
|
+
return info;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function buildVariableTypes(
|
|
173
|
+
defs: Iterable<Definition>,
|
|
174
|
+
model: ScopeModel,
|
|
175
|
+
rootScopesForFile: (file: string) => Set<Scope> | null
|
|
176
|
+
): VariableTypeInfo {
|
|
177
|
+
const types = new Map<string, Set<Scope> | null>();
|
|
178
|
+
const listItemTypes = new Map<string, Set<Scope> | null>();
|
|
179
|
+
const adhocListItemTypes = new Map<string, Set<Scope> | null>();
|
|
180
|
+
const savedScopeTypes = new Map<string, Set<Scope> | null>();
|
|
181
|
+
const merge = (map: Map<string, Set<Scope> | null>, key: string, resolved: Set<Scope> | null) => {
|
|
182
|
+
const prev = map.get(key);
|
|
183
|
+
if (prev === undefined) {
|
|
184
|
+
map.set(key, resolved ? new Set(resolved) : null);
|
|
185
|
+
} else if (prev === null || resolved === null) {
|
|
186
|
+
map.set(key, null);
|
|
187
|
+
} else {
|
|
188
|
+
for (const s of resolved) prev.add(s);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
for (const def of defs) {
|
|
192
|
+
// Ad-hoc lists: def.value is the set-site's enclosing key chain (dotted,
|
|
193
|
+
// outermost first — see index/references.ts), resolved from the file root.
|
|
194
|
+
if (def.kind === "list") {
|
|
195
|
+
if (def.value === undefined) continue;
|
|
196
|
+
const resolved = resolveKeyChainScopes(def.value.split("."), model, rootScopesForFile(def.file));
|
|
197
|
+
merge(adhocListItemTypes, def.name, resolved);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
// Saved scopes: typed per save form via the discriminated value hint.
|
|
201
|
+
if (def.kind === "saved_scope") {
|
|
202
|
+
merge(savedScopeTypes, def.name, resolveSavedScopeHint(def, model, rootScopesForFile));
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const varPrefix = VAR_KIND_PREFIX[def.kind];
|
|
206
|
+
const listPrefix = LIST_KIND_PREFIX[def.kind];
|
|
207
|
+
if (!varPrefix && !listPrefix) continue;
|
|
208
|
+
// Defs without an expression (change_variable sites, dual-indexed list
|
|
209
|
+
// shadows) contribute nothing — they evidence existence, not type.
|
|
210
|
+
if (def.value === undefined) continue;
|
|
211
|
+
const resolved = resolveValueExpr(def.value, def.file, model, rootScopesForFile);
|
|
212
|
+
if (varPrefix) merge(types, `${varPrefix}:${def.name}`, resolved);
|
|
213
|
+
else merge(listItemTypes, `${listPrefix}:${def.name}`, resolved);
|
|
214
|
+
}
|
|
215
|
+
return { types, listItemTypes, adhocListItemTypes, savedScopeTypes };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Resolve one save site's discriminated type hint; null = unknown. Sites
|
|
219
|
+
* without a hint (older index rows) contribute unknown, poisoning the merge —
|
|
220
|
+
* annotate, never guess (AD-5). */
|
|
221
|
+
function resolveSavedScopeHint(
|
|
222
|
+
def: Definition,
|
|
223
|
+
model: ScopeModel,
|
|
224
|
+
rootScopesForFile: (file: string) => Set<Scope> | null
|
|
225
|
+
): Set<Scope> | null {
|
|
226
|
+
const hint = def.value;
|
|
227
|
+
if (hint === undefined) return null;
|
|
228
|
+
if (hint === "type:value") return new Set(["value"]);
|
|
229
|
+
if (hint.startsWith("expr:")) {
|
|
230
|
+
return resolveValueExpr(hint.slice(5), def.file, model, rootScopesForFile);
|
|
231
|
+
}
|
|
232
|
+
if (hint.startsWith("chain:")) {
|
|
233
|
+
return resolveKeyChainScopes(hint.slice(6).split("."), model, rootScopesForFile(def.file));
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Statically resolve a set_variable value expression to the scope type(s) it
|
|
240
|
+
* produces; null = unknown. Handles literals (value/boolean/flag) and link
|
|
241
|
+
* chains anchored at `root` (the set-file's root scope) or a global data link
|
|
242
|
+
* (`culture:x.head_of_faith` …). Runtime anchors resolve to null.
|
|
243
|
+
*/
|
|
244
|
+
export function resolveValueExpr(
|
|
245
|
+
expr: string,
|
|
246
|
+
file: string,
|
|
247
|
+
model: ScopeModel,
|
|
248
|
+
rootScopesForFile: (file: string) => Set<Scope> | null
|
|
249
|
+
): Set<Scope> | null {
|
|
250
|
+
if (NUMBER.test(expr)) return new Set(["value"]);
|
|
251
|
+
if (expr === "yes" || expr === "no") return new Set(["boolean"]);
|
|
252
|
+
if (expr.startsWith("flag:")) return new Set(["flag"]);
|
|
253
|
+
if (expr.includes("$")) return null; // macro parameter
|
|
254
|
+
|
|
255
|
+
const parts = expr.split(".");
|
|
256
|
+
let current: Set<Scope> | null;
|
|
257
|
+
|
|
258
|
+
const first = parts[0];
|
|
259
|
+
if (first === "root") {
|
|
260
|
+
current = rootScopesForFile(file);
|
|
261
|
+
} else if (
|
|
262
|
+
first.startsWith("scope:") ||
|
|
263
|
+
first.startsWith("var:") ||
|
|
264
|
+
first.startsWith("local_var:") ||
|
|
265
|
+
first.startsWith("global_var:") ||
|
|
266
|
+
first === "this" ||
|
|
267
|
+
first === "prev"
|
|
268
|
+
) {
|
|
269
|
+
return null; // runtime anchor — unknowable statically
|
|
270
|
+
} else {
|
|
271
|
+
// Global/data link anchor: culture:x, faith:x, character:123, title:k_x …
|
|
272
|
+
const colon = first.indexOf(":");
|
|
273
|
+
const linkName = colon > 0 ? first.slice(0, colon) : first;
|
|
274
|
+
const link = model.links.get(linkName.toLowerCase());
|
|
275
|
+
if (!link || !link.outputs) return null;
|
|
276
|
+
// A bare link (no data argument) needs an input scope — unknowable here.
|
|
277
|
+
if (colon <= 0 && link.inputs !== null) return null;
|
|
278
|
+
current = new Set(link.outputs);
|
|
279
|
+
}
|
|
280
|
+
if (!current) return null;
|
|
281
|
+
|
|
282
|
+
for (let i = 1; i < parts.length; i++) {
|
|
283
|
+
const part = parts[i].toLowerCase();
|
|
284
|
+
const colon = part.indexOf(":");
|
|
285
|
+
const link = model.links.get(colon > 0 ? part.slice(0, colon) : part);
|
|
286
|
+
if (!link || !link.outputs) return null;
|
|
287
|
+
current = new Set(link.outputs);
|
|
288
|
+
}
|
|
289
|
+
return current;
|
|
290
|
+
}
|