wesl 0.7.25 → 0.7.27

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 CHANGED
@@ -41,5 +41,5 @@ fn random_color(uv: vec2u) -> vec3f {
41
41
  </pre>
42
42
 
43
43
  [wesl plugin]: https://www.npmjs.com/package/wesl-plugin
44
- [examples]: https://github.com/wgsl-tooling-wg/wesl-js/tree/main/examples
44
+ [examples]: https://github.com/webgpu-tools/wesl-js/tree/main/examples
45
45
  [wesl]: https://www.npmjs.com/package/wesl
package/dist/index.d.ts CHANGED
@@ -279,7 +279,7 @@ type ElemKindMap = {
279
279
  type: TypeRefElem;
280
280
  var: VarElem;
281
281
  };
282
- /** Inspired by https://github.com/wgsl-tooling-wg/wesl-rs/blob/3b2434eac1b2ebda9eb8bfb25f43d8600d819872/crates/wgsl-parse/src/syntax.rs#L364 */
282
+ /** Inspired by https://github.com/webgpu-tools/wesl-rs/blob/3b2434eac1b2ebda9eb8bfb25f43d8600d819872/crates/wgsl-parse/src/syntax.rs#L364 */
283
283
  type ExpressionElem = Literal | RefIdentElem | TypeRefElem | ParenthesizedExpression | ComponentExpression | ComponentMemberExpression | UnaryExpression | BinaryExpression | FunctionCallExpression;
284
284
  type TerminalElem = DirectiveElem | DeclIdentElem | NameElem | RefIdentElem | TextElem | ImportElem;
285
285
  type GlobalDeclarationElem = AliasElem | ConstElem | FnElem | GlobalVarElem | OverrideElem | StructElem;
@@ -737,7 +737,7 @@ globalNames: Set<string>) => string;
737
737
  /**
738
738
  * Construct a globally unique name based on the declaration
739
739
  * module path separated by underscores.
740
- * Corresponds to "Underscore-count mangling" from [NameMangling.md](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/NameMangling.md)
740
+ * Corresponds to "Underscore-count mangling" from [NameMangling.md](https://github.com/webgpu-tools/wesl-spec/blob/main/NameMangling.md)
741
741
  */
742
742
  declare function underscoreMangle(decl: DeclIdent, srcModule: SrcModule): string;
743
743
  /**
@@ -844,27 +844,15 @@ interface LinkConfig {
844
844
  plugins?: WeslJsPlugin[];
845
845
  }
846
846
  interface LinkParams {
847
- /** Module resolver for lazy loading (NEW API - preferred)
848
- * Replaces weslSrc for lazy module loading.
849
- * If provided, weslSrc will be ignored. */
847
+ /** Module resolver for lazy loading. If provided, weslSrc is ignored. */
850
848
  resolver?: ModuleResolver;
851
- /** record of file paths and wesl text for modules (LEGACY API - still supported).
852
- * key is module path or file path
853
- * `package::foo::bar`, or './foo/bar.wesl', or './foo/bar'
854
- * value is wesl src
855
- *
856
- *
857
- * Only accepts unix-style, relative filesystem paths that are valid WGSL identifiers
858
- * - Unix-style: Slashes as separators.
859
- * - Valid WGSL identifiers: No backslashes, no `..`, or other non-identifier symbols.
860
- * - Relative paths: They have to be relative to the wesl root.
861
- */
849
+ /** Record of module sources keyed by module path (`package::foo::bar`)
850
+ * or relative file path (`./foo/bar.wesl`). Paths must be unix-style,
851
+ * relative to the wesl root, and valid WGSL identifiers. */
862
852
  weslSrc?: Record<string, string>;
863
- /** name of root wesl module
864
- * for an app, the root module normally contains the '@compute', '@vertex' or '@fragment' entry points
865
- * for a library, the root module defines the public api fo the library
866
- * can be specified as file path (./main.wesl), a module path (package::main), or just a module name (main)
867
- */
853
+ /** Root module name: file path (`./main.wesl`), module path (`package::main`), or bare name (`main`).
854
+ * For apps, the root module contains entry points (`@compute`, `@vertex`, `@fragment`).
855
+ * For libraries, it defines the public API. */
868
856
  rootModuleName?: string;
869
857
  /** For debug logging. Will be prepended to file paths. */
870
858
  debugWeslRoot?: string;
@@ -888,9 +876,17 @@ interface LinkParams {
888
876
  mangler?: ManglerFn;
889
877
  }
890
878
  /** Project config for web components and tools. */
891
- type WeslProject = Pick<LinkParams, "weslSrc" | "rootModuleName" | "conditions" | "constants" | "libs" | "packageName">;
892
- /** Generate a virtual WESL module based on a set of conditions. */
893
- type VirtualLibraryFn = (conditions: Conditions) => string;
879
+ type WeslProject = Pick<LinkParams, "weslSrc" | "rootModuleName" | "conditions" | "constants" | "libs" | "packageName"> & {
880
+ /** Shader directory relative to project root (set by ?link from wesl.toml). */shaderRoot?: string;
881
+ };
882
+ /** Context passed to virtual library generators. */
883
+ interface VirtualLibContext {
884
+ conditions: Conditions;
885
+ rootModulePath: string;
886
+ packageName: string;
887
+ }
888
+ /** Generate a virtual WESL module. */
889
+ type VirtualLibraryFn = (ctx: VirtualLibContext) => string;
894
890
  /**
895
891
  * Link a set of WESL source modules (typically the text from .wesl files) into a single WGSL string.
896
892
  * Linking starts with a specified 'root' source module, and recursively incorporates code
@@ -927,23 +923,12 @@ declare function normalizeModuleName(name: string): string;
927
923
  //#endregion
928
924
  //#region src/BindIdents.d.ts
929
925
  /**
930
- * BindIdents pass: link reference identifiers to declarations.
931
- *
932
- * Goals:
933
- * - Link reference idents to declaration idents
934
- * - Collect used declarations (to emit in link)
935
- * - Create mangled names for globals to avoid conflicts
936
- *
937
- * Algorithm:
938
- * - Recursive depth-first walk of scope tree (not syntax tree)
939
- * - For each ref: search current scope, then up to parent scopes
940
- * - If no local match: check import statements for external matches
941
- * - For found global decls: mangle name to be unique, collect for emission
926
+ * BindIdents pass: depth-first walk of the scope tree (not syntax tree),
927
+ * linking ref idents to declarations and mangling global names.
942
928
  *
943
- * LiveDecls: tracks visible declarations at the current position, with parent links.
944
- *
945
- * @if/@else: respects conditional compilation by tracking @else validity state,
946
- * mirroring the emission phase's filterValidElements but on scopes.
929
+ * For each ref: search current scope upward, then check imports for external matches.
930
+ * LiveDecls tracks visible declarations with parent links.
931
+ * @if/@else: mirrors filterValidElements but on scopes.
947
932
  */
948
933
  /** Results returned from binding pass. */
