wesl 0.6.49 → 0.7.1

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 (94) hide show
  1. package/dist/index.d.ts +269 -215
  2. package/dist/index.js +2911 -1539
  3. package/package.json +6 -8
  4. package/src/AbstractElems.ts +81 -81
  5. package/src/Assertions.ts +5 -5
  6. package/src/BindIdents.ts +192 -306
  7. package/src/ClickableError.ts +3 -2
  8. package/src/Conditions.ts +2 -2
  9. package/src/LinkedWesl.ts +1 -1
  10. package/src/Linker.ts +4 -3
  11. package/src/LinkerUtil.ts +1 -1
  12. package/src/Logging.ts +165 -0
  13. package/src/LowerAndEmit.ts +278 -110
  14. package/src/ModuleResolver.ts +15 -25
  15. package/src/ParseError.ts +9 -0
  16. package/src/ParseWESL.ts +30 -94
  17. package/src/RawEmit.ts +1 -4
  18. package/src/Reflection.ts +1 -1
  19. package/src/Scope.ts +3 -0
  20. package/src/Span.ts +2 -0
  21. package/src/SrcMap.ts +208 -0
  22. package/src/Stream.ts +30 -0
  23. package/src/TransformBindingStructs.ts +2 -2
  24. package/src/Util.ts +1 -1
  25. package/src/debug/ASTtoString.ts +84 -135
  26. package/src/discovery/FindUnboundIdents.ts +14 -5
  27. package/src/index.ts +4 -0
  28. package/src/parse/ContentsHelpers.ts +70 -0
  29. package/src/parse/ExpressionUtil.ts +121 -0
  30. package/src/parse/Keywords.ts +12 -12
  31. package/src/parse/OperatorBinding.ts +146 -0
  32. package/src/parse/ParseAttribute.ts +272 -0
  33. package/src/parse/ParseCall.ts +77 -0
  34. package/src/parse/ParseControlFlow.ts +129 -0
  35. package/src/parse/ParseDirective.ts +105 -0
  36. package/src/parse/ParseExpression.ts +288 -0
  37. package/src/parse/ParseFn.ts +151 -0
  38. package/src/parse/ParseGlobalVar.ts +131 -0
  39. package/src/parse/ParseIdent.ts +77 -0
  40. package/src/parse/ParseImport.ts +160 -0
  41. package/src/parse/ParseLocalVar.ts +69 -0
  42. package/src/parse/ParseLoop.ts +112 -0
  43. package/src/parse/ParseModule.ts +116 -0
  44. package/src/parse/ParseSimpleStatement.ts +162 -0
  45. package/src/parse/ParseStatement.ts +215 -0
  46. package/src/parse/ParseStruct.ts +89 -0
  47. package/src/parse/ParseType.ts +71 -0
  48. package/src/parse/ParseUtil.ts +174 -0
  49. package/src/parse/ParseValueDeclaration.ts +130 -0
  50. package/src/parse/ParseWesl.ts +51 -0
  51. package/src/parse/ParsingContext.ts +93 -0
  52. package/src/parse/WeslStream.ts +63 -20
  53. package/src/parse/stream/CachingStream.ts +48 -0
  54. package/src/parse/stream/MatchersStream.ts +85 -0
  55. package/src/parse/stream/RegexHelpers.ts +38 -0
  56. package/src/test/BevyLink.test.ts +100 -0
  57. package/src/test/BindStdTypes.test.ts +110 -0
  58. package/src/test/{BindWESL.test.ts → BindWESLV2.test.ts} +21 -22
  59. package/src/test/BulkTests.test.ts +11 -12
  60. package/src/test/ConditionLinking.test.ts +107 -0
  61. package/src/test/ConditionalElif.test.ts +1 -13
  62. package/src/test/ConditionalTranslationCases.test.ts +5 -0
  63. package/src/test/ErrorLogging.test.ts +2 -2
  64. package/src/test/ImportCasesV2.test.ts +63 -0
  65. package/src/test/LinkFails.test.ts +69 -0
  66. package/src/test/LinkPackage.test.ts +1 -1
  67. package/src/test/Linker.test.ts +75 -2
  68. package/src/test/LogCatcher.ts +53 -0
  69. package/src/test/Mangling.test.ts +1 -1
  70. package/src/test/ParseComments.test.ts +1 -2
  71. package/src/test/{ParseConditions.test.ts → ParseConditionsV2.test.ts} +57 -49
  72. package/src/test/ParseErrorV2.test.ts +73 -0
  73. package/src/test/{ParseWESL.test.ts → ParseWeslV2.test.ts} +288 -370
  74. package/src/test/{ScopeWESL.test.ts → ScopeWESLV2.test.ts} +205 -176
  75. package/src/test/TestLink.ts +51 -51
  76. package/src/test/TestSetup.ts +9 -3
  77. package/src/test/TestUtil.ts +47 -77
  78. package/src/test/TrimmedMatch.ts +40 -0
  79. package/src/test/VirtualModules.test.ts +33 -2
  80. package/src/test/WeslDevice.test.ts +9 -2
  81. package/src/test/__snapshots__/ParseWeslV2.test.ts.snap +67 -0
  82. package/src/test-util.ts +7 -0
  83. package/src/WESLCollect.ts +0 -656
  84. package/src/parse/AttributeGrammar.ts +0 -232
  85. package/src/parse/ImportGrammar.ts +0 -195
  86. package/src/parse/WeslBaseGrammar.ts +0 -11
  87. package/src/parse/WeslExpression.ts +0 -231
  88. package/src/parse/WeslGrammar.ts +0 -739
  89. package/src/test/Expression.test.ts +0 -22
  90. package/src/test/ImportSyntaxCases.test.ts +0 -24
  91. package/src/test/ParseError.test.ts +0 -45
  92. package/src/test/Reflection.test.ts +0 -176
  93. package/src/test/TransformBindingStructs.test.ts +0 -238
  94. /package/src/test/{ParseElif.test.ts → ParseElifV2.test.ts} +0 -0
