wesl 0.6.0-pre10
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/README.md +31 -0
- package/dist/index.js +4468 -0
- package/dist/index.js.map +1 -0
- package/dist/minified.js +3426 -0
- package/dist/minified.js.map +1 -0
- package/dist/tools/packages/wesl/src/AbstractElems.d.ts +322 -0
- package/dist/tools/packages/wesl/src/Assertions.d.ts +27 -0
- package/dist/tools/packages/wesl/src/BindIdents.d.ts +70 -0
- package/dist/tools/packages/wesl/src/Conditions.d.ts +6 -0
- package/dist/tools/packages/wesl/src/FlattenTreeImport.d.ts +11 -0
- package/dist/tools/packages/wesl/src/LinkedWesl.d.ts +50 -0
- package/dist/tools/packages/wesl/src/Linker.d.ts +87 -0
- package/dist/tools/packages/wesl/src/LinkerUtil.d.ts +3 -0
- package/dist/tools/packages/wesl/src/LiveDeclarations.d.ts +12 -0
- package/dist/tools/packages/wesl/src/LowerAndEmit.d.ts +31 -0
- package/dist/tools/packages/wesl/src/Mangler.d.ts +39 -0
- package/dist/tools/packages/wesl/src/ParseWESL.d.ts +60 -0
- package/dist/tools/packages/wesl/src/ParsedRegistry.d.ts +29 -0
- package/dist/tools/packages/wesl/src/PathUtil.d.ts +6 -0
- package/dist/tools/packages/wesl/src/RawEmit.d.ts +6 -0
- package/dist/tools/packages/wesl/src/Reflection.d.ts +45 -0
- package/dist/tools/packages/wesl/src/Scope.d.ts +81 -0
- package/dist/tools/packages/wesl/src/StandardTypes.d.ts +13 -0
- package/dist/tools/packages/wesl/src/TransformBindingStructs.d.ts +52 -0
- package/dist/tools/packages/wesl/src/Util.d.ts +43 -0
- package/dist/tools/packages/wesl/src/WESLCollect.d.ts +94 -0
- package/dist/tools/packages/wesl/src/WeslBundle.d.ts +13 -0
- package/dist/tools/packages/wesl/src/WeslDevice.d.ts +25 -0
- package/dist/tools/packages/wesl/src/debug/ASTtoString.d.ts +5 -0
- package/dist/tools/packages/wesl/src/debug/ImportToString.d.ts +2 -0
- package/dist/tools/packages/wesl/src/debug/LineWrapper.d.ts +21 -0
- package/dist/tools/packages/wesl/src/debug/ScopeToString.d.ts +6 -0
- package/dist/tools/packages/wesl/src/index.d.ts +11 -0
- package/dist/tools/packages/wesl/src/parse/ImportGrammar.d.ts +5 -0
- package/dist/tools/packages/wesl/src/parse/Keywords.d.ts +4 -0
- package/dist/tools/packages/wesl/src/parse/WeslBaseGrammar.d.ts +5 -0
- package/dist/tools/packages/wesl/src/parse/WeslExpression.d.ts +13 -0
- package/dist/tools/packages/wesl/src/parse/WeslGrammar.d.ts +80 -0
- package/dist/tools/packages/wesl/src/parse/WeslStream.d.ts +44 -0
- package/dist/tools/packages/wesl/src/test/BindWESL.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ConditionLinking.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ConditionalTranslationCases.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ErrorLogging.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/Expression.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/FlattenTreeImport.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ImportCases.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ImportSyntaxCases.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/LinkGlob.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/LinkPackage.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/Linker.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/Mangling.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ParseComments.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ParseConditions.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ParseError.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ParseWESL.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/PathUtil.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/PrettyGrammar.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/Reflection.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ScopeWESL.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/TestLink.d.ts +21 -0
- package/dist/tools/packages/wesl/src/test/TestSetup.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/TestUtil.d.ts +40 -0
- package/dist/tools/packages/wesl/src/test/Tokenizer.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/TransformBindingStructs.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/Util.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/VirtualModules.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/WeslDevice.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/WgslTests.d.ts +0 -0
- package/dist/tools/packages/wesl/src/vlq/vlq.d.ts +11 -0
- package/package.json +46 -0
- package/src/AbstractElems.ts +446 -0
- package/src/Assertions.ts +51 -0
- package/src/BindIdents.ts +523 -0
- package/src/Conditions.ts +74 -0
- package/src/FlattenTreeImport.ts +55 -0
- package/src/LinkedWesl.ts +184 -0
- package/src/Linker.ts +284 -0
- package/src/LinkerUtil.ts +29 -0
- package/src/LiveDeclarations.ts +31 -0
- package/src/LowerAndEmit.ts +413 -0
- package/src/Mangler.ts +94 -0
- package/src/ParseWESL.ts +157 -0
- package/src/ParsedRegistry.ts +120 -0
- package/src/PathUtil.ts +31 -0
- package/src/RawEmit.ts +102 -0
- package/src/Reflection.ts +334 -0
- package/src/Scope.ts +162 -0
- package/src/StandardTypes.ts +97 -0
- package/src/TransformBindingStructs.ts +319 -0
- package/src/Util.ts +194 -0
- package/src/WESLCollect.ts +614 -0
- package/src/WeslBundle.ts +16 -0
- package/src/WeslDevice.ts +209 -0
- package/src/debug/ASTtoString.ts +290 -0
- package/src/debug/ImportToString.ts +29 -0
- package/src/debug/LineWrapper.ts +70 -0
- package/src/debug/ScopeToString.ts +79 -0
- package/src/index.ts +11 -0
- package/src/parse/ImportGrammar.ts +157 -0
- package/src/parse/Keywords.ts +26 -0
- package/src/parse/WeslBaseGrammar.ts +8 -0
- package/src/parse/WeslExpression.ts +207 -0
- package/src/parse/WeslGrammar.ts +856 -0
- package/src/parse/WeslStream.ts +279 -0
- package/src/test/BindWESL.test.ts +57 -0
- package/src/test/ConditionLinking.test.ts +91 -0
- package/src/test/ConditionalTranslationCases.test.ts +56 -0
- package/src/test/ErrorLogging.test.ts +30 -0
- package/src/test/Expression.test.ts +22 -0
- package/src/test/FlattenTreeImport.test.ts +74 -0
- package/src/test/ImportCases.test.ts +56 -0
- package/src/test/ImportSyntaxCases.test.ts +24 -0
- package/src/test/LinkGlob.test.ts +25 -0
- package/src/test/LinkPackage.test.ts +26 -0
- package/src/test/Linker.test.ts +125 -0
- package/src/test/Mangling.test.ts +45 -0
- package/src/test/ParseComments.test.ts +36 -0
- package/src/test/ParseConditions.test.ts +183 -0
- package/src/test/ParseError.test.ts +36 -0
- package/src/test/ParseWESL.test.ts +1572 -0
- package/src/test/PathUtil.test.ts +34 -0
- package/src/test/PrettyGrammar.test.ts +20 -0
- package/src/test/Reflection.test.ts +172 -0
- package/src/test/ScopeWESL.test.ts +462 -0
- package/src/test/TestLink.ts +82 -0
- package/src/test/TestSetup.ts +4 -0
- package/src/test/TestUtil.ts +126 -0
- package/src/test/Tokenizer.test.ts +135 -0
- package/src/test/TransformBindingStructs.test.ts +230 -0
- package/src/test/Util.test.ts +22 -0
- package/src/test/VirtualModules.test.ts +37 -0
- package/src/test/WeslDevice.test.ts +265 -0
- package/src/test/WgslTests.ts +0 -0
- package/src/test/__snapshots__/ParseDirectives.test.ts.snap +25 -0
- package/src/test/__snapshots__/ParseWESL.test.ts.snap +119 -0
- package/src/test/__snapshots__/RustDirective.test.ts.snap +359 -0
- package/src/test/wgsl_1/main.wgsl +3 -0
- package/src/test/wgsl_1/util.wgsl +1 -0
- package/src/test/wgsl_2/main2.wgsl +3 -0
- package/src/test/wgsl_2/util2.wgsl +1 -0
- package/src/vlq/vlq.ts +94 -0
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
import { srcLog } from "mini-parse";
|
|
2
|
+
import { AbstractElem } from "./AbstractElems.ts";
|
|
3
|
+
import {
|
|
4
|
+
assertThatDebug,
|
|
5
|
+
assertUnreachableSilent,
|
|
6
|
+
failDebug,
|
|
7
|
+
} from "./Assertions.ts";
|
|
8
|
+
import { elementValid, scopeValid } from "./Conditions.ts";
|
|
9
|
+
import { identToString } from "./debug/ScopeToString.ts";
|
|
10
|
+
import { FlatImport } from "./FlattenTreeImport.ts";
|
|
11
|
+
import { LinkRegistryParams, VirtualLibraryFn } from "./Linker.ts";
|
|
12
|
+
import { LiveDecls, makeLiveDecls } from "./LiveDeclarations.ts";
|
|
13
|
+
import { ManglerFn, minimalMangle } from "./Mangler.ts";
|
|
14
|
+
import { ParsedRegistry } from "./ParsedRegistry.ts";
|
|
15
|
+
import { flatImports, parseSrcModule, WeslAST } from "./ParseWESL.ts";
|
|
16
|
+
import {
|
|
17
|
+
Conditions,
|
|
18
|
+
DeclIdent,
|
|
19
|
+
publicDecl,
|
|
20
|
+
RefIdent,
|
|
21
|
+
Scope,
|
|
22
|
+
SrcModule,
|
|
23
|
+
} from "./Scope.ts";
|
|
24
|
+
import { stdEnumerant, stdFn, stdType } from "./StandardTypes.ts";
|
|
25
|
+
import { last } from "./Util.ts";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
BindIdents pass
|
|
29
|
+
|
|
30
|
+
Goals:
|
|
31
|
+
- link references identifiers to their declaration identifiers.
|
|
32
|
+
- produce a list of declarations that are used (and need to be emitted in the link)
|
|
33
|
+
- create mangled names for global declarations (to avoid name conflicts)
|
|
34
|
+
|
|
35
|
+
BindIdents proceeds as a recursive tree walk of the scope tree, starting from the root module (e.g. main.wesl).
|
|
36
|
+
It traverses the scope tree depth first (and not the syntax tree).
|
|
37
|
+
- For each ref ident, search prior declarations in the current scope, then
|
|
38
|
+
up the scope tree to find a matching declaration
|
|
39
|
+
- If no local match is found, check for partial matches with import statements
|
|
40
|
+
- combine the ident with import statement to match a decl in an exporting module
|
|
41
|
+
- As global declaration identifies are found, also:
|
|
42
|
+
- mutate their mangled name to be globally unique.
|
|
43
|
+
- collect the declarations (they will be emitted)
|
|
44
|
+
|
|
45
|
+
When iterating through the idents inside a scope, we maintain a parallel data structure of
|
|
46
|
+
'liveDecls', the declarations that are visible in the current scope at the currently
|
|
47
|
+
processed ident, along with a link to parent liveDecls for their current decl visibility.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/** results returned from binding pass */
|
|
51
|
+
export interface BindResults {
|
|
52
|
+
/** root level names (including names mangled due to conflict with earlier names) */
|
|
53
|
+
globalNames: Set<string>;
|
|
54
|
+
|
|
55
|
+
/** global declarations that were referenced (these will need to be emitted in the link) */
|
|
56
|
+
decls: DeclIdent[];
|
|
57
|
+
|
|
58
|
+
/** additional global statements to include in linked results
|
|
59
|
+
* (e.g. for adding module level const_assert statements) */
|
|
60
|
+
newStatements: EmittableElem[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** an element that can be directly emitted into the linked result */
|
|
64
|
+
export interface EmittableElem {
|
|
65
|
+
srcModule: SrcModule;
|
|
66
|
+
elem: AbstractElem;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** virtual package, generated by code generation function. */
|
|
70
|
+
export interface VirtualLibrary {
|
|
71
|
+
// LATER rename to VirtualPackage?
|
|
72
|
+
/** function to generate the module */
|
|
73
|
+
fn: VirtualLibraryFn;
|
|
74
|
+
|
|
75
|
+
/** parsed AST for the module (constructed lazily) */
|
|
76
|
+
ast?: WeslAST;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** key is virtual module name */
|
|
80
|
+
export type VirtualLibrarySet = Record<string, VirtualLibrary>;
|
|
81
|
+
|
|
82
|
+
export interface BindIdentsParams
|
|
83
|
+
extends Pick<LinkRegistryParams, "registry" | "conditions" | "mangler"> {
|
|
84
|
+
rootAst: WeslAST;
|
|
85
|
+
virtuals?: VirtualLibrarySet;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Bind active reference idents to declaration Idents by mutating the refersTo: field
|
|
90
|
+
* Also in this pass, set the mangledName: field for all active global declaration idents.
|
|
91
|
+
*
|
|
92
|
+
* @param parsed
|
|
93
|
+
* @param conditions only bind to/from idents that are valid with the current condition set
|
|
94
|
+
* @return any new declaration elements found (they will need to be emitted)
|
|
95
|
+
*/
|
|
96
|
+
export function bindIdents(params: BindIdentsParams): BindResults {
|
|
97
|
+
const { rootAst, registry, virtuals } = params;
|
|
98
|
+
const { conditions = {}, mangler = minimalMangle } = params;
|
|
99
|
+
const { rootScope } = rootAst;
|
|
100
|
+
|
|
101
|
+
const globalNames = new Set<string>();
|
|
102
|
+
const knownDecls = new Set<DeclIdent>();
|
|
103
|
+
const validRootDecls = findValidRootDecls(rootScope, conditions);
|
|
104
|
+
|
|
105
|
+
validRootDecls.forEach(decl => {
|
|
106
|
+
decl.mangledName = decl.originalName;
|
|
107
|
+
globalNames.add(decl.originalName);
|
|
108
|
+
knownDecls.add(decl);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const globalStatements = new Map<AbstractElem, EmittableElem>();
|
|
112
|
+
const bindContext = {
|
|
113
|
+
registry,
|
|
114
|
+
conditions,
|
|
115
|
+
knownDecls,
|
|
116
|
+
foundScopes: new Set<Scope>(),
|
|
117
|
+
globalNames,
|
|
118
|
+
globalStatements,
|
|
119
|
+
virtuals,
|
|
120
|
+
mangler,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// initialize liveDecls with all module level declarations
|
|
124
|
+
// (note that in wgsl module level declarations may appear in any order, incl after their references.)
|
|
125
|
+
const declEntries = validRootDecls.map(d => [d.originalName, d] as const);
|
|
126
|
+
const liveDecls: LiveDecls = { decls: new Map(declEntries), parent: null };
|
|
127
|
+
|
|
128
|
+
const decls = bindIdentsRecursive(rootScope, bindContext, liveDecls, true);
|
|
129
|
+
const filteredDecls = decls.filter(isGlobal); // TODO is this needed?
|
|
130
|
+
const newStatements = [...globalStatements.values()];
|
|
131
|
+
return { decls: filteredDecls, globalNames, newStatements };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @return the list of conditional valid declarations at the root level
|
|
136
|
+
* decls are either in the root scope or in a conditionally valid partial scope
|
|
137
|
+
*/
|
|
138
|
+
export function findValidRootDecls(
|
|
139
|
+
rootScope: Scope,
|
|
140
|
+
conditions: Conditions,
|
|
141
|
+
): DeclIdent[] {
|
|
142
|
+
const found: DeclIdent[] = [];
|
|
143
|
+
for (const e of rootScope.contents) {
|
|
144
|
+
if (e.kind === "decl") {
|
|
145
|
+
assertThatDebug(e.declElem);
|
|
146
|
+
if (elementValid(e.declElem!, conditions)) {
|
|
147
|
+
found.push(e);
|
|
148
|
+
}
|
|
149
|
+
} else if (e.kind === "partial") {
|
|
150
|
+
found.push(...findValidRootDecls(e, conditions));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return found;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** state used during the recursive scope tree walk to bind references to declarations */
|
|
157
|
+
interface BindContext {
|
|
158
|
+
registry: ParsedRegistry;
|
|
159
|
+
|
|
160
|
+
/** live runtime conditions currently defined by the user */
|
|
161
|
+
conditions: Record<string, any>;
|
|
162
|
+
|
|
163
|
+
/** decl idents discovered so far (to avoid re-traversing) */
|
|
164
|
+
knownDecls: Set<DeclIdent>;
|
|
165
|
+
|
|
166
|
+
/** save work by not processing scopes multiple times */
|
|
167
|
+
foundScopes: Set<Scope>;
|
|
168
|
+
|
|
169
|
+
/** root level names used so far (so that manglers or ast rewriting plugins can pick unique names) */
|
|
170
|
+
globalNames: Set<string>;
|
|
171
|
+
|
|
172
|
+
/** additional global statements to include in linked results
|
|
173
|
+
* (e.g. for adding module level const_assert statements)
|
|
174
|
+
* (indexed by elem for uniqueness) */
|
|
175
|
+
globalStatements: Map<AbstractElem, EmittableElem>;
|
|
176
|
+
|
|
177
|
+
/** construct unique identifer names for global declarations */
|
|
178
|
+
mangler: ManglerFn;
|
|
179
|
+
|
|
180
|
+
/** virtual libraries provided by the user (e.g. for code generators or constants) */
|
|
181
|
+
virtuals?: VirtualLibrarySet;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Recursively bind references to declarations in this scope and
|
|
186
|
+
* any child scopes referenced by these declarations.
|
|
187
|
+
* Uses a hash set of found declarations to avoid duplication
|
|
188
|
+
* @return any new declarations found
|
|
189
|
+
* @param liveDecls current set of live declaration in this scope
|
|
190
|
+
* (empty when traversing to a new scope, possibly non-empty for a partial scope)
|
|
191
|
+
* @param isRoot liveDecls refers to a prepopulated root scope
|
|
192
|
+
* (root scoope declarations may appear in any order)
|
|
193
|
+
*/
|
|
194
|
+
function bindIdentsRecursive(
|
|
195
|
+
scope: Scope,
|
|
196
|
+
bindContext: BindContext,
|
|
197
|
+
liveDecls: LiveDecls,
|
|
198
|
+
isRoot = false,
|
|
199
|
+
): DeclIdent[] {
|
|
200
|
+
// early exit if we've processed this scope before
|
|
201
|
+
const { conditions, foundScopes } = bindContext;
|
|
202
|
+
if (foundScopes.has(scope)) return [];
|
|
203
|
+
foundScopes.add(scope);
|
|
204
|
+
|
|
205
|
+
const newGlobals: DeclIdent[] = []; // new decl idents to process for binding (and return for emitting)
|
|
206
|
+
|
|
207
|
+
// active declarations in this scope
|
|
208
|
+
const newFromChildren: DeclIdent[] = [];
|
|
209
|
+
|
|
210
|
+
// process all identifiers and subscopes in this scope
|
|
211
|
+
scope.contents.forEach(child => {
|
|
212
|
+
const { kind } = child;
|
|
213
|
+
if (kind === "decl") {
|
|
214
|
+
const ident = child;
|
|
215
|
+
if (!isRoot) liveDecls.decls.set(ident.originalName, ident);
|
|
216
|
+
} else if (kind === "ref") {
|
|
217
|
+
const newDecl = handleRef(child, liveDecls, bindContext);
|
|
218
|
+
newDecl && newGlobals.push(newDecl);
|
|
219
|
+
} else {
|
|
220
|
+
const fromScope = handleScope(child, liveDecls, bindContext);
|
|
221
|
+
if (fromScope) {
|
|
222
|
+
newFromChildren.push(...fromScope);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// follow references from referenced declarations
|
|
228
|
+
const newFromRefs = newGlobals.flatMap(decl => {
|
|
229
|
+
const foundsScope = decl.scope;
|
|
230
|
+
const rootDecls = globalDeclToRootLiveDecls(decl, conditions);
|
|
231
|
+
if (rootDecls) {
|
|
232
|
+
const rootLive = makeLiveDecls(rootDecls);
|
|
233
|
+
return bindIdentsRecursive(foundsScope, bindContext, rootLive);
|
|
234
|
+
}
|
|
235
|
+
failDebug(`WARNING decl not from root ${identToString(decl)}`);
|
|
236
|
+
return [];
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return [newGlobals, newFromChildren, newFromRefs].flat();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Trace references to their declarations
|
|
244
|
+
* mutates to:
|
|
245
|
+
* mangle declarations
|
|
246
|
+
* mark references as 'std' if they match a wgsl std function or type
|
|
247
|
+
*
|
|
248
|
+
* @return the found declaration, or undefined if this ref has already been processed
|
|
249
|
+
*/
|
|
250
|
+
function handleRef(
|
|
251
|
+
ident: RefIdent,
|
|
252
|
+
liveDecls: LiveDecls,
|
|
253
|
+
bindContext: BindContext,
|
|
254
|
+
): DeclIdent | undefined {
|
|
255
|
+
const { registry, conditions } = bindContext;
|
|
256
|
+
const { virtuals } = bindContext;
|
|
257
|
+
if (!ident.refersTo && !ident.std) {
|
|
258
|
+
const foundDecl =
|
|
259
|
+
findDeclInModule(ident, liveDecls) ??
|
|
260
|
+
findQualifiedImport(ident, registry, conditions, virtuals);
|
|
261
|
+
|
|
262
|
+
if (foundDecl) {
|
|
263
|
+
ident.refersTo = foundDecl.decl;
|
|
264
|
+
return handleNewDecl(ident, foundDecl, bindContext);
|
|
265
|
+
} else if (stdWgsl(ident.originalName)) {
|
|
266
|
+
ident.std = true;
|
|
267
|
+
} else {
|
|
268
|
+
failMissingIdent(ident);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* If the child scope is conditionally valid,
|
|
275
|
+
* update liveDecls tree and recurse to process the elements in it.
|
|
276
|
+
*
|
|
277
|
+
* @return any new global declarations found
|
|
278
|
+
*/
|
|
279
|
+
function handleScope(
|
|
280
|
+
childScope: Scope,
|
|
281
|
+
liveDecls: LiveDecls,
|
|
282
|
+
bindContext: BindContext,
|
|
283
|
+
): DeclIdent[] | undefined {
|
|
284
|
+
const { conditions } = bindContext;
|
|
285
|
+
if (!scopeValid(childScope, conditions)) return;
|
|
286
|
+
const { kind } = childScope;
|
|
287
|
+
if (kind === "scope") {
|
|
288
|
+
const newLive = makeLiveDecls(liveDecls);
|
|
289
|
+
return bindIdentsRecursive(childScope, bindContext, newLive);
|
|
290
|
+
} else if (kind === "partial") {
|
|
291
|
+
return bindIdentsRecursive(childScope, bindContext, liveDecls);
|
|
292
|
+
} else {
|
|
293
|
+
assertUnreachableSilent(kind);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* If the found declaration is new, mangle its name and update the
|
|
299
|
+
* knownDecls and globalNames sets.
|
|
300
|
+
* If the found declaration is new and also a global declaration, return it
|
|
301
|
+
* for ruther processing (bindident traversing, and emitting to wgsl).
|
|
302
|
+
*/
|
|
303
|
+
function handleNewDecl(
|
|
304
|
+
refIdent: RefIdent,
|
|
305
|
+
foundDecl: FoundDecl,
|
|
306
|
+
bindContext: BindContext,
|
|
307
|
+
): DeclIdent | undefined {
|
|
308
|
+
const { decl, moduleAst } = foundDecl;
|
|
309
|
+
const { knownDecls, globalNames, mangler, globalStatements } = bindContext;
|
|
310
|
+
if (!knownDecls.has(decl)) {
|
|
311
|
+
knownDecls.add(decl);
|
|
312
|
+
|
|
313
|
+
const { srcModule } = decl;
|
|
314
|
+
const proposed = refIdent.originalName;
|
|
315
|
+
setMangledName(proposed, decl, globalNames, srcModule, mangler);
|
|
316
|
+
|
|
317
|
+
if (isGlobal(decl)) {
|
|
318
|
+
const { moduleAsserts } = moduleAst;
|
|
319
|
+
const moduleEmit = moduleAsserts?.map(elem => ({ srcModule, elem }));
|
|
320
|
+
moduleEmit?.forEach(e => globalStatements.set(e.elem, e));
|
|
321
|
+
|
|
322
|
+
return decl;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** given a global declIdent, return the liveDecls for its root scope */
|
|
328
|
+
function globalDeclToRootLiveDecls(
|
|
329
|
+
decl: DeclIdent,
|
|
330
|
+
conditions: Conditions,
|
|
331
|
+
): LiveDecls | undefined {
|
|
332
|
+
assertThatDebug(decl.isGlobal, identToString(decl));
|
|
333
|
+
let rootScope = decl.scope;
|
|
334
|
+
while (rootScope.parent) {
|
|
335
|
+
rootScope = rootScope.parent;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const rootDecls = findValidRootDecls(rootScope, conditions);
|
|
339
|
+
const entires = rootDecls.map(d => [d.originalName, d] as const);
|
|
340
|
+
const decls = new Map(entires);
|
|
341
|
+
return { decls };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** warn the user about a missing identifer */
|
|
345
|
+
function failMissingIdent(ident: RefIdent): void {
|
|
346
|
+
const { refIdentElem } = ident;
|
|
347
|
+
if (refIdentElem) {
|
|
348
|
+
const { srcModule, start, end } = refIdentElem;
|
|
349
|
+
const { debugFilePath: filePath } = srcModule;
|
|
350
|
+
const msg = `unresolved identifier '${ident.originalName}' in file: ${filePath}`; // TODO make error message clickable
|
|
351
|
+
srcLog(srcModule.src, [start, end], msg);
|
|
352
|
+
throw new Error(msg);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Mutate a DeclIdent to set a unique name for global linking
|
|
358
|
+
* using a mangling function to choose a unique name.
|
|
359
|
+
* Also update the set of globally unique names.
|
|
360
|
+
*/
|
|
361
|
+
function setMangledName(
|
|
362
|
+
proposedName: string,
|
|
363
|
+
decl: DeclIdent,
|
|
364
|
+
globalNames: Set<string>,
|
|
365
|
+
srcModule: SrcModule,
|
|
366
|
+
mangler: ManglerFn,
|
|
367
|
+
): void {
|
|
368
|
+
if (!decl.mangledName) {
|
|
369
|
+
let mangledName: string;
|
|
370
|
+
if (isGlobal(decl)) {
|
|
371
|
+
const sep = proposedName.lastIndexOf("::");
|
|
372
|
+
const name = sep === -1 ? proposedName : proposedName.slice(sep + 2);
|
|
373
|
+
mangledName = mangler(decl, srcModule, name, globalNames);
|
|
374
|
+
} else {
|
|
375
|
+
mangledName = decl.originalName;
|
|
376
|
+
}
|
|
377
|
+
decl.mangledName = mangledName;
|
|
378
|
+
globalNames.add(mangledName);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** @return true if ident is a standard wgsl type, fn, or enumerant */
|
|
383
|
+
function stdWgsl(name: string): boolean {
|
|
384
|
+
return stdType(name) || stdFn(name) || stdEnumerant(name); // TODO add tests for enumerants case (e.g. var x = read;)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** using the LiveDecls, search earlier in the scope and in parent scopes to find a matching decl ident */
|
|
388
|
+
function findDeclInModule(
|
|
389
|
+
ident: RefIdent,
|
|
390
|
+
liveDecls: LiveDecls,
|
|
391
|
+
): FoundDecl | undefined {
|
|
392
|
+
const { originalName } = ident;
|
|
393
|
+
const found = liveDecls.decls.get(originalName);
|
|
394
|
+
if (found) {
|
|
395
|
+
return { decl: found, moduleAst: ident.ast };
|
|
396
|
+
}
|
|
397
|
+
// recurse to check all idents in parent scope
|
|
398
|
+
const { parent } = liveDecls;
|
|
399
|
+
if (parent) {
|
|
400
|
+
return findDeclInModule(ident, parent);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** Match a reference identifier to a declaration in
|
|
405
|
+
* another module via an import statement
|
|
406
|
+
* or via an inline qualified ident e.g. foo::bar() */
|
|
407
|
+
function findQualifiedImport(
|
|
408
|
+
refIdent: RefIdent,
|
|
409
|
+
parsed: ParsedRegistry,
|
|
410
|
+
conditions: Conditions,
|
|
411
|
+
virtuals?: VirtualLibrarySet,
|
|
412
|
+
): FoundDecl | undefined {
|
|
413
|
+
const flatImps = flatImports(refIdent.ast);
|
|
414
|
+
|
|
415
|
+
const identParts = refIdent.originalName.split("::");
|
|
416
|
+
|
|
417
|
+
// find module path by combining identifer reference with import statement
|
|
418
|
+
const modulePathParts =
|
|
419
|
+
matchingImport(identParts, flatImps) ?? qualifiedImport(identParts);
|
|
420
|
+
|
|
421
|
+
if (modulePathParts) {
|
|
422
|
+
const { srcModule } = refIdent.ast;
|
|
423
|
+
return findExport(modulePathParts, srcModule, parsed, conditions, virtuals);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function qualifiedImport(identParts: string[]): string[] | undefined {
|
|
428
|
+
if (identParts.length > 1) return identParts;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** combine and import using the flattened import array, find an import that matches a provided identi*/
|
|
432
|
+
function matchingImport(
|
|
433
|
+
identParts: string[],
|
|
434
|
+
flatImports: FlatImport[],
|
|
435
|
+
): string[] | undefined {
|
|
436
|
+
for (const flat of flatImports) {
|
|
437
|
+
if (flat.importPath.at(-1) === identParts.at(0)) {
|
|
438
|
+
return [...flat.modulePath, ...identParts.slice(1)];
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** discovered declaration found during binding */
|
|
444
|
+
interface FoundDecl {
|
|
445
|
+
decl: DeclIdent;
|
|
446
|
+
/** module containing the decl */
|
|
447
|
+
moduleAst: WeslAST;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** @return an exported root declIdent for the provided path */
|
|
451
|
+
function findExport(
|
|
452
|
+
modulePathParts: string[],
|
|
453
|
+
srcModule: SrcModule,
|
|
454
|
+
parsed: ParsedRegistry,
|
|
455
|
+
conditions: Conditions = {},
|
|
456
|
+
virtuals?: VirtualLibrarySet,
|
|
457
|
+
): FoundDecl | undefined {
|
|
458
|
+
const fqPathParts = absoluteModulePath(modulePathParts, srcModule);
|
|
459
|
+
const modulePath = fqPathParts.slice(0, -1).join("::");
|
|
460
|
+
const moduleAst =
|
|
461
|
+
parsed.modules[modulePath] ??
|
|
462
|
+
virtualModule(modulePathParts[0], conditions, virtuals); // LATER consider virtual modules with submodules
|
|
463
|
+
|
|
464
|
+
if (!moduleAst) {
|
|
465
|
+
// TODO show error with source location
|
|
466
|
+
console.log(`ident ${modulePathParts.join("::")}, but module not found`);
|
|
467
|
+
return undefined;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const name = last(modulePathParts)!;
|
|
471
|
+
const decl = publicDecl(moduleAst.rootScope, name, conditions);
|
|
472
|
+
if (decl) {
|
|
473
|
+
return { decl, moduleAst };
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** convert a module path with super:: elements to one with no super:: elements */
|
|
478
|
+
function absoluteModulePath(
|
|
479
|
+
modulePathParts: string[],
|
|
480
|
+
srcModule: SrcModule,
|
|
481
|
+
): string[] {
|
|
482
|
+
const lastSuper = modulePathParts.findLastIndex(p => p === "super");
|
|
483
|
+
if (lastSuper > -1) {
|
|
484
|
+
const srcModuleParts = srcModule.modulePath.split("::");
|
|
485
|
+
const base = srcModuleParts.slice(0, -(lastSuper + 1));
|
|
486
|
+
const noSupers = modulePathParts.slice(lastSuper + 1);
|
|
487
|
+
return [...base, ...noSupers];
|
|
488
|
+
}
|
|
489
|
+
return modulePathParts;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** @return AST for a virtual module */
|
|
493
|
+
function virtualModule(
|
|
494
|
+
moduleName: string,
|
|
495
|
+
conditions: Conditions = {},
|
|
496
|
+
virtuals?: VirtualLibrarySet,
|
|
497
|
+
): WeslAST | undefined {
|
|
498
|
+
if (!virtuals) return undefined;
|
|
499
|
+
const found = virtuals[moduleName];
|
|
500
|
+
if (found) {
|
|
501
|
+
const { ast, fn } = found;
|
|
502
|
+
if (ast) return ast;
|
|
503
|
+
const src = fn(conditions); // generate the virtual module
|
|
504
|
+
const srcModule: SrcModule = {
|
|
505
|
+
modulePath: moduleName,
|
|
506
|
+
debugFilePath: moduleName,
|
|
507
|
+
src,
|
|
508
|
+
};
|
|
509
|
+
found.ast = parseSrcModule(srcModule); // cache parsed virtual module
|
|
510
|
+
return found.ast;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// LATER capture isGlobal in the ident during parsing
|
|
515
|
+
/** @return true if this decl is at the root scope level of a module */
|
|
516
|
+
export function isGlobal(declIdent: DeclIdent): boolean {
|
|
517
|
+
const { declElem } = declIdent;
|
|
518
|
+
if (!declElem) return false;
|
|
519
|
+
|
|
520
|
+
return ["alias", "const", "override", "fn", "struct", "gvar"].includes(
|
|
521
|
+
declElem.kind,
|
|
522
|
+
);
|
|
523
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AttributeElem,
|
|
3
|
+
ElemWithAttributes,
|
|
4
|
+
ExpressionElem,
|
|
5
|
+
IfAttribute,
|
|
6
|
+
} from "./AbstractElems.ts";
|
|
7
|
+
import { assertThatDebug, assertUnreachable } from "./Assertions.ts";
|
|
8
|
+
import { Conditions, Scope } from "./Scope.ts";
|
|
9
|
+
import { findMap } from "./Util.ts";
|
|
10
|
+
|
|
11
|
+
/** @return true if the element is valid under current Conditions */
|
|
12
|
+
export function elementValid(
|
|
13
|
+
elem: ElemWithAttributes,
|
|
14
|
+
conditions: Conditions,
|
|
15
|
+
): boolean {
|
|
16
|
+
const attributes = elem.attributes;
|
|
17
|
+
if (!attributes) return true;
|
|
18
|
+
const ifAttr = findMap(attributes, extractIfAttribute);
|
|
19
|
+
return !ifAttr || evaluateIfAttribute(ifAttr, conditions);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** @return true if the scope is valid under current conditions */
|
|
23
|
+
export function scopeValid(scope: Scope, conditions: Conditions): boolean {
|
|
24
|
+
const { ifAttribute } = scope;
|
|
25
|
+
if (!ifAttribute) return true;
|
|
26
|
+
const result = evaluateIfAttribute(ifAttribute, conditions); // LATER cache?
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** @return return IfAttribute if AttributeElem contains an IfAttribute */
|
|
31
|
+
function extractIfAttribute(elem: AttributeElem): IfAttribute | undefined {
|
|
32
|
+
const { attribute } = elem;
|
|
33
|
+
return attribute.kind === "@if" ? attribute : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** @return true if the @if attribute is valid with current Conditions */
|
|
37
|
+
function evaluateIfAttribute(
|
|
38
|
+
ifAttribute: IfAttribute,
|
|
39
|
+
conditions: Conditions,
|
|
40
|
+
): boolean {
|
|
41
|
+
return evaluateIfExpression(ifAttribute.param.expression, conditions);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Evaluate an @if expression based on current runtime Conditions
|
|
45
|
+
* @return true if the expression is true */
|
|
46
|
+
function evaluateIfExpression(
|
|
47
|
+
expression: ExpressionElem,
|
|
48
|
+
conditions: Conditions,
|
|
49
|
+
): boolean {
|
|
50
|
+
const { kind } = expression;
|
|
51
|
+
if (kind == "unary-expression") {
|
|
52
|
+
assertThatDebug(expression.operator.value === "!");
|
|
53
|
+
return !evaluateIfExpression(expression.expression, conditions);
|
|
54
|
+
} else if (kind == "binary-expression") {
|
|
55
|
+
const op = expression.operator.value;
|
|
56
|
+
assertThatDebug(op === "||" || op === "&&");
|
|
57
|
+
const leftResult = evaluateIfExpression(expression.left, conditions);
|
|
58
|
+
if (op === "||") {
|
|
59
|
+
return leftResult || evaluateIfExpression(expression.right, conditions);
|
|
60
|
+
} else if (op === "&&") {
|
|
61
|
+
return leftResult && evaluateIfExpression(expression.right, conditions);
|
|
62
|
+
} else {
|
|
63
|
+
assertUnreachable(op);
|
|
64
|
+
}
|
|
65
|
+
} else if (kind == "literal") {
|
|
66
|
+
const { value } = expression;
|
|
67
|
+
assertThatDebug(value === "true" || value === "false");
|
|
68
|
+
return value === "true";
|
|
69
|
+
} else if (kind == "parenthesized-expression") {
|
|
70
|
+
return evaluateIfExpression(expression.expression, conditions);
|
|
71
|
+
} else {
|
|
72
|
+
throw new Error("unexpected @if expression ${expression}");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ImportCollection,
|
|
3
|
+
ImportItem,
|
|
4
|
+
ImportSegment,
|
|
5
|
+
ImportStatement,
|
|
6
|
+
} from "./AbstractElems";
|
|
7
|
+
import { assertUnreachable } from "./Assertions";
|
|
8
|
+
|
|
9
|
+
export interface FlatImport {
|
|
10
|
+
importPath: string[];
|
|
11
|
+
modulePath: string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Simplify importTree into a flattened map from import paths to module paths.
|
|
16
|
+
*
|
|
17
|
+
* @return map from import path (with 'as' renaming) to module Path
|
|
18
|
+
*/
|
|
19
|
+
export function flattenTreeImport(imp: ImportStatement): FlatImport[] {
|
|
20
|
+
return recursiveResolve([], [], imp.segments, imp.finalSegment);
|
|
21
|
+
|
|
22
|
+
/** recurse through segments of path, producing */
|
|
23
|
+
function recursiveResolve(
|
|
24
|
+
resolvedImportPath: string[],
|
|
25
|
+
resolvedExportPath: string[],
|
|
26
|
+
remainingPath: ImportSegment[],
|
|
27
|
+
finalSegment: ImportCollection | ImportItem,
|
|
28
|
+
): FlatImport[] {
|
|
29
|
+
if (remainingPath.length > 0) {
|
|
30
|
+
const [segment, ...rest] = remainingPath;
|
|
31
|
+
const importPath = [...resolvedImportPath, segment.name];
|
|
32
|
+
const modulePath = [...resolvedExportPath, segment.name];
|
|
33
|
+
return recursiveResolve(importPath, modulePath, rest, finalSegment);
|
|
34
|
+
} else if (finalSegment.kind === "import-collection") {
|
|
35
|
+
// resolve path with each element in the list
|
|
36
|
+
return finalSegment.subtrees.flatMap(elem => {
|
|
37
|
+
return recursiveResolve(
|
|
38
|
+
resolvedImportPath,
|
|
39
|
+
resolvedExportPath,
|
|
40
|
+
elem.segments,
|
|
41
|
+
elem.finalSegment,
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
} else if (finalSegment.kind === "import-item") {
|
|
45
|
+
const importPath = [
|
|
46
|
+
...resolvedImportPath,
|
|
47
|
+
finalSegment.as ?? finalSegment.name,
|
|
48
|
+
];
|
|
49
|
+
const modulePath = [...resolvedExportPath, finalSegment.name];
|
|
50
|
+
return [{ importPath, modulePath }];
|
|
51
|
+
} else {
|
|
52
|
+
assertUnreachable(finalSegment);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|