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.
Files changed (141) hide show
  1. package/README.md +31 -0
  2. package/dist/index.js +4468 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/minified.js +3426 -0
  5. package/dist/minified.js.map +1 -0
  6. package/dist/tools/packages/wesl/src/AbstractElems.d.ts +322 -0
  7. package/dist/tools/packages/wesl/src/Assertions.d.ts +27 -0
  8. package/dist/tools/packages/wesl/src/BindIdents.d.ts +70 -0
  9. package/dist/tools/packages/wesl/src/Conditions.d.ts +6 -0
  10. package/dist/tools/packages/wesl/src/FlattenTreeImport.d.ts +11 -0
  11. package/dist/tools/packages/wesl/src/LinkedWesl.d.ts +50 -0
  12. package/dist/tools/packages/wesl/src/Linker.d.ts +87 -0
  13. package/dist/tools/packages/wesl/src/LinkerUtil.d.ts +3 -0
  14. package/dist/tools/packages/wesl/src/LiveDeclarations.d.ts +12 -0
  15. package/dist/tools/packages/wesl/src/LowerAndEmit.d.ts +31 -0
  16. package/dist/tools/packages/wesl/src/Mangler.d.ts +39 -0
  17. package/dist/tools/packages/wesl/src/ParseWESL.d.ts +60 -0
  18. package/dist/tools/packages/wesl/src/ParsedRegistry.d.ts +29 -0
  19. package/dist/tools/packages/wesl/src/PathUtil.d.ts +6 -0
  20. package/dist/tools/packages/wesl/src/RawEmit.d.ts +6 -0
  21. package/dist/tools/packages/wesl/src/Reflection.d.ts +45 -0
  22. package/dist/tools/packages/wesl/src/Scope.d.ts +81 -0
  23. package/dist/tools/packages/wesl/src/StandardTypes.d.ts +13 -0
  24. package/dist/tools/packages/wesl/src/TransformBindingStructs.d.ts +52 -0
  25. package/dist/tools/packages/wesl/src/Util.d.ts +43 -0
  26. package/dist/tools/packages/wesl/src/WESLCollect.d.ts +94 -0
  27. package/dist/tools/packages/wesl/src/WeslBundle.d.ts +13 -0
  28. package/dist/tools/packages/wesl/src/WeslDevice.d.ts +25 -0
  29. package/dist/tools/packages/wesl/src/debug/ASTtoString.d.ts +5 -0
  30. package/dist/tools/packages/wesl/src/debug/ImportToString.d.ts +2 -0
  31. package/dist/tools/packages/wesl/src/debug/LineWrapper.d.ts +21 -0
  32. package/dist/tools/packages/wesl/src/debug/ScopeToString.d.ts +6 -0
  33. package/dist/tools/packages/wesl/src/index.d.ts +11 -0
  34. package/dist/tools/packages/wesl/src/parse/ImportGrammar.d.ts +5 -0
  35. package/dist/tools/packages/wesl/src/parse/Keywords.d.ts +4 -0
  36. package/dist/tools/packages/wesl/src/parse/WeslBaseGrammar.d.ts +5 -0
  37. package/dist/tools/packages/wesl/src/parse/WeslExpression.d.ts +13 -0
  38. package/dist/tools/packages/wesl/src/parse/WeslGrammar.d.ts +80 -0
  39. package/dist/tools/packages/wesl/src/parse/WeslStream.d.ts +44 -0
  40. package/dist/tools/packages/wesl/src/test/BindWESL.test.d.ts +1 -0
  41. package/dist/tools/packages/wesl/src/test/ConditionLinking.test.d.ts +1 -0
  42. package/dist/tools/packages/wesl/src/test/ConditionalTranslationCases.test.d.ts +1 -0
  43. package/dist/tools/packages/wesl/src/test/ErrorLogging.test.d.ts +1 -0
  44. package/dist/tools/packages/wesl/src/test/Expression.test.d.ts +1 -0
  45. package/dist/tools/packages/wesl/src/test/FlattenTreeImport.test.d.ts +1 -0
  46. package/dist/tools/packages/wesl/src/test/ImportCases.test.d.ts +1 -0
  47. package/dist/tools/packages/wesl/src/test/ImportSyntaxCases.test.d.ts +1 -0
  48. package/dist/tools/packages/wesl/src/test/LinkGlob.test.d.ts +1 -0
  49. package/dist/tools/packages/wesl/src/test/LinkPackage.test.d.ts +1 -0
  50. package/dist/tools/packages/wesl/src/test/Linker.test.d.ts +1 -0
  51. package/dist/tools/packages/wesl/src/test/Mangling.test.d.ts +1 -0
  52. package/dist/tools/packages/wesl/src/test/ParseComments.test.d.ts +1 -0
  53. package/dist/tools/packages/wesl/src/test/ParseConditions.test.d.ts +1 -0
  54. package/dist/tools/packages/wesl/src/test/ParseError.test.d.ts +1 -0
  55. package/dist/tools/packages/wesl/src/test/ParseWESL.test.d.ts +1 -0
  56. package/dist/tools/packages/wesl/src/test/PathUtil.test.d.ts +1 -0
  57. package/dist/tools/packages/wesl/src/test/PrettyGrammar.test.d.ts +1 -0
  58. package/dist/tools/packages/wesl/src/test/Reflection.test.d.ts +1 -0
  59. package/dist/tools/packages/wesl/src/test/ScopeWESL.test.d.ts +1 -0
  60. package/dist/tools/packages/wesl/src/test/TestLink.d.ts +21 -0
  61. package/dist/tools/packages/wesl/src/test/TestSetup.d.ts +1 -0
  62. package/dist/tools/packages/wesl/src/test/TestUtil.d.ts +40 -0
  63. package/dist/tools/packages/wesl/src/test/Tokenizer.test.d.ts +1 -0
  64. package/dist/tools/packages/wesl/src/test/TransformBindingStructs.test.d.ts +1 -0
  65. package/dist/tools/packages/wesl/src/test/Util.test.d.ts +1 -0
  66. package/dist/tools/packages/wesl/src/test/VirtualModules.test.d.ts +1 -0
  67. package/dist/tools/packages/wesl/src/test/WeslDevice.test.d.ts +1 -0
  68. package/dist/tools/packages/wesl/src/test/WgslTests.d.ts +0 -0
  69. package/dist/tools/packages/wesl/src/vlq/vlq.d.ts +11 -0
  70. package/package.json +46 -0
  71. package/src/AbstractElems.ts +446 -0
  72. package/src/Assertions.ts +51 -0
  73. package/src/BindIdents.ts +523 -0
  74. package/src/Conditions.ts +74 -0
  75. package/src/FlattenTreeImport.ts +55 -0
  76. package/src/LinkedWesl.ts +184 -0
  77. package/src/Linker.ts +284 -0
  78. package/src/LinkerUtil.ts +29 -0
  79. package/src/LiveDeclarations.ts +31 -0
  80. package/src/LowerAndEmit.ts +413 -0
  81. package/src/Mangler.ts +94 -0
  82. package/src/ParseWESL.ts +157 -0
  83. package/src/ParsedRegistry.ts +120 -0
  84. package/src/PathUtil.ts +31 -0
  85. package/src/RawEmit.ts +102 -0
  86. package/src/Reflection.ts +334 -0
  87. package/src/Scope.ts +162 -0
  88. package/src/StandardTypes.ts +97 -0
  89. package/src/TransformBindingStructs.ts +319 -0
  90. package/src/Util.ts +194 -0
  91. package/src/WESLCollect.ts +614 -0
  92. package/src/WeslBundle.ts +16 -0
  93. package/src/WeslDevice.ts +209 -0
  94. package/src/debug/ASTtoString.ts +290 -0
  95. package/src/debug/ImportToString.ts +29 -0
  96. package/src/debug/LineWrapper.ts +70 -0
  97. package/src/debug/ScopeToString.ts +79 -0
  98. package/src/index.ts +11 -0
  99. package/src/parse/ImportGrammar.ts +157 -0
  100. package/src/parse/Keywords.ts +26 -0
  101. package/src/parse/WeslBaseGrammar.ts +8 -0
  102. package/src/parse/WeslExpression.ts +207 -0
  103. package/src/parse/WeslGrammar.ts +856 -0
  104. package/src/parse/WeslStream.ts +279 -0
  105. package/src/test/BindWESL.test.ts +57 -0
  106. package/src/test/ConditionLinking.test.ts +91 -0
  107. package/src/test/ConditionalTranslationCases.test.ts +56 -0
  108. package/src/test/ErrorLogging.test.ts +30 -0
  109. package/src/test/Expression.test.ts +22 -0
  110. package/src/test/FlattenTreeImport.test.ts +74 -0
  111. package/src/test/ImportCases.test.ts +56 -0
  112. package/src/test/ImportSyntaxCases.test.ts +24 -0
  113. package/src/test/LinkGlob.test.ts +25 -0
  114. package/src/test/LinkPackage.test.ts +26 -0
  115. package/src/test/Linker.test.ts +125 -0
  116. package/src/test/Mangling.test.ts +45 -0
  117. package/src/test/ParseComments.test.ts +36 -0
  118. package/src/test/ParseConditions.test.ts +183 -0
  119. package/src/test/ParseError.test.ts +36 -0
  120. package/src/test/ParseWESL.test.ts +1572 -0
  121. package/src/test/PathUtil.test.ts +34 -0
  122. package/src/test/PrettyGrammar.test.ts +20 -0
  123. package/src/test/Reflection.test.ts +172 -0
  124. package/src/test/ScopeWESL.test.ts +462 -0
  125. package/src/test/TestLink.ts +82 -0
  126. package/src/test/TestSetup.ts +4 -0
  127. package/src/test/TestUtil.ts +126 -0
  128. package/src/test/Tokenizer.test.ts +135 -0
  129. package/src/test/TransformBindingStructs.test.ts +230 -0
  130. package/src/test/Util.test.ts +22 -0
  131. package/src/test/VirtualModules.test.ts +37 -0
  132. package/src/test/WeslDevice.test.ts +265 -0
  133. package/src/test/WgslTests.ts +0 -0
  134. package/src/test/__snapshots__/ParseDirectives.test.ts.snap +25 -0
  135. package/src/test/__snapshots__/ParseWESL.test.ts.snap +119 -0
  136. package/src/test/__snapshots__/RustDirective.test.ts.snap +359 -0
  137. package/src/test/wgsl_1/main.wgsl +3 -0
  138. package/src/test/wgsl_1/util.wgsl +1 -0
  139. package/src/test/wgsl_2/main2.wgsl +3 -0
  140. package/src/test/wgsl_2/util2.wgsl +1 -0
  141. 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
+ }