package/src/BindIdents.ts CHANGED
@@ -22,67 +22,57 @@ import { stdEnumerant, stdFn, stdType } from "./StandardTypes.ts";
22
22
  import { last } from "./Util.ts";
23
23
 
24
24
  /**
25
- BindIdents pass
26
-
27
- Goals:
28
- - link references identifiers to their declaration identifiers.
29
- - produce a list of declarations that are used (and need to be emitted in the link)
30
- - create mangled names for global declarations (to avoid name conflicts)
31
-
32
- BindIdents proceeds as a recursive tree walk of the scope tree, starting from the root module (e.g. main.wesl).
33
- It traverses the scope tree depth first (and not the syntax tree).
34
- - For each ref ident, search prior declarations in the current scope, then
35
- up the scope tree to find a matching declaration
36
- - If no local match is found, check for partial matches with import statements
37
- - combine the ident with import statement to match a decl in an exporting module
38
- - As global declaration identifies are found, also:
39
- - mutate their mangled name to be globally unique.
40
- - collect the declarations (they will be emitted)
41
-
42
- When iterating through the idents inside a scope, we maintain a parallel data structure of
43
- 'liveDecls', the declarations that are visible in the current scope at the currently
44
- processed ident, along with a link to parent liveDecls for their current decl visibility.
45
-
46
- @if/@else Handling:
47
- The binding phase respects conditional compilation by tracking @else validity state as it
48
- processes sibling scopes. This prevents references from within filtered @else blocks from
49
- pulling in declarations that won't be emitted. The algorithm mirrors the emission phase's
50
- filterValidElements approach but operates on scopes rather than elements.
51
- */
52
-
53
- /** results returned from binding pass */
25
+ * BindIdents pass: link reference identifiers to declarations.
26
+ *
27
+ * Goals:
28
+ * - Link reference idents to declaration idents
29
+ * - Collect used declarations (to emit in link)
30
+ * - Create mangled names for globals to avoid conflicts
31
+ *
32
+ * Algorithm:
33
+ * - Recursive depth-first walk of scope tree (not syntax tree)
34
+ * - For each ref: search current scope, then up to parent scopes
35
+ * - If no local match: check import statements for external matches
36
+ * - For found global decls: mangle name to be unique, collect for emission
37
+ *
38
+ * LiveDecls: tracks visible declarations at the current position, with parent links.
39
+ *
40
+ * @if/@else: respects conditional compilation by tracking @else validity state,
41
+ * mirroring the emission phase's filterValidElements but on scopes.
42
+ */
43
+
44
+ /** Results returned from binding pass. */
54
45
  export interface BindResults {
55
- /** root level names (including names mangled due to conflict with earlier names) */
46
+ /** Root level names (including mangled names from conflicts). */
56
47
  globalNames: Set<string>;
57
48
 
58
- /** global declarations that were referenced (these will need to be emitted in the link) */
49
+ /** Global declarations referenced (to emit in link). */
59
50
  decls: DeclIdent[];
60
51
 
61
- /** additional global statements to include in linked results
62
- * (e.g. for adding module level const_assert statements) */
52
+ /** Additional global statements to emit (e.g., const_assert). */
63
53
  newStatements: EmittableElem[];
64
54
 
65
- /** if accumulateUnbound is true, collection of unbound module paths */
55
+ /** Unbound module paths (only if accumulateUnbound is true). */
66
56
  unbound?: string[][];
67
57
  }
68
58
 
69
- /** an element that can be directly emitted into the linked result */
59
+ /** An element that can be directly emitted into the linked result. */
70
60
  export interface EmittableElem {
71
61
  srcModule: SrcModule;
72
62
  elem: AbstractElem;
73
63
  }
74
64
 