949
934
  interface BindResults {
@@ -1014,6 +999,8 @@ interface BindContext {
1014
999
  virtuals?: VirtualLibrarySet;
1015
1000
  /** Host package name for resolving package:: in virtual modules. */
1016
1001
  packageName: string;
1002
+ /** Root module path (e.g., "package::main"). */
1003
+ rootModulePath: string;
1017
1004
  /** Unbound identifiers if accumulateUnbound is true. */
1018
1005
  unbound?: UnboundRef[];
1019
1006
  /** Don't follow references from declarations (for library dependency detection). */
@@ -1021,11 +1008,7 @@ interface BindContext {
1021
1008
  /** Visit all conditional branches (for dependency discovery). */
1022
1009
  discoveryMode?: boolean;
1023
1010
  }
1024
- /**
1025
- * Recursively bind references to declarations in this scope and child scopes.
1026
- * Tracks @else state to ensure filtered @else blocks don't pull in unused declarations.
1027
- * @return new declarations found
1028
- */
1011
+ /** Recursively bind refs to decls in this scope and children. @return new declarations found */
1029
1012
  declare function bindIdentsRecursive(scope: Scope, bindContext: BindContext, liveDecls: LiveDecls): DeclIdent[];
1030
1013
  //#endregion
1031
1014
  //#region src/Conditions.d.ts
@@ -1284,4 +1267,4 @@ declare function offsetToLineNumber(offset: number, text: string): [lineNum: num
1284
1267
  */
1285
1268
  declare function errorHighlight(source: string, span: Span): [string, string];
1286
1269
  //#endregion
1287
- export { AbstractElem, AbstractElemBase, AliasElem, Attribute, AttributeElem, BatchModuleResolver, BinaryExpression, BinaryOperator, BindIdentsParams, BindResults, BindingAST, BindingStructElem, BlockStatement, BoundAndTransformed, BuiltinAttribute, BundleResolver, ComponentExpression, ComponentMemberExpression, CompositeResolver, ConditionalAttribute, Conditions, ConstAssertElem, ConstElem, ContainerElem, ContinuingElem, DeclIdent, DeclIdentElem, DeclarationElem, DiagnosticAttribute, DiagnosticDirective, DiagnosticRule, DirectiveElem, DirectiveVariant, ElemKindMap, ElemWithAttributes, ElemWithContentsBase, ElifAttribute, ElseAttribute, EmittableElem, EnableDirective, ExpressionElem, ExtendedGPUValidationError, FnElem, FnParamElem, FunctionCallExpression, GlobalDeclarationElem, GlobalVarElem, GrammarElem, HasAttributes, Ident, IfAttribute, ImportCollection, ImportElem, ImportItem, ImportSegment, ImportStatement, InterpolateAttribute, LetElem, LexicalScope, LinkConfig, LinkParams, LinkRegistryParams, LinkedWesl, LinkerTransform, Literal, LiveDecls, ManglerFn, ModuleElem, ModuleResolver, NameElem, OpenElem, OverrideElem, ParenthesizedExpression, ParseError, type ParseOptions, PartialScope, RecordResolver, RecordResolverOptions, RefIdent, RefIdentElem, RequiresDirective, Scope, ScopeItem, SimpleMemberRef, Span, SrcMap, SrcMapBuilder, SrcMapEntry, SrcModule, SrcPosition, SrcWithPath, StableState, StandardAttribute, StatementElem, StructElem, StructMemberElem, StuffElem, SwitchClauseElem, SyntheticElem, TerminalElem, TextElem, TrackingResolver, TransformedAST, TranslateTimeExpressionElem, TypeRefElem, TypeTemplateParameter, TypedDeclElem, UnaryExpression, UnaryOperator, UnboundRef, UnknownExpressionElem, VarElem, VirtualLibrary, VirtualLibraryFn, VirtualLibrarySet, WeslAST, WeslBundle, WeslDevice, WeslGPUCompilationInfo, WeslGPUCompilationMessage, WeslJsPlugin, WeslParseContext, WeslParseError, WeslParseState, WeslProject, WeslStream, _linkSync, astToString, attributeToString, bindAndTransform, bindIdents, bindIdentsRecursive, bindingStructsPlugin, childIdent, childScope, containsScope, debug, debugContentsToString, discoverModules, emptyScope, errorHighlight, fileToModulePath, filterMap, filterValidElements, findAllRootDecls, findMap, findRefsToBindingStructs, findUnboundIdents, findUnboundRefs, findValidRootDecls, flatImports, freshResolver, groupBy, grouped, identToString, last, lengthPrefixMangle, link, linkRegistry, liveDeclsToString, log, lowerBindingStructs, makeLiveDecls, makeWeslDevice, mapForward, mapValues, markBindingStructs, markEntryTypes, mergeScope, minimalMangle, minimallyMangledName, modulePartsToRelativePath, moduleToRelativePath, multiKeySet, multisampledTextureTypes, nextIdentId, noSuffix, normalize, normalizeDebugRoot, normalizeModuleName, npmNameVariations, offsetToLineNumber, overlapTail, parseSrcModule, partition, publicDecl, replaceWords, requestWeslDevice, resetScopeIds, resolveModulePath, sampledTextureTypes, sanitizePackageName, scan, scopeToString, scopeToStringLong, srcLog, stdEnumerant, stdEnumerants, stdFn, stdFns, stdType, stdTypes, textureStorageTypes, transformBindingReference, transformBindingStruct, underscoreMangle, validation, wgslStandardAttributes, withLoggerAsync };
1270
+ export { AbstractElem, AbstractElemBase, AliasElem, Attribute, AttributeElem, BatchModuleResolver, BinaryExpression, BinaryOperator, BindIdentsParams, BindResults, BindingAST, BindingStructElem, BlockStatement, BoundAndTransformed, BuiltinAttribute, BundleResolver, ComponentExpression, ComponentMemberExpression, CompositeResolver, ConditionalAttribute, Conditions, ConstAssertElem, ConstElem, ContainerElem, ContinuingElem, DeclIdent, DeclIdentElem, DeclarationElem, DiagnosticAttribute, DiagnosticDirective, DiagnosticRule, DirectiveElem, DirectiveVariant, ElemKindMap, ElemWithAttributes, ElemWithContentsBase, ElifAttribute, ElseAttribute, EmittableElem, EnableDirective, ExpressionElem, ExtendedGPUValidationError, FnElem, FnParamElem, FunctionCallExpression, GlobalDeclarationElem, GlobalVarElem, GrammarElem, HasAttributes, Ident, IfAttribute, ImportCollection, ImportElem, ImportItem, ImportSegment, ImportStatement, InterpolateAttribute, LetElem, LexicalScope, LinkConfig, LinkParams, LinkRegistryParams, LinkedWesl, LinkerTransform, Literal, LiveDecls, ManglerFn, ModuleElem, ModuleResolver, NameElem, OpenElem, OverrideElem, ParenthesizedExpression, ParseError, type ParseOptions, PartialScope, RecordResolver, RecordResolverOptions, RefIdent, RefIdentElem, RequiresDirective, Scope, ScopeItem, SimpleMemberRef, Span, SrcMap, SrcMapBuilder, SrcMapEntry, SrcModule, SrcPosition, SrcWithPath, StableState, StandardAttribute, StatementElem, StructElem, StructMemberElem, StuffElem, SwitchClauseElem, SyntheticElem, TerminalElem, TextElem, TrackingResolver, TransformedAST, TranslateTimeExpressionElem, TypeRefElem, TypeTemplateParameter, TypedDeclElem, UnaryExpression, UnaryOperator, UnboundRef, UnknownExpressionElem, VarElem, VirtualLibContext, VirtualLibrary, VirtualLibraryFn, VirtualLibrarySet, WeslAST, WeslBundle, WeslDevice, WeslGPUCompilationInfo, WeslGPUCompilationMessage, WeslJsPlugin, WeslParseContext, WeslParseError, WeslParseState, WeslProject, WeslStream, _linkSync, astToString, attributeToString, bindAndTransform, bindIdents, bindIdentsRecursive, bindingStructsPlugin, childIdent, childScope, containsScope, debug, debugContentsToString, discoverModules, emptyScope, errorHighlight, fileToModulePath, filterMap, filterValidElements, findAllRootDecls, findMap, findRefsToBindingStructs, findUnboundIdents, findUnboundRefs, findValidRootDecls, flatImports, freshResolver, groupBy, grouped, identToString, last, lengthPrefixMangle, link, linkRegistry, liveDeclsToString, log, lowerBindingStructs, makeLiveDecls, makeWeslDevice, mapForward, mapValues, markBindingStructs, markEntryTypes, mergeScope, minimalMangle, minimallyMangledName, modulePartsToRelativePath, moduleToRelativePath, multiKeySet, multisampledTextureTypes, nextIdentId, noSuffix, normalize, normalizeDebugRoot, normalizeModuleName, npmNameVariations, offsetToLineNumber, overlapTail, parseSrcModule, partition, publicDecl, replaceWords, requestWeslDevice, resetScopeIds, resolveModulePath, sampledTextureTypes, sanitizePackageName, scan, scopeToString, scopeToStringLong, srcLog, stdEnumerant, stdEnumerants, stdFn, stdFns, stdType, stdTypes, textureStorageTypes, transformBindingReference, transformBindingStruct, underscoreMangle, validation, wgslStandardAttributes, withLoggerAsync };
package/dist/index.js CHANGED
@@ -283,12 +283,13 @@ const isBrowser = "document" in globalThis;
283
283
  /** Throw an error with an embedded source map so that browser users can
284
284
  * click on the error in the browser debug console and see the wesl source code. */
285
285
  function throwClickableError(params) {
286
- const { url, text, lineNumber, lineColumn, length, error } = params;
286
+ const { url, text, lineNumber, lineColumn, length, offset, error } = params;
287
287
  const weslLocation = {
288
288
  file: url,
289
289
  line: lineNumber,
290
290
  column: lineColumn,
291
- length
291
+ length,
292
+ offset
292
293
  };
293
294
  error.weslLocation = weslLocation;
294
295
  if (!isBrowser) throw error;
@@ -349,6 +350,7 @@ function failIdentElem(identElem, msg = "") {
349
350
  lineNumber,
350
351
  lineColumn,
351
352
  length: end - start,
353
+ offset: start,
352
354
  error: new Error(detailedMessage)
353
355
  });
354
356
  }
@@ -656,7 +658,7 @@ function lowerAndEmitElem(e, ctx) {
656
658
  case "let":
657
659
  case "statement":
658
660
  case "continuing":
659
- emitStatement$1(e, ctx);
661
+ emitStatement(e, ctx);
660
662
  return;
661
663
  case "override":
662
664
  case "const":
@@ -694,7 +696,7 @@ function emitModule(e, ctx) {
694
696
  lowerAndEmitElem(child, ctx);
695
697
  }
696
698
  }
697
- function emitStatement$1(e, ctx) {
699
+ function emitStatement(e, ctx) {
698
700
  if (!(e.contents.length > 0 && e.contents[0].kind === "attribute")) emitAttributes(e.attributes, ctx);
699
701
  emitContents(e, ctx);
700
702
  }
@@ -1257,7 +1259,7 @@ function liveDeclsToString(liveDecls) {
1257
1259
  /**
1258
1260
  * Construct a globally unique name based on the declaration
1259
1261
  * module path separated by underscores.
1260
- * Corresponds to "Underscore-count mangling" from [NameMangling.md](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/NameMangling.md)
1262
+ * Corresponds to "Underscore-count mangling" from [NameMangling.md](https://github.com/webgpu-tools/wesl-spec/blob/main/NameMangling.md)
1261
1263
  */
1262
1264
  function underscoreMangle(decl, srcModule) {
1263
1265
  const { modulePath } = srcModule;
@@ -3633,9 +3635,8 @@ function flatImports(ast, conditions) {
3633
3635
  //#region src/BindIdents.ts
3634
3636
  /** Bind ref idents to declarations and mangle global declaration names. */
3635
3637
  function bindIdents(params) {
3636
- const { rootAst, resolver, virtuals, accumulateUnbound } = params;
3638
+ const { rootAst, resolver, virtuals, accumulateUnbound, discoveryMode } = params;
3637
3639
  const { conditions = {}, mangler = minimalMangle } = params;
3638
- const { discoveryMode } = params;
3639
3640
  const packageName = rootAst.srcModule.modulePath.split("::")[0];
3640
3641
  const rootDecls = discoveryMode ? findAllRootDecls(rootAst.rootScope) : findValidRootDecls(rootAst.rootScope, conditions);
3641
3642
  const { globalNames, knownDecls } = initRootDecls(rootDecls);
@@ -3646,6 +3647,7 @@ function bindIdents(params) {
3646
3647
  virtuals,
3647
3648
  mangler,
3648
3649
  packageName,
3650
+ rootModulePath: rootAst.srcModule.modulePath,
3649
3651
  foundScopes: /* @__PURE__ */ new Set(),
3650
3652
  globalNames,
3651
3653
  globalStatements: /* @__PURE__ */ new Map(),
@@ -3656,8 +3658,9 @@ function bindIdents(params) {
3656
3658
  decls: new Map(rootDecls.map((d) => [d.originalName, d])),
3657
3659
  parent: null
3658
3660
  };
3659
- const fromRootDecls = rootDecls.flatMap((decl) => processDependentScope(decl, bindContext));
3660
- const fromRefs = bindIdentsRecursive(rootAst.rootScope, bindContext, liveDecls);
3661
+ const fromRootDecls = rootDecls.flatMap((d) => processDependentScope(d, bindContext));
3662
+ const { rootScope } = rootAst;
3663
+ const fromRefs = bindIdentsRecursive(rootScope, bindContext, liveDecls);
3661
3664
  const newStatements = [...bindContext.globalStatements.values()];
3662
3665
  return {
3663
3666
  decls: [...fromRootDecls, ...fromRefs],
@@ -3702,11 +3705,7 @@ function findAllRootDecls(rootScope) {
3702
3705
  function publicDecl(scope, name, conditions) {
3703
3706
  return getValidRootDecls(scope, conditions).find((d) => d.originalName === name);
3704
3707
  }
3705
- /**
3706
- * Recursively bind references to declarations in this scope and child scopes.
3707
- * Tracks @else state to ensure filtered @else blocks don't pull in unused declarations.
3708
- * @return new declarations found
3709
- */
3708
+ /** Recursively bind refs to decls in this scope and children. @return new declarations found */
3710
3709
  function bindIdentsRecursive(scope, bindContext, liveDecls) {
3711
3710
  const { dontFollowDecls, foundScopes } = bindContext;
3712
3711
  if (foundScopes.has(scope)) return [];
@@ -3744,11 +3743,7 @@ function processDependentScope(decl, ctx) {
3744
3743
  if (!rootDecls) return [];
3745
3744
  return bindIdentsRecursive(dependentScope, ctx, makeLiveDecls(rootDecls));
3746
3745
  }
3747
- /**
3748
- * Trace references to their declarations.
3749
- * Mutates to mangle declarations and mark std references.
3750
- * @return found declaration, or undefined if already processed
3751
- */
3746
+ /** Resolve a ref to its declaration, mangling globals and marking std refs. */
3752
3747
  function handleRef(ident, liveDecls, bindContext) {
3753
3748
  if (ident.refersTo || ident.std) return;
3754
3749
  if (ident.conditionRef) return;
@@ -3828,7 +3823,8 @@ function findExport(pathParts, srcModule, ctx) {
3828
3823
  const modulePath = resolveModulePath(pathParts, srcModule.modulePath.split("::")).slice(0, -1).join("::");
3829
3824
  const moduleAst = ctx.resolver.resolveModule(modulePath) ?? virtualModule(pathParts[0], ctx);
3830
3825
  if (!moduleAst) return void 0;
3831
- const decl = publicDecl(moduleAst.rootScope, last(pathParts), ctx.conditions);
3826
+ const name = last(pathParts);
3827
+ const decl = publicDecl(moduleAst.rootScope, name, ctx.conditions);
3832
3828
  if (decl) return {
3833
3829
  decl,
3834
3830
  moduleAst
@@ -3839,9 +3835,14 @@ function virtualModule(moduleName, ctx) {
3839
3835
  const found = ctx.virtuals?.[moduleName];
3840
3836
  if (!found) return void 0;
3841
3837
  if (found.ast) return found.ast;
3842
- const src = found.fn(ctx.conditions);
3838
+ const { conditions, rootModulePath, packageName } = ctx;
3839
+ const src = found.fn({
3840
+ conditions,
3841
+ rootModulePath,
3842
+ packageName
3843
+ });
3843
3844
  found.ast = parseSrcModule({
3844
- modulePath: ctx.packageName + "::" + moduleName,
3845
+ modulePath: packageName + "::" + moduleName,
3845
3846
  debugFilePath: moduleName,
3846
3847
  src
3847
3848
  });
@@ -3861,8 +3862,8 @@ function rootLiveDecls(decl, conditions) {
3861
3862
  assertThatDebug(scope.kind === "scope");
3862
3863
  const root = scope;
3863
3864
  if (!root._scopeDecls) {
3864
- const rootDecls = findValidRootDecls(scope, conditions);
3865
- root._scopeDecls = { decls: new Map(rootDecls.map((d) => [d.originalName, d])) };
3865
+ const decls = findValidRootDecls(scope, conditions);
3866
+ root._scopeDecls = { decls: new Map(decls.map((d) => [d.originalName, d])) };
3866
3867
  }
3867
3868
  return root._scopeDecls;
3868
3869
  }
@@ -4083,6 +4084,7 @@ function findUnboundRefs(resolver) {
4083
4084
  globalStatements: /* @__PURE__ */ new Map(),
4084
4085
  mangler: minimalMangle,
4085
4086
  packageName: "package",
4087
+ rootModulePath: "package::main",
4086
4088
  unbound: [],
4087
4089
  dontFollowDecls: true,
4088
4090
  discoveryMode: true
@@ -4468,20 +4470,13 @@ async function link(params) {
4468
4470
  }
4469
4471
  /** linker api for benchmarking */
4470
4472
  function _linkSync(params) {
4471
- const { weslSrc, libs = [], packageName, debugWeslRoot } = params;
4472
- const { resolver } = params;
4473
- const resolvers = [];
4474
- if (resolver) resolvers.push(resolver);
4475
- else if (weslSrc) resolvers.push(new RecordResolver(weslSrc, {
4473
+ const { weslSrc, libs = [], packageName, debugWeslRoot, resolver } = params;
4474
+ if (!resolver && !weslSrc) throw new Error("Either resolver or weslSrc must be provided");
4475
+ const allResolvers = [resolver ?? new RecordResolver(weslSrc, {
4476
4476
  packageName,
4477
4477
  debugWeslRoot
4478
- }));
4479
- else throw new Error("Either resolver or weslSrc must be provided");
4480
- if (libs.length > 0) {
4481
- const libResolvers = createLibraryResolvers(libs, debugWeslRoot);
4482
- resolvers.push(...libResolvers);
4483
- }
4484
- const finalResolver = resolvers.length === 1 ? resolvers[0] : new CompositeResolver(resolvers);
4478
+ }), ...createLibraryResolvers(libs, debugWeslRoot)];
4479
+ const finalResolver = allResolvers.length === 1 ? allResolvers[0] : new CompositeResolver(allResolvers);
4485
4480
  return linkRegistry({
4486
4481
  ...params,
4487
4482
  resolver: finalResolver
@@ -4515,8 +4510,9 @@ function flattenLibraryTree(libs) {
4515
4510
  * that share some sources.)
4516
4511
  */
4517
4512
  function linkRegistry(params) {
4518
- const { transformedAst, newDecls, newStatements } = bindAndTransform(params);
4519
- return SrcMapBuilder.build(emitWgsl(transformedAst.moduleElem, transformedAst.srcModule, newDecls, newStatements, params.conditions));
4513
+ const { transformedAst: ast, newDecls, newStatements } = bindAndTransform(params);
4514
+ const builders = emitWgsl(ast.moduleElem, ast.srcModule, newDecls, newStatements, params.conditions);
4515
+ return SrcMapBuilder.build(builders);
4520
4516
  }
4521
4517
  /** Bind identifiers and apply transform plugins */
4522
4518
  function bindAndTransform(params) {
@@ -4564,6 +4560,7 @@ function setupVirtualLibs(virtualLibs, constants) {
4564
4560
  }
4565
4561
  return libs && mapValues(libs, (fn) => ({ fn }));
4566
4562
  }
4563
+ /** Run registered transform plugins over the bound AST. */
4567
4564
  function applyTransformPlugins(rootModule, globalNames, config) {
4568
4565
  const { moduleElem, srcModule } = rootModule;
4569
4566
  const startAst = {
@@ -4574,54 +4571,40 @@ function applyTransformPlugins(rootModule, globalNames, config) {
4574
4571
  };
4575
4572
  return filterMap(config?.plugins ?? [], (plugin) => plugin.transform).reduce((ast, transform) => transform(ast), startAst);
4576
4573
  }
4574
+ /** Assemble WGSL output from prologue statements, root module, and imported declarations. */
4577
4575
  function emitWgsl(rootModuleElem, srcModule, newDecls, newStatements, conditions = {}) {
4578
- const prologueBuilders = newStatements.map((s) => emitStatement(s, conditions));
4579
- const rootBuilder = new SrcMapBuilder({
4580
- text: srcModule.src,
4581
- path: srcModule.debugFilePath
4582
- });
4576
+ const prologueBuilders = newStatements.map((s) => emitElem(s.srcModule, s.elem, conditions, { addNl: true }));
4577
+ const rootBuilder = builderFromModule(srcModule);
4583
4578
  lowerAndEmit({
4584
4579
  srcBuilder: rootBuilder,
4585
4580
  rootElems: [rootModuleElem],
4586
4581
  conditions,
4587
4582
  extracting: false
4588
4583
  });
4589
- const declBuilders = newDecls.map((decl) => emitDecl(decl, conditions));
4584
+ const declBuilders = newDecls.map((decl) => emitElem(decl.srcModule, decl.declElem, conditions, { skipConditionalFiltering: true }));
4590
4585
  return [
4591
4586
  ...prologueBuilders,
4592
4587
  rootBuilder,
4593
4588
  ...declBuilders
4594
4589
  ];
4595
4590
  }
4596
- function emitStatement(s, conditions) {
4597
- const { elem, srcModule } = s;
4598
- const { src: text, debugFilePath: path } = srcModule;
4599
- const builder = new SrcMapBuilder({
4600
- text,
4601
- path
4602
- });
4591
+ /** Emit a single element (prologue statement or imported declaration) into a SrcMapBuilder. */
4592
+ function emitElem(srcModule, elem, conditions, opts = {}) {
4593
+ const builder = builderFromModule(srcModule);
4603
4594
  lowerAndEmit({
4604
4595
  srcBuilder: builder,
4605
4596
  rootElems: [elem],
4606
- conditions
4597
+ conditions,
4598
+ skipConditionalFiltering: opts.skipConditionalFiltering
4607
4599
  });
4608
- builder.addNl();
4600
+ if (opts.addNl) builder.addNl();
4609
4601
  return builder;
4610
4602
  }
4611
- /** Skip conditional filtering because findValidRootDecls already validated these declarations */
4612
- function emitDecl(decl, conditions) {
4613
- const { src: text, debugFilePath: path } = decl.srcModule;
4614
- const builder = new SrcMapBuilder({
4615
- text,
4616
- path
4617
- });
4618
- lowerAndEmit({
4619
- srcBuilder: builder,
4620
- rootElems: [decl.declElem],
4621
- conditions,
4622
- skipConditionalFiltering: true
4603
+ function builderFromModule(srcModule) {
4604
+ return new SrcMapBuilder({
4605
+ text: srcModule.src,
4606
+ path: srcModule.debugFilePath
4623
4607
  });
4624
- return builder;
4625
4608
  }
4626
4609
  //#endregion
4627
4610
  //#region src/LinkerUtil.ts
@@ -4922,6 +4905,7 @@ function makeWeslDevice(device) {
4922
4905
  lineNumber: message.lineNum,
4923
4906
  lineColumn: message.linePos,
4924
4907
  length: message.length,
4908
+ offset: message.offset,
4925
4909
  error: /* @__PURE__ */ new Error(message.type + ": " + message.message)
4926
4910
  });
4927
4911
  else console.error(ev.error.message);
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "wesl",
3
- "version": "0.7.25",
3
+ "version": "0.7.27",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
7
7
  "src"
8
8
  ],
9
- "repository": "github:wgsl-tooling-wg/wesl-js",
9
+ "repository": "github:webgpu-tools/wesl-js",
10
10
  "exports": {
11
11
  ".": {
12
12
  "types": "./dist/index.d.ts",
@@ -52,7 +52,7 @@ export type ElemKindMap = {
52
52
  var: VarElem;
53
53
  };
54
54
 
55
- /** Inspired by https://github.com/wgsl-tooling-wg/wesl-rs/blob/3b2434eac1b2ebda9eb8bfb25f43d8600d819872/crates/wgsl-parse/src/syntax.rs#L364 */
55
+ /** Inspired by https://github.com/webgpu-tools/wesl-rs/blob/3b2434eac1b2ebda9eb8bfb25f43d8600d819872/crates/wgsl-parse/src/syntax.rs#L364 */
56
56
  export type ExpressionElem =
57
57
  | Literal
58
58
  | RefIdentElem
package/src/BindIdents.ts CHANGED
@@ -28,23 +28,12 @@ import {
28
28
  import { last } from "./Util.ts";
29
29
 
30
30
  /**
31
- * BindIdents pass: link reference identifiers to declarations.
31
+ * BindIdents pass: depth-first walk of the scope tree (not syntax tree),
32
+ * linking ref idents to declarations and mangling global names.
32
33
  *
33
- * Goals:
34
- * - Link reference idents to declaration idents
35
- * - Collect used declarations (to emit in link)
36
- * - Create mangled names for globals to avoid conflicts
37
- *
38
- * Algorithm:
39
- * - Recursive depth-first walk of scope tree (not syntax tree)
40
- * - For each ref: search current scope, then up to parent scopes
41
- * - If no local match: check import statements for external matches
42
- * - For found global decls: mangle name to be unique, collect for emission
43
- *
44
- * LiveDecls: tracks visible declarations at the current position, with parent links.
45
- *
46
- * @if/@else: respects conditional compilation by tracking @else validity state,
47
- * mirroring the emission phase's filterValidElements but on scopes.
34
+ * For each ref: search current scope upward, then check imports for external matches.
35
+ * LiveDecls tracks visible declarations with parent links.
36
+ * @if/@else: mirrors filterValidElements but on scopes.
48
37
  */
49
38
 
50
39
  /** Results returned from binding pass. */
@@ -107,9 +96,9 @@ export interface BindIdentsParams
107
96
 
108
97
  /** Bind ref idents to declarations and mangle global declaration names. */
109
98
  export function bindIdents(params: BindIdentsParams): BindResults {
110
- const { rootAst, resolver, virtuals, accumulateUnbound } = params;
99
+ const { rootAst, resolver, virtuals, accumulateUnbound, discoveryMode } =
100
+ params;
111
101
  const { conditions = {}, mangler = minimalMangle } = params;
112
- const { discoveryMode } = params;
113
102
  const packageName = rootAst.srcModule.modulePath.split("::")[0];
114
103
 
115
104
  const rootDecls = discoveryMode
@@ -117,6 +106,7 @@ export function bindIdents(params: BindIdentsParams): BindResults {
117
106
  : findValidRootDecls(rootAst.rootScope, conditions);
118
107
  const { globalNames, knownDecls } = initRootDecls(rootDecls);
119
108
 
109
+ const rootModulePath = rootAst.srcModule.modulePath;
120
110
  const bindContext = {
121
111
  resolver,
122
112
  conditions,
@@ -124,6 +114,7 @@ export function bindIdents(params: BindIdentsParams): BindResults {
124
114
  virtuals,
125
115
  mangler,
126
116
  packageName,
117
+ rootModulePath,
127
118
  foundScopes: new Set<Scope>(),
128
119
  globalNames,
129
120
  globalStatements: new Map<AbstractElem, EmittableElem>(),
@@ -134,15 +125,11 @@ export function bindIdents(params: BindIdentsParams): BindResults {
134
125
  const decls = new Map(rootDecls.map(d => [d.originalName, d] as const));
135
126
  const liveDecls: LiveDecls = { decls, parent: null };
136
127
 
137
- const fromRootDecls = rootDecls.flatMap(decl =>
138
- processDependentScope(decl, bindContext),
139
- );
140
-
141
- const fromRefs = bindIdentsRecursive(
142
- rootAst.rootScope,
143
- bindContext,
144
- liveDecls,
128
+ const fromRootDecls = rootDecls.flatMap(d =>
129
+ processDependentScope(d, bindContext),
145
130
  );
131
+ const { rootScope } = rootAst;
132
+ const fromRefs = bindIdentsRecursive(rootScope, bindContext, liveDecls);
146
133
  const newStatements = [...bindContext.globalStatements.values()];
147
134
  return {
148
135
  decls: [...fromRootDecls, ...fromRefs],
@@ -233,6 +220,9 @@ interface BindContext {
233
220
  /** Host package name for resolving package:: in virtual modules. */
234
221
  packageName: string;
235
222
 
223
+ /** Root module path (e.g., "package::main"). */
224
+ rootModulePath: string;
225
+
236
226
  /** Unbound identifiers if accumulateUnbound is true. */
237
227
  unbound?: UnboundRef[];
238
228
 
@@ -243,11 +233,7 @@ interface BindContext {
243
233
  discoveryMode?: boolean;
244
234
  }
245
235
 
246
- /**
247
- * Recursively bind references to declarations in this scope and child scopes.
248
- * Tracks @else state to ensure filtered @else blocks don't pull in unused declarations.
249
- * @return new declarations found
250
- */
236
+ /** Recursively bind refs to decls in this scope and children. @return new declarations found */
251
237
  export function bindIdentsRecursive(
252
238
  scope: Scope,
253
239
  bindContext: BindContext,
@@ -257,8 +243,11 @@ export function bindIdentsRecursive(
257
243
  if (foundScopes.has(scope)) return [];
258
244
  foundScopes.add(scope);
259
245
 
260
- const result = processScope(scope, bindContext, liveDecls);
261
- const { newGlobals, newFromChildren } = result;
246
+ const { newGlobals, newFromChildren } = processScope(
247
+ scope,
248
+ bindContext,
249
+ liveDecls,
250
+ );
262
251
  const newFromRefs = dontFollowDecls
263
252
  ? []
264
253
  : handleDecls(newGlobals, bindContext);
@@ -301,11 +290,7 @@ function processDependentScope(decl: DeclIdent, ctx: BindContext): DeclIdent[] {
301
290
  return bindIdentsRecursive(dependentScope, ctx, makeLiveDecls(rootDecls));
302
291
  }
303
292
 
304
- /**
305
- * Trace references to their declarations.
306
- * Mutates to mangle declarations and mark std references.
307
- * @return found declaration, or undefined if already processed
308
- */
293
+ /** Resolve a ref to its declaration, mangling globals and marking std refs. */
309
294
  function handleRef(
310
295
  ident: RefIdent,
311
296
  liveDecls: LiveDecls,
@@ -443,11 +428,8 @@ function findExport(
443
428
  ctx.resolver.resolveModule(modulePath) ?? virtualModule(pathParts[0], ctx);
444
429
  if (!moduleAst) return undefined;
445
430
 
446
- const decl = publicDecl(
447
- moduleAst.rootScope,
448
- last(pathParts)!,
449
- ctx.conditions,
450
- );
431
+ const name = last(pathParts)!;
432
+ const decl = publicDecl(moduleAst.rootScope, name, ctx.conditions);
451
433
  if (decl) return { decl, moduleAst };
452
434
  }
453
435
 
@@ -460,10 +442,10 @@ function virtualModule(
460
442
  if (!found) return undefined;
461
443
  if (found.ast) return found.ast;
462
444
 
463
- const src = found.fn(ctx.conditions);
464
- const modulePath = ctx.packageName + "::" + moduleName;
465
- const debugFilePath = moduleName;
466
- found.ast = parseSrcModule({ modulePath, debugFilePath, src });
445
+ const { conditions, rootModulePath, packageName } = ctx;
446
+ const src = found.fn({ conditions, rootModulePath, packageName });
447
+ const modulePath = packageName + "::" + moduleName;
448
+ found.ast = parseSrcModule({ modulePath, debugFilePath: moduleName, src });
467
449
  return found.ast;
468
450
  }
469
451
 
@@ -490,10 +472,8 @@ function rootLiveDecls(
490
472
 
491
473
  const root = scope as LexicalScope;
492
474
  if (!root._scopeDecls) {
493
- const rootDecls = findValidRootDecls(scope, conditions);
494
- root._scopeDecls = {
495
- decls: new Map(rootDecls.map(d => [d.originalName, d])),
496
- };
475
+ const decls = findValidRootDecls(scope, conditions);
476
+ root._scopeDecls = { decls: new Map(decls.map(d => [d.originalName, d])) };
497
477
  }
498
478
  return root._scopeDecls;
499
479
  }
@@ -9,6 +9,8 @@ export interface WeslErrorLocation {
9
9
  line: number;
10
10
  column: number;
11
11
  length: number;
12
+ /** byte offset into the source file */
13
+ offset: number;
12
14
  }
13
15
 
14
16
  export interface ClickableErrorParams {
@@ -27,6 +29,9 @@ export interface ClickableErrorParams {
27
29
  /** number of characters in the error section */
28
30
  length: number;
29
31
 
32
+ /** byte offset of the error section within the source text */
33
+ offset: number;
34
+
30
35
  /** the original error */
31
36
  error: Error;
32
37
  }
@@ -36,12 +41,13 @@ const isBrowser = "document" in globalThis;
36
41
  /** Throw an error with an embedded source map so that browser users can
37
42
  * click on the error in the browser debug console and see the wesl source code. */
38
43
  export function throwClickableError(params: ClickableErrorParams): void {
39
- const { url, text, lineNumber, lineColumn, length, error } = params;
44
+ const { url, text, lineNumber, lineColumn, length, offset, error } = params;
40
45
  const weslLocation: WeslErrorLocation = {
41
46
  file: url,
42
47
  line: lineNumber,
43
48
  column: lineColumn,
44
49
  length,
50
+ offset,
45
51
  };
46
52
  (error as any).weslLocation = weslLocation;
47
53
  if (!debug || !isBrowser) throw error;
@@ -142,6 +148,7 @@ export function failIdentElem(
142
148
  lineNumber,
143
149
  lineColumn,
144
150
  length,
151
+ offset: start,
145
152
  error: new Error(detailedMessage),
146
153
  });
147
154
  }
package/src/Linker.ts CHANGED
@@ -37,29 +37,17 @@ export interface LinkConfig {
37
37
  }
38
38
 
39
39
  export interface LinkParams {
40
- /** Module resolver for lazy loading (NEW API - preferred)
41
- * Replaces weslSrc for lazy module loading.
42
- * If provided, weslSrc will be ignored. */
40
+ /** Module resolver for lazy loading. If provided, weslSrc is ignored. */
43
41
  resolver?: ModuleResolver;
44
42
 
45
- /** record of file paths and wesl text for modules (LEGACY API - still supported).
46
- * key is module path or file path
47
- * `package::foo::bar`, or './foo/bar.wesl', or './foo/bar'
48
- * value is wesl src
49
- *
50
- *
51
- * Only accepts unix-style, relative filesystem paths that are valid WGSL identifiers
52
- * - Unix-style: Slashes as separators.
53
- * - Valid WGSL identifiers: No backslashes, no `..`, or other non-identifier symbols.
54
- * - Relative paths: They have to be relative to the wesl root.
55
- */
43
+ /** Record of module sources keyed by module path (`package::foo::bar`)
44
+ * or relative file path (`./foo/bar.wesl`). Paths must be unix-style,
45
+ * relative to the wesl root, and valid WGSL identifiers. */
56
46
  weslSrc?: Record<string, string>;
57
47
 
58
- /** name of root wesl module
59
- * for an app, the root module normally contains the '@compute', '@vertex' or '@fragment' entry points
60
- * for a library, the root module defines the public api fo the library
61
- * can be specified as file path (./main.wesl), a module path (package::main), or just a module name (main)
62
- */
48
+ /** Root module name: file path (`./main.wesl`), module path (`package::main`), or bare name (`main`).
49
+ * For apps, the root module contains entry points (`@compute`, `@vertex`, `@fragment`).
50
+ * For libraries, it defines the public API. */
63
51
  rootModuleName?: string;
64
52
 
65
53
  /** For debug logging. Will be prepended to file paths. */
@@ -100,10 +88,20 @@ export type WeslProject = Pick<
100
88
  | "constants"
101
89
  | "libs"
102
90
  | "packageName"
103
- >;
91
+ > & {
92
+ /** Shader directory relative to project root (set by ?link from wesl.toml). */
93
+ shaderRoot?: string;
94
+ };
95
+
96
+ /** Context passed to virtual library generators. */
97
+ export interface VirtualLibContext {
98
+ conditions: Conditions;
99
+ rootModulePath: string;
100
+ packageName: string;
101
+ }
104
102
 
105
- /** Generate a virtual WESL module based on a set of conditions. */
106
- export type VirtualLibraryFn = (conditions: Conditions) => string;
103
+ /** Generate a virtual WESL module. */
104
+ export type VirtualLibraryFn = (ctx: VirtualLibContext) => string;
107
105
 
108
106
  /**
109
107
  * Link a set of WESL source modules (typically the text from .wesl files) into a single WGSL string.
@@ -120,26 +118,20 @@ export async function link(params: LinkParams): Promise<LinkedWesl> {
120
118
 
121
119
  /** linker api for benchmarking */
122
120
  export function _linkSync(params: LinkParams): SrcMap {
123
- const { weslSrc, libs = [], packageName, debugWeslRoot } = params;
124
- const { resolver } = params;
125
-
126
- const resolvers: ModuleResolver[] = [];
121
+ const { weslSrc, libs = [], packageName, debugWeslRoot, resolver } = params;
127
122
 
128
- if (resolver) {
129
- resolvers.push(resolver);
130
- } else if (weslSrc) {
131
- resolvers.push(new RecordResolver(weslSrc, { packageName, debugWeslRoot }));
132
- } else {
123
+ if (!resolver && !weslSrc) {
133
124
  throw new Error("Either resolver or weslSrc must be provided");
134
125
  }
126
+ const primaryResolver =
127
+ resolver ?? new RecordResolver(weslSrc!, { packageName, debugWeslRoot });
135
128
 
136
- if (libs.length > 0) {
137
- const libResolvers = createLibraryResolvers(libs, debugWeslRoot);
138
- resolvers.push(...libResolvers);
139
- }
140
-
129
+ const libResolvers = createLibraryResolvers(libs, debugWeslRoot);
130
+ const allResolvers = [primaryResolver, ...libResolvers];
141
131
  const finalResolver =
142
- resolvers.length === 1 ? resolvers[0] : new CompositeResolver(resolvers);
132
+ allResolvers.length === 1
133
+ ? allResolvers[0]
134
+ : new CompositeResolver(allResolvers);
143
135
 
144
136
  return linkRegistry({ ...params, resolver: finalResolver });
145
137
  }
@@ -194,17 +186,15 @@ export interface LinkRegistryParams
194
186
  */
195
187
  export function linkRegistry(params: LinkRegistryParams): SrcMap {
196
188
  const bound = bindAndTransform(params);
197
- const { transformedAst, newDecls, newStatements } = bound;
198
-
199
- return SrcMapBuilder.build(
200
- emitWgsl(
201
- transformedAst.moduleElem,
202
- transformedAst.srcModule,
203
- newDecls,
204
- newStatements,
205
- params.conditions,
206
- ),
189
+ const { transformedAst: ast, newDecls, newStatements } = bound;
190
+ const builders = emitWgsl(
191
+ ast.moduleElem,
192
+ ast.srcModule,
193
+ newDecls,
194
+ newStatements,
195
+ params.conditions,
207
196
  );
197
+ return SrcMapBuilder.build(builders);
208
198
  }
209
199
 
210
200
  export interface BoundAndTransformed {
@@ -222,18 +212,16 @@ export function bindAndTransform(
222
212
 
223
213
  const modulePath = normalizeModuleName(rootModuleName);
224
214
  const rootAst = getRootModule(resolver, modulePath, rootModuleName);
225
-
226
215
  const virtuals = setupVirtualLibs(params.virtualLibs, constants);
227
216
 
228
- const bindParams = {
217
+ const bound = bindIdents({
229
218
  rootAst,
230
219
  resolver,
231
220
  conditions,
232
221
  virtuals,
233
222
  mangler,
234
- };
235
- const bindResults = bindIdents(bindParams);
236
- const { globalNames, decls: newDecls, newStatements } = bindResults;
223
+ });
224
+ const { globalNames, decls: newDecls, newStatements } = bound;
237
225
 
238
226
  const transformedAst = applyTransformPlugins(rootAst, globalNames, config);
239
227
  return { transformedAst, newDecls, newStatements };
@@ -275,7 +263,7 @@ function setupVirtualLibs(
275
263
  ): VirtualLibrarySet | undefined {
276
264
  let libs = virtualLibs;
277
265
  if (constants) {
278
- const constantsGen = () =>
266
+ const constantsGen: VirtualLibraryFn = () =>
279
267
  Object.entries(constants)
280
268
  .map(([name, value]) => `const ${name} = ${value};`)
281
269
  .join("\n");
@@ -284,25 +272,21 @@ function setupVirtualLibs(
284
272
  return libs && mapValues(libs, fn => ({ fn }));
285
273
  }
286
274
 
275
+ /** Run registered transform plugins over the bound AST. */
287
276
  function applyTransformPlugins(
288
277
  rootModule: WeslAST,
289
278
  globalNames: Set<string>,
290
279
  config?: LinkConfig,
291
280
  ): TransformedAST {
292
- const { moduleElem, srcModule } = rootModule;
293
-
294
281
  // for now only transform the root module
282
+ const { moduleElem, srcModule } = rootModule;
295
283
  const startAst = { moduleElem, srcModule, globalNames, notableElems: {} };
296
284
  const plugins = config?.plugins ?? [];
297
285
  const transforms = filterMap(plugins, plugin => plugin.transform);
298
- const transformedAst = transforms.reduce(
299
- (ast, transform) => transform(ast),
300
- startAst,
301
- );
302
-
303
- return transformedAst;
286
+ return transforms.reduce((ast, transform) => transform(ast), startAst);
304
287
  }
305
288
 
289
+ /** Assemble WGSL output from prologue statements, root module, and imported declarations. */
306
290
  function emitWgsl(
307
291
  rootModuleElem: ModuleElem,
308
292
  srcModule: SrcModule,
@@ -310,12 +294,11 @@ function emitWgsl(
310
294
  newStatements: EmittableElem[],
311
295
  conditions: Conditions = {},
312
296
  ): SrcMapBuilder[] {
313
- const prologueBuilders = newStatements.map(s => emitStatement(s, conditions));
297
+ const prologueBuilders = newStatements.map(s =>
298
+ emitElem(s.srcModule, s.elem, conditions, { addNl: true }),
299
+ );
314
300
 
315
- const rootBuilder = new SrcMapBuilder({
316
- text: srcModule.src,
317
- path: srcModule.debugFilePath,
318
- });
301
+ const rootBuilder = builderFromModule(srcModule);
319
302
  lowerAndEmit({
320
303
  srcBuilder: rootBuilder,
321
304
  rootElems: [rootModuleElem],
@@ -323,37 +306,40 @@ function emitWgsl(
323
306
  extracting: false,
324
307
  });
325
308
 
326
- const declBuilders = newDecls.map(decl => emitDecl(decl, conditions));
309
+ const declBuilders = newDecls.map(decl =>
310
+ emitElem(decl.srcModule, decl.declElem!, conditions, {
311
+ skipConditionalFiltering: true,
312
+ }),
313
+ );
327
314
 
328
315
  return [...prologueBuilders, rootBuilder, ...declBuilders];
329
316
  }
330
317
 
331
- function emitStatement(
332
- s: EmittableElem,
318
+ /** Emit a single element (prologue statement or imported declaration) into a SrcMapBuilder. */
319
+ function emitElem(
320
+ srcModule: SrcModule,
321
+ elem: AbstractElem,
333
322
  conditions: Conditions,
323
+ opts: { addNl?: boolean; skipConditionalFiltering?: boolean } = {},
334
324
  ): SrcMapBuilder {
335
- const { elem, srcModule } = s;
336
- const { src: text, debugFilePath: path } = srcModule;
337
- const builder = new SrcMapBuilder({ text, path });
338
- lowerAndEmit({ srcBuilder: builder, rootElems: [elem], conditions });
339
- builder.addNl();
340
- return builder;
341
- }
342
-
343
- /** Skip conditional filtering because findValidRootDecls already validated these declarations */
344
- function emitDecl(decl: DeclIdent, conditions: Conditions): SrcMapBuilder {
345
- const { src: text, debugFilePath: path } = decl.srcModule;
346
- const builder = new SrcMapBuilder({ text, path });
325
+ const builder = builderFromModule(srcModule);
347
326
  lowerAndEmit({
348
327
  srcBuilder: builder,
349
- rootElems: [decl.declElem!],
328
+ rootElems: [elem],
350
329
  conditions,
351
- skipConditionalFiltering: true,
330
+ skipConditionalFiltering: opts.skipConditionalFiltering,
352
331
  });
332
+ if (opts.addNl) builder.addNl();
353
333
  return builder;
354
334
  }
355
335
 
356
- /* ---- Commentary on present and future features ---- */
336
+ function builderFromModule(srcModule: SrcModule): SrcMapBuilder {
337
+ return new SrcMapBuilder({
338
+ text: srcModule.src,
339
+ path: srcModule.debugFilePath,
340
+ });
341
+ }
342
+
357
343
  /*
358
344
 
359
345
  LATER
package/src/Mangler.ts CHANGED
@@ -25,7 +25,7 @@ export type ManglerFn = (
25
25
  /**
26
26
  * Construct a globally unique name based on the declaration
27
27
  * module path separated by underscores.
28
- * Corresponds to "Underscore-count mangling" from [NameMangling.md](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/NameMangling.md)
28
+ * Corresponds to "Underscore-count mangling" from [NameMangling.md](https://github.com/webgpu-tools/wesl-spec/blob/main/NameMangling.md)
29
29
  */
30
30
  export function underscoreMangle(
31
31
  decl: DeclIdent,
@@ -1,5 +1,5 @@
1
1
  // From https://www.w3.org/TR/WGSL/#predeclared
2
- // Use https://github.com/wgsl-tooling-wg/wgsl-spec to regenerate these list in the future
2
+ // Use https://github.com/webgpu-tools/wgsl-spec to regenerate these list in the future
3
3
 
4
4
  export const stdFns = `bitcast all any select arrayLength
5
5
  abs acos acosh asin asinh atan atanh atan2 ceil clamp cos cosh
package/src/WeslDevice.ts CHANGED
@@ -89,6 +89,7 @@ export function makeWeslDevice(device: GPUDevice): WeslDevice {
89
89
  lineNumber: message.lineNum,
90
90
  lineColumn: message.linePos,
91
91
  length: message.length,
92
+ offset: message.offset,
92
93
  error: new Error(message.type + ": " + message.message),
93
94
  });
94
95
  }
@@ -42,6 +42,7 @@ export function findUnboundRefs(resolver: BatchModuleResolver): UnboundRef[] {
42
42
  globalStatements: new Map<AbstractElem, EmittableElem>(),
43
43
  mangler: minimalMangle,
44
44
  packageName: "package",
45
+ rootModulePath: "package::main",
45
46
  unbound: [] as UnboundRef[],
46
47
  dontFollowDecls: true,
47
48
  discoveryMode: true,
@@ -1,4 +1,4 @@
1
- // Use https://github.com/wgsl-tooling-wg/wgsl-spec to check this list in the future
1
+ // Use https://github.com/webgpu-tools/wgsl-spec to check this list in the future
2
2
  // I recommend checking whether a new list and the current list are equal
3
3
 
4
4
  /** https://www.w3.org/TR/WGSL/#keyword-summary */
@@ -39,7 +39,7 @@ interface CompoundOptions {
39
39
 
40
40
  // Experimental: declarations in conditional blocks visible in outer scope.
41
41
  // e.g. @if(X) { let y = 1; } makes y visible outside the block.
42
- // see https://github.com/wgsl-tooling-wg/wesl-spec/issues/158
42
+ // see https://github.com/webgpu-tools/wesl-spec/issues/158
43
43
  const conditionalBlockFeature = true;
44
44
 
45
45
  /** Function bodies share scope with parameters (per WGSL spec). */
@@ -11,7 +11,7 @@ const bevyBulkTest = {
11
11
  name: "Bevy",
12
12
  baseDir: "bevy-wgsl",
13
13
  git: {
14
- url: "https://github.com/wgsl-tooling-wg/bevy-wgsl.git",
14
+ url: "https://github.com/webgpu-tools/bevy-wgsl.git",
15
15
  revision: "84977ff025eaf8d92e56a9c35b815fae70eb4af0",
16
16
  },
17
17
  };