75
- /** virtual package, generated by code generation function. */
65
+ /** Virtual package generated by code generation function. */
76
66
  export interface VirtualLibrary {
77
67
  // LATER rename to VirtualPackage?
78
- /** function to generate the module */
68
+ /** Function to generate the module. */
79
69
  fn: VirtualLibraryFn;
80
70
 
81
- /** parsed AST for the module (constructed lazily) */
71
+ /** Parsed AST for the module (constructed lazily). */
82
72
  ast?: WeslAST;
83
73
  }
84
74
 
85
- /** key is virtual module name */
75
+ /** Key is virtual module name. */
86
76
  export type VirtualLibrarySet = Record<string, VirtualLibrary>;
87
77
 
88
78
  export interface BindIdentsParams
@@ -90,25 +80,17 @@ export interface BindIdentsParams
90
80
  rootAst: WeslAST;
91
81
  virtuals?: VirtualLibrarySet;
92
82
 
93
- /** if false, throw on unbound identifiers. If true, accumulate unbound identifiers into
94
- * a BindResults.unbound. */
83
+ /** If true, accumulate unbound identifiers into BindResults.unbound instead of throwing. */
95
84
  accumulateUnbound?: true;
96
85
  }
97
86
 
98
- /**
99
- * Bind active reference idents to declaration Idents by mutating the refersTo: field
100
- * Also in this pass, set the mangledName: field for all active global declaration idents.
101
- *
102
- * @param parsed
103
- * @param conditions only bind to/from idents that are valid with the current condition set
104
- * @return any new declaration elements found (they will need to be emitted)
105
- */
87
+ /** Bind ref idents to declarations and mangle global declaration names. */
106
88
  export function bindIdents(params: BindIdentsParams): BindResults {
107
89
  const { rootAst, resolver, virtuals, accumulateUnbound } = params;
108
90
  const { conditions = {}, mangler = minimalMangle } = params;
109
91
 
110
92
  const validRootDecls = findValidRootDecls(rootAst.rootScope, conditions);
111
- const { globalNames, knownDecls } = initializeRootDecls(validRootDecls);
93
+ const { globalNames, knownDecls } = initRootDecls(validRootDecls);
112
94
 
113
95
  const bindContext = {
114
96
  resolver,
@@ -122,78 +104,74 @@ export function bindIdents(params: BindIdentsParams): BindResults {
122
104
  unbound: accumulateUnbound ? [] : undefined,
123
105
  };
124
106
 
125
- const declEntries = validRootDecls.map(d => [d.originalName, d] as const);
126
- const liveDecls: LiveDecls = { decls: new Map(declEntries), parent: null };
107
+ const decls = new Map(validRootDecls.map(d => [d.originalName, d] as const));
108
+ const liveDecls: LiveDecls = { decls, parent: null };
127
109
 
128
- const decls = bindIdentsRecursive(
110
+ // Process dependent scopes for all valid root decls (already filtered by conditions)
111
+ const fromRootDecls = validRootDecls.flatMap(decl =>
112
+ processDependentScope(decl, bindContext),
113
+ );
114
+
115
+ const fromRefs = bindIdentsRecursive(
129
116
  rootAst.rootScope,
130
117
  bindContext,
131
118
  liveDecls,
132
- true,
133
119
  );
134
120
  const newStatements = [...bindContext.globalStatements.values()];
135
- return { decls, globalNames, newStatements, unbound: bindContext.unbound };
121
+ return {
122
+ decls: [...fromRootDecls, ...fromRefs],
123
+ globalNames,
124
+ newStatements,
125
+ unbound: bindContext.unbound,
126
+ };
136
127
  }
137
128
 
138
- /** Initialize root declarations with mangled names and add to tracking sets */
139
- function initializeRootDecls(validRootDecls: DeclIdent[]): {
140
- globalNames: Set<string>;
141
- knownDecls: Set<DeclIdent>;
142
- } {
143
- const globalNames = new Set<string>();
144
- const knownDecls = new Set<DeclIdent>();
145
- validRootDecls.forEach(decl => {
146
- decl.mangledName = decl.originalName;
147
- globalNames.add(decl.originalName);
148
- knownDecls.add(decl);
149
- });
129
+ /** Initialize root declarations with mangled names and add to tracking sets. */
130
+ function initRootDecls(validRootDecls: DeclIdent[]) {
131
+ for (const d of validRootDecls) d.mangledName = d.originalName;
132
+ const knownDecls = new Set(validRootDecls);
133
+ const globalNames = new Set(validRootDecls.map(d => d.originalName));
150
134
  return { globalNames, knownDecls };
151
135
  }
152
136
 
153
- /**
154
- * Find all conditionally valid declarations at the root level.
155
- * Declarations are either directly in the root scope or in conditionally valid partial scopes.
156
- *
157
- * This function tracks @else state to ensure that declarations in @else blocks are only
158
- * included when the corresponding @if block is invalid. This is essential for preventing
159
- * unused declarations from being included in the output.
160
- *
161
- * Results are cached on the root scope's _validRootDecls field.
162
- * Used both for binding and for publicDecl() lookups.
163
- *
164
- * @param rootScope The root scope to search
165
- * @param conditions Current conditional compilation settings
166
- * @return Array of valid declaration identifiers
167
- */
137
+ /** Get conditional attribute from any scope item. */
138
+ function getCondAttr(
139
+ item: DeclIdent | RefIdent | Scope,
140
+ ): Scope["condAttribute"] {
141
+ // Decls inside PartialScopes don't need their own conditional checked -
142
+ // the PartialScope.condAttribute handles filtering at the scope level.
143
+ if (item.kind === "decl" && item.containingScope.kind === "partial")
144
+ return undefined;
145
+ if (item.kind === "decl") return findConditional(item.declElem?.attributes);
146
+ if (item.kind === "partial" || item.kind === "scope")
147
+ return item.condAttribute;
148
+ return undefined;
149
+ }
150
+
151
+ /** Iterate scope contents, yielding only conditionally valid items. */
152
+ function* validItems(scope: Scope, conditions: Conditions) {
153
+ let elseValid = false;
154
+ for (const item of scope.contents) {
155
+ const cond = validateConditional(getCondAttr(item), elseValid, conditions);
156
+ elseValid = cond.nextElseState;
157
+ if (cond.valid) yield item;
158
+ }
159
+ }
160
+
161
+ /** Find all conditionally valid declarations at the root level. */
168
162
  export function findValidRootDecls(
169
163
  rootScope: Scope,
170
164
  conditions: Conditions,
171
165
  ): DeclIdent[] {
172
166
  const found: DeclIdent[] = [];
173
- let elseValid = false;
174
-
175
- for (const item of rootScope.contents) {
176
- if (item.kind === "decl") {
177
- assertThatDebug(item.declElem);
178
- const attr = findConditional(item.declElem.attributes);
179
- const validation = validateConditional(attr, elseValid, conditions);
180
- elseValid = validation.nextElseState;
181
- if (validation.valid) found.push(item);
182
- } else if (item.kind === "partial") {
183
- const validation = validateConditional(
184
- item.condAttribute,
185
- elseValid,
186
- conditions,
187
- );
188
- elseValid = validation.nextElseState;
189
- if (validation.valid) collectDecls(item, found);
190
- }
167
+ for (const item of validItems(rootScope, conditions)) {
168
+ if (item.kind === "decl") found.push(item);
169
+ else if (item.kind === "partial") collectDecls(item, found);
191
170
  }
192
-
193
171
  return found;
194
172
  }
195
173
 
196
- /** Find a public declaration with the given original name */
174
+ /** Find a public declaration with the given original name. */
197
175
  export function publicDecl(
198
176
  scope: Scope,
199
177
  name: string,
@@ -203,212 +181,153 @@ export function publicDecl(
203
181
  return validDecls.find(d => d.originalName === name);
204
182
  }
205
183
 
206
- /** @return true if this decl is at the root scope level of a module */
207
- export function isGlobal(declIdent: DeclIdent): boolean {
208
- const { declElem } = declIdent;
209
- if (!declElem) return false;
210
-
211
- return ["alias", "const", "override", "fn", "struct", "gvar"].includes(
212
- declElem.kind,
213
- );
214
- }
215
-
216
- /** state used during the recursive scope tree walk to bind references to declarations */
184
+ /** State used during the recursive scope tree walk to bind references to declarations. */
217
185
  interface BindContext {
218
186
  resolver: ModuleResolver;
219
- conditions: Record<string, any>;
220
187
 
221
- /** decl idents discovered so far (to avoid re-traversing) */
188
+ conditions: Conditions;
189
+
190
+ /** Decl idents discovered so far (to avoid re-traversing). */
222
191
  knownDecls: Set<DeclIdent>;
223
192
 
224
- /** save work by not processing scopes multiple times */
193
+ /** Scopes already processed (avoid duplicate work). */
225
194
  foundScopes: Set<Scope>;
226
195
 
227
- /** root level names used so far (enables manglers/plugins to pick unique names) */
196
+ /** Root level names used so far (enables manglers to pick unique names). */
228
197
  globalNames: Set<string>;
229
198
 
230
- /** additional global statements to emit (e.g. module level const_assert, indexed by elem for uniqueness) */
199
+ /** Additional global statements to emit (indexed by elem for uniqueness). */
231
200
  globalStatements: Map<AbstractElem, EmittableElem>;
232
201
 
233
- /** construct unique identifer names for global declarations */
202
+ /** Construct unique identifier names for global declarations. */
234
203
  mangler: ManglerFn;
235
204
 
236
205
  virtuals?: VirtualLibrarySet;
237
206
 
238
- /** list of unbound identifiers if accumulateUnbound is true */
207
+ /** Unbound identifiers if accumulateUnbound is true. */
239
208
  unbound?: string[][];
240
209
 
241
- /** don't follow references from declarations (used for library dependency detection) */
210
+ /** Don't follow references from declarations (for library dependency detection). */
242
211
  dontFollowDecls?: boolean;
243
212
  }
244
213
 
245
214
  /**
246
- * Recursively bind references to declarations in this scope and
247
- * any child scopes referenced by these declarations.
248
- * Uses a hash set of found declarations to avoid duplication.
249
- *
250
- * IMPORTANT: This function tracks @else state while traversing sibling scopes.
251
- * When an @if scope is processed, its validity determines whether the following
252
- * @else scope should be considered valid. This ensures that references from
253
- * filtered @else blocks don't pull in unused declarations.
254
- *
255
- * @return any new declarations found
256
- * @param liveDecls current set of live declaration in this scope
257
- * (empty when traversing to a new scope, possibly non-empty for a partial scope)
258
- * @param isRoot liveDecls refers to a prepopulated root scope
259
- * (root scope declarations may appear in any order)
215
+ * Recursively bind references to declarations in this scope and child scopes.
216
+ * Tracks @else state to ensure filtered @else blocks don't pull in unused declarations.
217
+ * @return new declarations found
260
218
  */
261
219
  export function bindIdentsRecursive(
262
220
  scope: Scope,
263
221
  bindContext: BindContext,
264
222
  liveDecls: LiveDecls,
265
- isRoot = false,
266
223
  ): DeclIdent[] {
267
224
  const { dontFollowDecls, foundScopes } = bindContext;
268
225
  if (foundScopes.has(scope)) return [];
269
226
  foundScopes.add(scope);
270
227
 
271
- const { newGlobals, newFromChildren } = processScope(
272
- scope,
273
- bindContext,
274
- liveDecls,
275
- isRoot,
276
- );
277
-
228
+ const result = processScope(scope, bindContext, liveDecls);
229
+ const { newGlobals, newFromChildren } = result;
278
230
  const newFromRefs = dontFollowDecls
279
231
  ? []
280
232
  : handleDecls(newGlobals, bindContext);
281
-
282
233
  return [newGlobals, newFromChildren, newFromRefs].flat();
283
234
  }
284
235
 
285
- /** Process all identifiers and subscopes in this scope */
236
+ /** Process all identifiers and subscopes in this scope. */
286
237
  function processScope(
287
238
  scope: Scope,
288
239
  bindContext: BindContext,
289
240
  liveDecls: LiveDecls,
290
- isRoot: boolean,
291
241
  ): { newGlobals: DeclIdent[]; newFromChildren: DeclIdent[] } {
292
242
  const newGlobals: DeclIdent[] = [];
293
243
  const newFromChildren: DeclIdent[] = [];
294
- let elseValid = false;
295
244
 
296
- scope.contents.forEach(child => {
245
+ for (const child of validItems(scope, bindContext.conditions)) {
297
246
  if (child.kind === "decl") {
298
- if (!isRoot) liveDecls.decls.set(child.originalName, child);
247
+ liveDecls.decls.set(child.originalName, child);
299
248
  } else if (child.kind === "ref") {
300
249
  const newDecl = handleRef(child, liveDecls, bindContext);
301
250
  if (newDecl) newGlobals.push(newDecl);
302
251
  } else {
303
- const fromScope = handleScope(child, liveDecls, bindContext, elseValid);
304
- if (fromScope) {
305
- newFromChildren.push(...fromScope.decls);
306
- elseValid = fromScope.nextElseState;
307
- }
252
+ const newLive =
253
+ child.kind === "scope" ? makeLiveDecls(liveDecls) : liveDecls;
254
+ newFromChildren.push(...bindIdentsRecursive(child, bindContext, newLive));
308
255
  }
309
- });
310
-
256
+ }
311
257
  return { newGlobals, newFromChildren };
312
258
  }
313
259
 
260
+ /** Process dependent scope for a single declaration. */
261
+ function processDependentScope(decl: DeclIdent, ctx: BindContext): DeclIdent[] {
262
+ const { dependentScope } = decl;
263
+ if (!dependentScope) return [];
264
+ const rootDecls = rootLiveDecls(decl, ctx.conditions);
265
+ if (!rootDecls) return [];
266
+ return bindIdentsRecursive(dependentScope, ctx, makeLiveDecls(rootDecls));
267
+ }
268
+
314
269
  /**
315
270
  * Trace references to their declarations.
316
271
  * Mutates to mangle declarations and mark std references.
317
- *
318
- * @return the found declaration, or undefined if this ref has already been processed
272
+ * @return found declaration, or undefined if already processed
319
273
  */
320
274
  function handleRef(
321
275
  ident: RefIdent,
322
276
  liveDecls: LiveDecls,
323
277
  bindContext: BindContext,
324
278
  ): DeclIdent | undefined {
325
- const { resolver, conditions, unbound, virtuals } = bindContext;
326
279
  if (ident.refersTo || ident.std) return;
327
280
 
281
+ // Skip binding for condition refs - they resolve via Conditions map (for now)
282
+ if (ident.conditionRef) return;
283
+
328
284
  const foundDecl =
329
285
  findDeclInModule(ident, liveDecls) ??
330
- findQualifiedImport(ident, resolver, conditions, virtuals, unbound);
286
+ findQualifiedImport(ident, bindContext);
331
287
 
332
288
  if (foundDecl) {
333
289
  ident.refersTo = foundDecl.decl;
334
290
  return handleNewDecl(ident, foundDecl, bindContext);
335
- } else if (stdWgsl(ident.originalName)) {
336
- ident.std = true;
337
- } else if (!unbound) {
338
- failIdent(ident, `unresolved identifier '${ident.originalName}'`);
339
291
  }
340
- }
341
292
 
342
- /**
343
- * Process a child scope, checking its conditional validity,
344
- * and tracking @else validity for siblings.
345
- * Updates liveDecls tree and recurses to process the elements in valid scopes.
346
- */
347
- function handleScope(
348
- childScope: Scope,
349
- liveDecls: LiveDecls,
350
- bindContext: BindContext,
351
- elseValid: boolean,
352
- ): { decls: DeclIdent[]; nextElseState: boolean } | undefined {
353
- const { valid, nextElseState } = validateConditional(
354
- childScope.condAttribute,
355
- elseValid,
356
- bindContext.conditions,
357
- );
358
- if (!valid) return { decls: [], nextElseState };
293
+ if (stdWgsl(ident.originalName)) {
294
+ ident.std = true;
295
+ return;
296
+ }
359
297
 
360
- const newLiveDecls =
361
- childScope.kind === "scope" ? makeLiveDecls(liveDecls) : liveDecls;
362
- const decls = bindIdentsRecursive(childScope, bindContext, newLiveDecls);
363
- return { decls, nextElseState };
298
+ if (!bindContext.unbound)
299
+ failIdent(ident, `unresolved identifier '${ident.originalName}'`);
364
300
  }
365
301
 
366
302
  function handleDecls(
367
303
  newGlobals: DeclIdent[],
368
304
  bindContext: BindContext,
369
305
  ): DeclIdent[] {
370
- const { conditions } = bindContext;
371
- return newGlobals.flatMap(decl => {
372
- const foundScope = decl.dependentScope;
373
- if (!foundScope) return [];
374
- const rootDecls = rootLiveDecls(decl, conditions);
375
- if (!rootDecls) return [];
376
- const rootLive = makeLiveDecls(rootDecls);
377
- return bindIdentsRecursive(foundScope, bindContext, rootLive);
378
- });
306
+ return newGlobals.flatMap(decl => processDependentScope(decl, bindContext));
379
307
  }
380
308
 
381
- /**
382
- * If the found declaration is new, mangle its name and update the
383
- * knownDecls and globalNames sets. Return it if it's a global declaration.
384
- */
309
+ /** If found declaration is new, mangle its name. Return if it's a global declaration. */
385
310
  function handleNewDecl(
386
311
  refIdent: RefIdent,
387
312
  foundDecl: FoundDecl,
388
- bindContext: BindContext,
313
+ ctx: BindContext,
389
314
  ): DeclIdent | undefined {
390
315
  const { decl, moduleAst } = foundDecl;
391
- const { knownDecls, globalNames, mangler, globalStatements } = bindContext;
316
+ const { knownDecls, globalNames, mangler, globalStatements } = ctx;
392
317
  if (knownDecls.has(decl)) return;
393
318
 
394
319
  knownDecls.add(decl);
395
- setMangledName(
396
- refIdent.originalName,
397
- decl,
398
- globalNames,
399
- decl.srcModule,
400
- mangler,
401
- );
320
+ const name = refIdent.originalName;
321
+ setMangledName(name, decl, globalNames, decl.srcModule, mangler);
322
+ if (!decl.isGlobal) return;
402
323
 
403
- if (isGlobal(decl)) {
404
- moduleAst.moduleAsserts?.forEach(elem => {
405
- globalStatements.set(elem, { srcModule: decl.srcModule, elem });
406
- });
407
- return decl;
324
+ for (const elem of moduleAst.moduleAsserts ?? []) {
325
+ globalStatements.set(elem, { srcModule: decl.srcModule, elem });
408
326
  }
327
+ return decl;
409
328
  }
410
329
 
411
- /** Search earlier in the scope and parent scopes for a matching declaration */
330
+ /** Search current scope and parent scopes for a matching declaration. */
412
331
  function findDeclInModule(
413
332
  ident: RefIdent,
414
333
  liveDecls: LiveDecls,
@@ -418,146 +337,119 @@ function findDeclInModule(
418
337
  if (liveDecls.parent) return findDeclInModule(ident, liveDecls.parent);
419
338
  }
420
339
 
421
- /**
422
- * Match a reference identifier to a declaration in another module via an import statement
423
- * or via an inline qualified ident e.g. foo::bar().
424
- */
340
+ /** Match a ref ident to a declaration in another module via import or qualified ident. */
425
341
  function findQualifiedImport(
426
342
  refIdent: RefIdent,
427
- resolver: ModuleResolver,
428
- conditions: Conditions,
429
- virtuals: VirtualLibrarySet | undefined,
430
- unbound: string[][] | undefined,
343
+ ctx: BindContext,
431
344
  ): FoundDecl | undefined {
345
+ const { conditions, unbound } = ctx;
432
346
  const flatImps = flatImports(refIdent.ast, conditions);
433
347
  const identParts = refIdent.originalName.split("::");
434
- const modulePathParts =
348
+ const pathParts =
435
349
  matchingImport(identParts, flatImps) ?? qualifiedIdent(identParts);
436
350
 
437
- if (!modulePathParts) {
351
+ if (!pathParts) {
438
352
  if (unbound && !stdWgsl(refIdent.originalName)) unbound.push(identParts);
439
353
  return undefined;
440
354
  }
441
355
 
442
- const result = findExport(
443
- modulePathParts,
444
- refIdent.ast.srcModule,
445
- resolver,
446
- conditions,
447
- virtuals,
448
- );
356
+ const result = findExport(pathParts, refIdent.ast.srcModule, ctx);
449
357
  if (!result) {
450
- if (unbound) {
451
- unbound.push(modulePathParts);
452
- } else {
453
- failIdent(
454
- refIdent,
455
- `module not found for '${modulePathParts.join("::")}'`,
456
- );
457
- }
358
+ if (unbound) unbound.push(pathParts);
359
+ else failIdent(refIdent, `module not found for '${pathParts.join("::")}'`);
458
360
  }
459
361
  return result;
460
362
  }
461
363
 
462
- /** Find an import statement that matches a provided identifier */
364
+ /** Find an import statement that matches a provided identifier. */
463
365
  function matchingImport(
464
366
  identParts: string[],
465
- flatImports: FlatImport[],
367
+ imports: FlatImport[],
466
368
  ): string[] | undefined {
467
- for (const flat of flatImports) {
468
- if (flat.importPath.at(-1) === identParts.at(0)) {
469
- return [...flat.modulePath, ...identParts.slice(1)];
470
- }
471
- }
369
+ const flat = imports.find(f => f.importPath.at(-1) === identParts[0]);
370
+ if (flat) return [...flat.modulePath, ...identParts.slice(1)];
472
371
  }
473
372
 
474
- /** discovered declaration found during binding */
373
+ /** Discovered declaration found during binding. */
475
374
  interface FoundDecl {
476
375
  decl: DeclIdent;
477
376
  /** module containing the decl */
478
377
  moduleAst: WeslAST;
479
378
  }
480
379
 
481
- /** @return an exported root declIdent for the provided path */
380
+ /** @return an exported root declIdent for the provided path. */
482
381
  function findExport(
483
- modulePathParts: string[],
382
+ pathParts: string[],
484
383
  srcModule: SrcModule,
485
- resolver: ModuleResolver,
486
- conditions: Conditions = {},
487
- virtuals?: VirtualLibrarySet,
384
+ ctx: BindContext,
488
385
  ): FoundDecl | undefined {
489
- const srcModuleParts = srcModule.modulePath.split("::");
490
- const fqPathParts = resolveSuper(modulePathParts, srcModuleParts);
491
- const modulePath = fqPathParts.slice(0, -1).join("::");
386
+ const { resolver, conditions, virtuals } = ctx;
387
+ const srcParts = srcModule.modulePath.split("::");
388
+ const fqParts = resolveSuper(pathParts, srcParts);
389
+ const modulePath = fqParts.slice(0, -1).join("::");
390
+
492
391
  const moduleAst =
493
392
  resolver.resolveModule(modulePath) ??
494
- virtualModule(modulePathParts[0], conditions, virtuals);
393
+ virtualModule(pathParts[0], conditions, virtuals);
495
394
  if (!moduleAst) return undefined;
496
395
 
497
- const name = last(modulePathParts)!;
498
- const decl = publicDecl(moduleAst.rootScope, name, conditions);
396
+ const decl = publicDecl(moduleAst.rootScope, last(pathParts)!, conditions);
499
397
  if (decl) return { decl, moduleAst };
500
398
  }
501
399
 
502
- /** @return AST for a virtual module */
400
+ /** @return AST for a virtual module. */
503
401
  function virtualModule(
504
402
  moduleName: string,
505
403
  conditions: Conditions = {},
506
404
  virtuals?: VirtualLibrarySet,
507
405
  ): WeslAST | undefined {
508
- if (!virtuals) return undefined;
509
- const found = virtuals[moduleName];
406
+ const found = virtuals?.[moduleName];
510
407
  if (!found) return undefined;
511
-
512
408
  if (found.ast) return found.ast;
409
+
513
410
  const src = found.fn(conditions);
514
- const srcModule: SrcModule = {
515
- modulePath: moduleName,
516
- debugFilePath: moduleName,
517
- src,
518
- };
519
- found.ast = parseSrcModule(srcModule);
411
+ const modulePath = moduleName;
412
+ const debugFilePath = moduleName;
413
+ found.ast = parseSrcModule({ modulePath, debugFilePath, src });
520
414
  return found.ast;
521
415
  }
522
416
 
523
- /** Get cached valid root declarations, computing on first access */
417
+ /** Get cached valid root declarations, computing on first access. */
524
418
  function getValidRootDecls(
525
419
  rootScope: Scope,
526
420
  conditions: Conditions,
527
421
  ): DeclIdent[] {
528
422
  const lexScope = rootScope as LexicalScope;
529
- if (lexScope._validRootDecls) return lexScope._validRootDecls;
530
-
531
- const valid = findValidRootDecls(rootScope, conditions);
532
- lexScope._validRootDecls = valid;
533
- return valid;
423
+ if (!lexScope._validRootDecls) {
424
+ lexScope._validRootDecls = findValidRootDecls(rootScope, conditions);
425
+ }
426
+ return lexScope._validRootDecls;
534
427
  }
535
428
 
536
- /** Given a global declIdent, return the liveDecls for its root scope */
429
+ /** Given a global declIdent, return the liveDecls for its root scope. */
537
430
  function rootLiveDecls(
538
431
  decl: DeclIdent,
539
432
  conditions: Conditions,
540
433
  ): LiveDecls | undefined {
541
434
  assertThatDebug(decl.isGlobal, identToString(decl));
542
- let rootScope = decl.containingScope;
543
- while (rootScope.parent) {
544
- rootScope = rootScope.parent;
435
+
436
+ let scope = decl.containingScope;
437
+ while (scope.parent) {
438
+ scope = scope.parent;
545
439
  }
546
- assertThatDebug(rootScope.kind === "scope");
547
- const root = rootScope as LexicalScope;
548
- if (root._scopeDecls) return root._scopeDecls;
549
-
550
- const rootDecls = findValidRootDecls(rootScope, conditions);
551
- const entries = rootDecls.map(d => [d.originalName, d] as const);
552
- const liveDecls = { decls: new Map(entries) };
553
- root._scopeDecls = liveDecls;
554
- return liveDecls;
440
+ assertThatDebug(scope.kind === "scope");
441
+
442
+ const root = scope as LexicalScope;
443
+ if (!root._scopeDecls) {
444
+ const rootDecls = findValidRootDecls(scope, conditions);
445
+ root._scopeDecls = {
446
+ decls: new Map(rootDecls.map(d => [d.originalName, d])),
447
+ };
448
+ }
449
+ return root._scopeDecls;
555
450
  }
556
451
 
557
- /**
558
- * Set a globally unique mangled name for this declaration.
559
- * Uses the mangler function to avoid name collisions.
560
- */
452
+ /** Set a globally unique mangled name for this declaration. */
561
453
  function setMangledName(
562
454
  proposedName: string,
563
455
  decl: DeclIdent,
@@ -567,19 +459,16 @@ function setMangledName(
567
459
  ): void {
568
460
  if (decl.mangledName) return;
569
461
 
570
- let mangledName: string;
571
- if (isGlobal(decl)) {
572
- const sep = proposedName.lastIndexOf("::");
573
- const name = sep === -1 ? proposedName : proposedName.slice(sep + 2);
574
- mangledName = mangler(decl, srcModule, name, globalNames);
575
- } else {
576
- mangledName = decl.originalName;
577
- }
462
+ const sep = proposedName.lastIndexOf("::");
463
+ const name = sep === -1 ? proposedName : proposedName.slice(sep + 2);
464
+ const mangledName = decl.isGlobal
465
+ ? mangler(decl, srcModule, name, globalNames)
466
+ : decl.originalName;
578
467
  decl.mangledName = mangledName;
579
468
  globalNames.add(mangledName);
580
469
  }
581
470
 
582
- /** @return true if ident is a standard wgsl type, fn, or enumerant */
471
+ /** @return true if ident is a standard WGSL type, fn, or enumerant. */
583
472
  function stdWgsl(name: string): boolean {
584
473
  return stdType(name) || stdFn(name) || stdEnumerant(name); // TODO add tests for enumerants case (e.g. var x = read;)
585
474
  }
@@ -588,13 +477,10 @@ function qualifiedIdent(identParts: string[]): string[] | undefined {
588
477
  if (identParts.length > 1) return identParts;
589
478
  }
590
479
 
591
- /** Collect all declarations in a scope (used when scope is already validated) */
480
+ /** Collect all declarations in a scope (used when scope is already validated). */
592
481
  function collectDecls(scope: Scope, found: DeclIdent[]): void {
593
482
  for (const item of scope.contents) {
594
- if (item.kind === "decl") {
595
- found.push(item);
596
- } else if (item.kind === "partial") {
597
- collectDecls(item, found);
598
- }
483
+ if (item.kind === "decl") found.push(item);
484
+ else if (item.kind === "partial") collectDecls(item, found);
599
485
  }
600
486
  }