wesl 0.7.22 → 0.7.23

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/dist/index.d.ts CHANGED
@@ -171,6 +171,7 @@ interface SrcModule {
171
171
  }
172
172
  /** a src declaration or reference to an ident */
173
173
  type Ident = DeclIdent | RefIdent;
174
+ type ScopeItem = Ident | Scope;
174
175
  /** LATER change this to a Map, so that `toString` isn't accidentally a condition */
175
176
  type Conditions = Record<string, boolean>;
176
177
  interface IdentBase {
@@ -229,7 +230,7 @@ interface ScopeBase {
229
230
  id: number;
230
231
  /** null for root scope in a module */
231
232
  parent: Scope | null;
232
- contents: (Ident | Scope)[];
233
+ contents: ScopeItem[];
233
234
  /** Conditional attribute (@if or @else) for this scope */
234
235
  condAttribute?: IfAttribute | ElifAttribute | ElseAttribute;
235
236
  }
@@ -976,6 +977,8 @@ interface BindIdentsParams extends Pick<LinkRegistryParams, "resolver" | "condit
976
977
  virtuals?: VirtualLibrarySet;
977
978
  /** If true, accumulate unbound identifiers into BindResults.unbound instead of throwing. */
978
979
  accumulateUnbound?: true;
980
+ /** Visit all conditional branches (for dependency discovery). */
981
+ discoveryMode?: boolean;
979
982
  }
980
983
  /** Bind ref idents to declarations and mangle global declaration names. */
981
984
  declare function bindIdents(params: BindIdentsParams): BindResults;
@@ -1016,6 +1019,17 @@ interface BindContext {
1016
1019
  */
1017
1020
  declare function bindIdentsRecursive(scope: Scope, bindContext: BindContext, liveDecls: LiveDecls): DeclIdent[];
1018
1021
  //#endregion
1022
+ //#region src/Conditions.d.ts
1023
+ /**
1024
+ * Filter elements based on @if/@else conditional logic.
1025
+ * This function processes elements sequentially to handle @if/@else chains correctly.
1026
+ *
1027
+ * @param elements Array of elements at the same scope level
1028
+ * @param conditions Current conditional compilation settings
1029
+ * @return Array of valid elements after applying @if/@else logic
1030
+ */
1031
+ declare function filterValidElements<T extends AbstractElem>(elements: readonly T[], conditions: Conditions): T[];
1032
+ //#endregion
1019
1033
  //#region src/debug/ASTtoString.d.ts
1020
1034
  declare function astToString(elem: AbstractElem, indent?: number): string;
1021
1035
  /** @return string representation of an attribute (for test/debug) */
@@ -1043,6 +1057,23 @@ declare function identToString(ident?: Ident): string;
1043
1057
  declare function findUnboundIdents(resolver: BatchModuleResolver): string[][];
1044
1058
  /** Find unbound references with full position info. */
1045
1059
  declare function findUnboundRefs(resolver: BatchModuleResolver): UnboundRef[];
1060
+ /** Thin decorator that records which modules were resolved. */
1061
+ declare class TrackingResolver implements ModuleResolver {
1062
+ #private;
1063
+ readonly visited: Set<string>;
1064
+ constructor(inner: ModuleResolver);
1065
+ resolveModule(modulePath: string): WeslAST | undefined;
1066
+ }
1067
+ /**
1068
+ * Discover reachable modules and unbound external refs from a single root.
1069
+ *
1070
+ * Traces the import graph from `rootModuleName`, returning only the reachable
1071
+ * local modules in `weslSrc` and unresolved external references in `unbound`.
1072
+ */
1073
+ declare function discoverModules(weslSrc: Record<string, string>, resolver: ModuleResolver, rootModuleName: string, packageName?: string): {
1074
+ weslSrc: Record<string, string>;
1075
+ unbound: string[][];
1076
+ };
1046
1077
  //#endregion
1047
1078
  //#region src/discovery/PackageNameUtils.d.ts
1048
1079
  /** Package name sanitization for WESL.
@@ -1139,6 +1170,9 @@ declare const textureStorageTypes = "\n texture_storage_1d texture_storage_2d t
1139
1170
  declare const stdTypes: string[];
1140
1171
  /** https://www.w3.org/TR/WGSL/#predeclared-enumerants */
1141
1172
  declare const stdEnumerants: string[];
1173
+ /** WGSL standard attributes whose params need binding (e.g., @workgroup_size).
1174
+ * See: https://www.w3.org/TR/WGSL/#attributes */
1175
+ declare const wgslStandardAttributes: Set<string>;
1142
1176
  /** return true if the name is for a built in type (not a user struct) */
1143
1177
  declare function stdType(name: string): boolean;
1144
1178
  /** return true if the name is for a built in fn (not a user function) */
@@ -1241,4 +1275,4 @@ declare function offsetToLineNumber(offset: number, text: string): [lineNum: num
1241
1275
  */
1242
1276
  declare function errorHighlight(source: string, span: Span): [string, string];
1243
1277
  //#endregion
1244
- 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, SimpleMemberRef, Span, SrcMap, SrcMapBuilder, SrcMapEntry, SrcModule, SrcPosition, SrcWithPath, StableState, StandardAttribute, StatementElem, StructElem, StructMemberElem, StuffElem, SwitchClauseElem, SyntheticElem, TerminalElem, TextElem, TransformedAST, TranslateTimeExpressionElem, TypeRefElem, TypeTemplateParameter, TypedDeclElem, UnaryExpression, UnaryOperator, UnboundRef, UnknownExpressionElem, VarElem, VirtualLibrary, VirtualLibraryFn, VirtualLibrarySet, WeslAST, WeslBundle, WeslDevice, WeslGPUCompilationInfo, WeslGPUCompilationMessage, WeslJsPlugin, WeslParseContext, WeslParseError, WeslParseState, WeslStream, _linkSync, astToString, attributeToString, bindAndTransform, bindIdents, bindIdentsRecursive, bindingStructsPlugin, childIdent, childScope, containsScope, debug, debugContentsToString, emptyScope, errorHighlight, fileToModulePath, filterMap, findAllRootDecls, findMap, findRefsToBindingStructs, findUnboundIdents, findUnboundRefs, findValidRootDecls, flatImports, 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, withLoggerAsync };
1278
+ 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, 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, 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
@@ -504,11 +504,78 @@ function childIdent(child) {
504
504
  }
505
505
 
506
506
  //#endregion
507
- //#region src/LowerAndEmit.ts
508
- /** Valid WGSL standard attributes (from spec). Non-WGSL attributes are stripped.
509
- * See: https://www.w3.org/TR/WGSL/#attributes
510
- * Note: @builtin, @diagnostic, @interpolate are parsed as separate attribute types. */
511
- const wgslStandardAttributes$1 = new Set([
507
+ //#region src/StandardTypes.ts
508
+ const stdFns = `bitcast all any select arrayLength
509
+ abs acos acosh asin asinh atan atanh atan2 ceil clamp cos cosh
510
+ countLeadingZeros countOneBits countTrailingZeros cross
511
+ degrees determinant distance dot dot4U8Packed dot4I8Packed
512
+ exp exp2 extractBits faceForward firstLeadingBit firstTrailingBit
513
+ floor fma fract frexp insertBits inverseSqrt ldexp length log log2
514
+ max min mix modf normalize pow quantizeToF16 radians reflect refract
515
+ reverseBits round saturate sign sin sinh smoothstep sqrt step tan tanh
516
+ transpose trunc
517
+ dpdx dpdxCoarse dpdxFine dpdy dpdyCoarse dpdyFine fwidth
518
+ fwidthCoarse fwidthFine
519
+ textureDimensions textureGather textureGatherCompare textureLoad
520
+ textureNumLayers textureNumLevels textureNumSamples
521
+ textureSample textureSampleBias textureSampleCompare textureSampleCompareLevel
522
+ textureSampleGrad textureSampleLevel textureSampleBaseClampToEdge
523
+ textureStore
524
+ atomicLoad atomicStore atomicAdd atomicSub atomicMax atomicMin
525
+ atomicAnd atomicOr atomicXor atomicExchange atomicCompareExchangeWeak
526
+ pack4x8snorm pack4x8unorm pack4xI8 pack4xU8 pack4xI8Clamp pack4xU8Clamp
527
+ pack2x16snorm pack2x16unorm pack2x16float
528
+ unpack4x8snorm unpack4x8unorm unpack4xI8 unpack4xU8
529
+ unpack2x16snorm unpack2x16unorm unpack2x16float
530
+ storageBarrier textureBarrier workgroupBarrier workgroupUniformLoad
531
+ subgroupAdd subgroupAll subgroupAnd subgroupAny subgroupBallot
532
+ subgroupBroadcast subgroupBroadcastFirst subgroupElect
533
+ subgroupExclusiveAdd subgroupExclusiveMul subgroupInclusiveAdd
534
+ subgroupInclusiveMul subgroupMax subgroupMin subgroupMul subgroupOr
535
+ subgroupShuffle subgroupShuffleUp subgroupShuffleXor subgroupXor
536
+ quadBroadcast quadSwapDiagonal quadSwapX quadSwapY`.split(/\s+/);
537
+ const sampledTextureTypes = `
538
+ texture_1d texture_2d texture_2d_array texture_3d
539
+ texture_cube texture_cube_array
540
+ `;
541
+ const multisampledTextureTypes = `
542
+ texture_multisampled_2d texture_depth_multisampled_2d
543
+ `;
544
+ const textureStorageTypes = `
545
+ texture_storage_1d texture_storage_2d texture_storage_2d_array
546
+ texture_storage_3d
547
+ `;
548
+ const stdTypes = `array atomic bool f16 f32 i32
549
+ mat2x2 mat2x3 mat2x4 mat3x2 mat3x3 mat3x4 mat4x2 mat4x3 mat4x4
550
+ mat2x2f mat2x3f mat2x4f mat3x2f mat3x3f mat3x4f
551
+ mat4x2f mat4x3f mat4x4f
552
+ mat2x2h mat2x3h mat2x4h mat3x2h mat3x3h mat3x4h
553
+ mat4x2h mat4x3h mat4x4h
554
+ u32 vec2 vec3 vec4 ptr
555
+ vec2i vec3i vec4i vec2u vec3u vec4u
556
+ vec2f vec3f vec4f vec2h vec3h vec4h
557
+ ${sampledTextureTypes}
558
+ ${multisampledTextureTypes}
559
+ texture_external
560
+ ${textureStorageTypes}
561
+ texture_depth_2d texture_depth_2d_array texture_depth_cube
562
+ texture_depth_cube_array
563
+ sampler sampler_comparison
564
+ rgba8unorm rgba8snorm rgba8uint rgba8sint
565
+ rgba16uint rgba16sint rgba16float
566
+ r32uint r32sint r32float rg32uint rg32sint rg32float
567
+ rgba32uint rgba32sint rgba32float
568
+ bgra8unorm`.split(/\s+/);
569
+ /** https://www.w3.org/TR/WGSL/#predeclared-enumerants */
570
+ const stdEnumerants = `read write read_write
571
+ function private workgroup uniform storage
572
+ rgba8unorm rgba8snorm rgba8uint rgba8sint
573
+ rgba16uint rgba16sint rgba16float
574
+ r32uint r32sint r32float rg32uint rg32sint rg32float
575
+ rgba32uint rgba32sint rgba32float bgra8unorm`.split(/\s+/);
576
+ /** WGSL standard attributes whose params need binding (e.g., @workgroup_size).
577
+ * See: https://www.w3.org/TR/WGSL/#attributes */
578
+ const wgslStandardAttributes = new Set([
512
579
  "align",
513
580
  "binding",
514
581
  "blend_src",
@@ -524,6 +591,21 @@ const wgslStandardAttributes$1 = new Set([
524
591
  "vertex",
525
592
  "workgroup_size"
526
593
  ]);
594
+ /** return true if the name is for a built in type (not a user struct) */
595
+ function stdType(name) {
596
+ return stdTypes.includes(name);
597
+ }
598
+ /** return true if the name is for a built in fn (not a user function) */
599
+ function stdFn(name) {
600
+ return stdFns.includes(name) || stdType(name);
601
+ }
602
+ /** return true if the name is for a built in enumerant */
603
+ function stdEnumerant(name) {
604
+ return stdEnumerants.includes(name);
605
+ }
606
+
607
+ //#endregion
608
+ //#region src/LowerAndEmit.ts
527
609
  /** Traverse the AST, starting from root elements, emitting WGSL for each. */
528
610
  function lowerAndEmit(params) {
529
611
  const { srcBuilder, rootElems, conditions } = params;
@@ -803,7 +885,7 @@ function emitAttribute(e, ctx) {
803
885
  const { kind } = e.attribute;
804
886
  if (kind === "@if" || kind === "@elif" || kind === "@else") return false;
805
887
  if (kind === "@attribute") {
806
- if (!wgslStandardAttributes$1.has(e.attribute.name)) return false;
888
+ if (!wgslStandardAttributes.has(e.attribute.name)) return false;
807
889
  emitStandardAttribute(e, ctx);
808
890
  return true;
809
891
  }
@@ -3595,115 +3677,16 @@ function flatImports(ast, conditions) {
3595
3677
  return flat;
3596
3678
  }
3597
3679
 
3598
- //#endregion
3599
- //#region src/StandardTypes.ts
3600
- const stdFns = `bitcast all any select arrayLength
3601
- abs acos acosh asin asinh atan atanh atan2 ceil clamp cos cosh
3602
- countLeadingZeros countOneBits countTrailingZeros cross
3603
- degrees determinant distance dot dot4U8Packed dot4I8Packed
3604
- exp exp2 extractBits faceForward firstLeadingBit firstTrailingBit
3605
- floor fma fract frexp insertBits inverseSqrt ldexp length log log2
3606
- max min mix modf normalize pow quantizeToF16 radians reflect refract
3607
- reverseBits round saturate sign sin sinh smoothstep sqrt step tan tanh
3608
- transpose trunc
3609
- dpdx dpdxCoarse dpdxFine dpdy dpdyCoarse dpdyFine fwidth
3610
- fwidthCoarse fwidthFine
3611
- textureDimensions textureGather textureGatherCompare textureLoad
3612
- textureNumLayers textureNumLevels textureNumSamples
3613
- textureSample textureSampleBias textureSampleCompare textureSampleCompareLevel
3614
- textureSampleGrad textureSampleLevel textureSampleBaseClampToEdge
3615
- textureStore
3616
- atomicLoad atomicStore atomicAdd atomicSub atomicMax atomicMin
3617
- atomicAnd atomicOr atomicXor atomicExchange atomicCompareExchangeWeak
3618
- pack4x8snorm pack4x8unorm pack4xI8 pack4xU8 pack4xI8Clamp pack4xU8Clamp
3619
- pack2x16snorm pack2x16unorm pack2x16float
3620
- unpack4x8snorm unpack4x8unorm unpack4xI8 unpack4xU8
3621
- unpack2x16snorm unpack2x16unorm unpack2x16float
3622
- storageBarrier textureBarrier workgroupBarrier workgroupUniformLoad
3623
- subgroupAdd subgroupAll subgroupAnd subgroupAny subgroupBallot
3624
- subgroupBroadcast subgroupBroadcastFirst subgroupElect
3625
- subgroupExclusiveAdd subgroupExclusiveMul subgroupInclusiveAdd
3626
- subgroupInclusiveMul subgroupMax subgroupMin subgroupMul subgroupOr
3627
- subgroupShuffle subgroupShuffleUp subgroupShuffleXor subgroupXor
3628
- quadBroadcast quadSwapDiagonal quadSwapX quadSwapY`.split(/\s+/);
3629
- const sampledTextureTypes = `
3630
- texture_1d texture_2d texture_2d_array texture_3d
3631
- texture_cube texture_cube_array
3632
- `;
3633
- const multisampledTextureTypes = `
3634
- texture_multisampled_2d texture_depth_multisampled_2d
3635
- `;
3636
- const textureStorageTypes = `
3637
- texture_storage_1d texture_storage_2d texture_storage_2d_array
3638
- texture_storage_3d
3639
- `;
3640
- const stdTypes = `array atomic bool f16 f32 i32
3641
- mat2x2 mat2x3 mat2x4 mat3x2 mat3x3 mat3x4 mat4x2 mat4x3 mat4x4
3642
- mat2x2f mat2x3f mat2x4f mat3x2f mat3x3f mat3x4f
3643
- mat4x2f mat4x3f mat4x4f
3644
- mat2x2h mat2x3h mat2x4h mat3x2h mat3x3h mat3x4h
3645
- mat4x2h mat4x3h mat4x4h
3646
- u32 vec2 vec3 vec4 ptr
3647
- vec2i vec3i vec4i vec2u vec3u vec4u
3648
- vec2f vec3f vec4f vec2h vec3h vec4h
3649
- ${sampledTextureTypes}
3650
- ${multisampledTextureTypes}
3651
- texture_external
3652
- ${textureStorageTypes}
3653
- texture_depth_2d texture_depth_2d_array texture_depth_cube
3654
- texture_depth_cube_array
3655
- sampler sampler_comparison
3656
- rgba8unorm rgba8snorm rgba8uint rgba8sint
3657
- rgba16uint rgba16sint rgba16float
3658
- r32uint r32sint r32float rg32uint rg32sint rg32float
3659
- rgba32uint rgba32sint rgba32float
3660
- bgra8unorm`.split(/\s+/);
3661
- /** https://www.w3.org/TR/WGSL/#predeclared-enumerants */
3662
- const stdEnumerants = `read write read_write
3663
- function private workgroup uniform storage
3664
- rgba8unorm rgba8snorm rgba8uint rgba8sint
3665
- rgba16uint rgba16sint rgba16float
3666
- r32uint r32sint r32float rg32uint rg32sint rg32float
3667
- rgba32uint rgba32sint rgba32float bgra8unorm`.split(/\s+/);
3668
- /** return true if the name is for a built in type (not a user struct) */
3669
- function stdType(name) {
3670
- return stdTypes.includes(name);
3671
- }
3672
- /** return true if the name is for a built in fn (not a user function) */
3673
- function stdFn(name) {
3674
- return stdFns.includes(name) || stdType(name);
3675
- }
3676
- /** return true if the name is for a built in enumerant */
3677
- function stdEnumerant(name) {
3678
- return stdEnumerants.includes(name);
3679
- }
3680
-
3681
3680
  //#endregion
3682
3681
  //#region src/BindIdents.ts
3683
- /** WGSL standard attributes whose params need binding (e.g., @workgroup_size). */
3684
- const wgslStandardAttributes = new Set([
3685
- "align",
3686
- "binding",
3687
- "blend_src",
3688
- "compute",
3689
- "const",
3690
- "fragment",
3691
- "group",
3692
- "id",
3693
- "invariant",
3694
- "location",
3695
- "must_use",
3696
- "size",
3697
- "vertex",
3698
- "workgroup_size"
3699
- ]);
3700
3682
  /** Bind ref idents to declarations and mangle global declaration names. */
3701
3683
  function bindIdents(params) {
3702
3684
  const { rootAst, resolver, virtuals, accumulateUnbound } = params;
3703
3685
  const { conditions = {}, mangler = minimalMangle } = params;
3686
+ const { discoveryMode } = params;
3704
3687
  const packageName = rootAst.srcModule.modulePath.split("::")[0];
3705
- const validRootDecls = findValidRootDecls(rootAst.rootScope, conditions);
3706
- const { globalNames, knownDecls } = initRootDecls(validRootDecls);
3688
+ const rootDecls = discoveryMode ? findAllRootDecls(rootAst.rootScope) : findValidRootDecls(rootAst.rootScope, conditions);
3689
+ const { globalNames, knownDecls } = initRootDecls(rootDecls);
3707
3690
  const bindContext = {
3708
3691
  resolver,
3709
3692
  conditions,
@@ -3714,13 +3697,14 @@ function bindIdents(params) {
3714
3697
  foundScopes: /* @__PURE__ */ new Set(),
3715
3698
  globalNames,
3716
3699
  globalStatements: /* @__PURE__ */ new Map(),
3717
- unbound: accumulateUnbound ? [] : void 0
3700
+ unbound: accumulateUnbound ? [] : void 0,
3701
+ discoveryMode
3718
3702
  };
3719
3703
  const liveDecls = {
3720
- decls: new Map(validRootDecls.map((d) => [d.originalName, d])),
3704
+ decls: new Map(rootDecls.map((d) => [d.originalName, d])),
3721
3705
  parent: null
3722
3706
  };
3723
- const fromRootDecls = validRootDecls.flatMap((decl) => processDependentScope(decl, bindContext));
3707
+ const fromRootDecls = rootDecls.flatMap((decl) => processDependentScope(decl, bindContext));
3724
3708
  const fromRefs = bindIdentsRecursive(rootAst.rootScope, bindContext, liveDecls);
3725
3709
  const newStatements = [...bindContext.globalStatements.values()];
3726
3710
  return {
@@ -3756,17 +3740,11 @@ function* validItems(scope, conditions) {
3756
3740
  }
3757
3741
  /** Find all conditionally valid declarations at the root level. */
3758
3742
  function findValidRootDecls(rootScope, conditions) {
3759
- const found = [];
3760
- for (const item of validItems(rootScope, conditions)) if (item.kind === "decl") found.push(item);
3761
- else if (item.kind === "partial") collectDecls(item, found);
3762
- return found;
3743
+ return collectDecls(validItems(rootScope, conditions));
3763
3744
  }
3764
3745
  /** Find all declarations at the root level, ignoring conditions. */
3765
3746
  function findAllRootDecls(rootScope) {
3766
- const found = [];
3767
- for (const item of rootScope.contents) if (item.kind === "decl") found.push(item);
3768
- else if (item.kind === "partial") collectDecls(item, found);
3769
- return found;
3747
+ return collectDecls(rootScope.contents);
3770
3748
  }
3771
3749
  /** Find a public declaration with the given original name. */
3772
3750
  function publicDecl(scope, name, conditions) {
@@ -3834,10 +3812,11 @@ function handleRef(ident, liveDecls, bindContext) {
3834
3812
  }
3835
3813
  if (!bindContext.unbound) failIdent(ident, `unresolved identifier '${ident.originalName}'`);
3836
3814
  }
3815
+ /** Follow new global declarations into their dependent scopes. */
3837
3816
  function handleDecls(newGlobals, bindContext) {
3838
3817
  return newGlobals.flatMap((decl) => processDependentScope(decl, bindContext));
3839
3818
  }
3840
- /** If found declaration is new, mangle its name. Return if it's a global declaration. */
3819
+ /** If found declaration is new, mangle its name. @return the decl if it's global. */
3841
3820
  function handleNewDecl(refIdent, foundDecl, ctx) {
3842
3821
  const { decl, moduleAst } = foundDecl;
3843
3822
  const { knownDecls, globalNames, mangler, globalStatements } = ctx;
@@ -3864,7 +3843,8 @@ function findDeclInModule(ident, liveDecls) {
3864
3843
  /** Match a ref ident to a declaration in another module via import or qualified ident. */
3865
3844
  function findQualifiedImport(refIdent, ctx) {
3866
3845
  const { conditions, unbound, discoveryMode } = ctx;
3867
- const flatImps = flatImports(refIdent.ast, discoveryMode ? void 0 : conditions);
3846
+ const conds = discoveryMode ? void 0 : conditions;
3847
+ const flatImps = flatImports(refIdent.ast, conds);
3868
3848
  const identParts = refIdent.originalName.split("::");
3869
3849
  const pathParts = matchingImport(identParts, flatImps) ?? qualifiedIdent(identParts);
3870
3850
  if (!pathParts) {
@@ -3918,7 +3898,7 @@ function virtualModule(moduleName, ctx) {
3918
3898
  /** Get cached valid root declarations, computing on first access. */
3919
3899
  function getValidRootDecls(rootScope, conditions) {
3920
3900
  const lexScope = rootScope;
3921
- if (!lexScope._validRootDecls) lexScope._validRootDecls = findValidRootDecls(rootScope, conditions);
3901
+ lexScope._validRootDecls ??= findValidRootDecls(rootScope, conditions);
3922
3902
  return lexScope._validRootDecls;
3923
3903
  }
3924
3904
  /** Given a global declIdent, return the liveDecls for its root scope. */
@@ -3947,13 +3927,167 @@ function setMangledName(proposedName, decl, globalNames, srcModule, mangler) {
3947
3927
  function stdWgsl(name) {
3948
3928
  return stdType(name) || stdFn(name) || stdEnumerant(name);
3949
3929
  }
3930
+ /** @return identParts if it's a qualified path (has ::). */
3950
3931
  function qualifiedIdent(identParts) {
3951
3932
  if (identParts.length > 1) return identParts;
3952
3933
  }
3953
- /** Collect all declarations in a scope (used when scope is already validated). */
3954
- function collectDecls(scope, found) {
3955
- for (const item of scope.contents) if (item.kind === "decl") found.push(item);
3956
- else if (item.kind === "partial") collectDecls(item, found);
3934
+ /** Collect all declarations from scope items, recursing into partial scopes. */
3935
+ function collectDecls(items) {
3936
+ return [...items].flatMap((item) => {
3937
+ const { kind } = item;
3938
+ if (kind === "decl") return [item];
3939
+ if (kind === "partial") return collectDecls(item.contents);
3940
+ return [];
3941
+ });
3942
+ }
3943
+
3944
+ //#endregion
3945
+ //#region src/PathUtil.ts
3946
+ /** simplistic path manipulation utilities */
3947
+ /** return path with ./ and foo/.. elements removed */
3948
+ function normalize(path) {
3949
+ const noDots = path.split("/").filter((s) => s !== ".");
3950
+ const noDbl = [];
3951
+ noDots.forEach((s) => {
3952
+ if (s !== "") if (s === ".." && noDbl.length && noDbl[noDbl.length - 1] !== "..") noDbl.pop();
3953
+ else noDbl.push(s);
3954
+ });
3955
+ return noDbl.join("/");
3956
+ }
3957
+ /** return path w/o a suffix.
3958
+ * e.g. /foo/bar.wgsl => /foo/bar */
3959
+ function noSuffix(path) {
3960
+ const lastSlash = path.lastIndexOf("/");
3961
+ const lastStart = lastSlash === -1 ? 0 : lastSlash + 1;
3962
+ const suffix = path.indexOf(".", lastStart);
3963
+ const suffixStart = suffix === -1 ? path.length : suffix;
3964
+ return path.slice(0, suffixStart);
3965
+ }
3966
+
3967
+ //#endregion
3968
+ //#region src/ModuleResolver.ts
3969
+ const libRegex = /^lib\.w[eg]sl$/i;
3970
+ /** Module resolver for in-memory source records. Lazy by default. */
3971
+ var RecordResolver = class {
3972
+ astCache = /* @__PURE__ */ new Map();
3973
+ sources;
3974
+ packageName;
3975
+ debugWeslRoot;
3976
+ constructor(sources, options = {}) {
3977
+ const { packageName = "package", debugWeslRoot } = options;
3978
+ this.sources = sources;
3979
+ this.packageName = packageName;
3980
+ this.debugWeslRoot = normalizeDebugRoot(debugWeslRoot);
3981
+ }
3982
+ resolveModule(modulePath) {
3983
+ const cached = this.astCache.get(modulePath);
3984
+ if (cached) return cached;
3985
+ const source = this.findSource(modulePath);
3986
+ if (source === void 0) return void 0;
3987
+ const ast = parseSrcModule({
3988
+ modulePath,
3989
+ debugFilePath: this.modulePathToDebugPath(modulePath),
3990
+ src: source
3991
+ });
3992
+ this.astCache.set(modulePath, ast);
3993
+ return ast;
3994
+ }
3995
+ findSource(modulePath) {
3996
+ if (this.sources[modulePath] !== void 0) return this.sources[modulePath];
3997
+ const filePath = this.moduleToFilePath(modulePath);
3998
+ if (filePath === void 0) return void 0;
3999
+ return findInVariants(this.sources, filePath);
4000
+ }
4001
+ /** Convert module path to file path, or undefined if not local. */
4002
+ moduleToFilePath(modulePath) {
4003
+ return moduleToRelativePath(modulePath, this.packageName);
4004
+ }
4005
+ modulePathToDebugPath(modulePath) {
4006
+ const filePath = this.moduleToFilePath(modulePath) ?? modulePath;
4007
+ return this.debugWeslRoot + filePath + ".wesl";
4008
+ }
4009
+ /** Parse all modules and return entries. */
4010
+ allModules() {
4011
+ for (const filePath of Object.keys(this.sources)) {
4012
+ const treatLibAsRoot = this.packageName !== "package";
4013
+ const modulePath = fileToModulePath(filePath, this.packageName, treatLibAsRoot);
4014
+ this.resolveModule(modulePath);
4015
+ }
4016
+ return this.astCache.entries();
4017
+ }
4018
+ };
4019
+ /** Composite resolver that tries each resolver in order until one succeeds. */
4020
+ var CompositeResolver = class {
4021
+ resolvers;
4022
+ constructor(resolvers) {
4023
+ this.resolvers = resolvers;
4024
+ }
4025
+ resolveModule(modulePath) {
4026
+ for (const resolver of this.resolvers) {
4027
+ const ast = resolver.resolveModule(modulePath);
4028
+ if (ast) return ast;
4029
+ }
4030
+ }
4031
+ };
4032
+ /** Lazy resolver for WeslBundle library modules. */
4033
+ var BundleResolver = class {
4034
+ astCache = /* @__PURE__ */ new Map();
4035
+ sources;
4036
+ packageName;
4037
+ debugWeslRoot;
4038
+ constructor(bundle, debugWeslRoot) {
4039
+ this.sources = bundle.modules;
4040
+ this.packageName = bundle.name;
4041
+ this.debugWeslRoot = normalizeDebugRoot(debugWeslRoot);
4042
+ }
4043
+ resolveModule(modulePath) {
4044
+ const pkgPrefix = this.packageName + "::";
4045
+ if (modulePath !== this.packageName && !modulePath.startsWith(pkgPrefix)) return;
4046
+ const cached = this.astCache.get(modulePath);
4047
+ if (cached) return cached;
4048
+ const source = this.findSource(modulePath);
4049
+ if (!source) return void 0;
4050
+ const ast = parseSrcModule({
4051
+ modulePath,
4052
+ debugFilePath: this.modulePathToDebugPath(modulePath),
4053
+ src: source
4054
+ });
4055
+ this.astCache.set(modulePath, ast);
4056
+ return ast;
4057
+ }
4058
+ findSource(modulePath) {
4059
+ const filePath = this.moduleToFilePath(modulePath);
4060
+ if (modulePath === this.packageName) {
4061
+ const libSrc = findInVariants(this.sources, "lib", ["wesl", "wgsl"]);
4062
+ if (libSrc) return libSrc;
4063
+ }
4064
+ return findInVariants(this.sources, filePath);
4065
+ }
4066
+ moduleToFilePath(modulePath) {
4067
+ return moduleToRelativePath(modulePath, this.packageName) ?? modulePath;
4068
+ }
4069
+ modulePathToDebugPath(modulePath) {
4070
+ const filePath = this.moduleToFilePath(modulePath);
4071
+ return this.debugWeslRoot + this.packageName + "/" + filePath + ".wesl";
4072
+ }
4073
+ };
4074
+ /** Convert file path to module path (e.g., "foo/bar.wesl" to "package::foo::bar"). */
4075
+ function fileToModulePath(filePath, packageName, treatLibAsRoot) {
4076
+ if (filePath.includes("::")) return filePath;
4077
+ if (treatLibAsRoot && libRegex.test(filePath)) return packageName;
4078
+ const moduleSuffix = noSuffix(normalize(filePath)).replaceAll("/", "::");
4079
+ return packageName + "::" + moduleSuffix;
4080
+ }
4081
+ /** Try path variants with/without ./ prefix and extension suffixes. */
4082
+ function findInVariants(sources, basePath, extensions = ["wesl", "wgsl"]) {
4083
+ for (const prefix of ["", "./"]) {
4084
+ const path = prefix + basePath;
4085
+ if (sources[path] !== void 0) return sources[path];
4086
+ for (const ext of extensions) {
4087
+ const withExt = `${path}.${ext}`;
4088
+ if (sources[withExt] !== void 0) return sources[withExt];
4089
+ }
4090
+ }
3957
4091
  }
3958
4092
 
3959
4093
  //#endregion
@@ -3992,13 +4126,48 @@ function findUnboundRefs(resolver) {
3992
4126
  decls: new Map(rootDecls.map((d) => [d.originalName, d])),
3993
4127
  parent: null
3994
4128
  };
3995
- filterMap(rootDecls, (decl) => decl.dependentScope).forEach((s) => {
3996
- bindIdentsRecursive(s, bindContext, makeLiveDecls(liveDecls));
3997
- });
4129
+ for (const s of filterMap(rootDecls, (decl) => decl.dependentScope)) bindIdentsRecursive(s, bindContext, makeLiveDecls(liveDecls));
3998
4130
  bindIdentsRecursive(ast.rootScope, bindContext, liveDecls);
3999
4131
  }
4000
4132
  return bindContext.unbound;
4001
4133
  }
4134
+ /** Thin decorator that records which modules were resolved. */
4135
+ var TrackingResolver = class {
4136
+ visited = /* @__PURE__ */ new Set();
4137
+ #inner;
4138
+ constructor(inner) {
4139
+ this.#inner = inner;
4140
+ }
4141
+ resolveModule(modulePath) {
4142
+ const ast = this.#inner.resolveModule(modulePath);
4143
+ if (ast) this.visited.add(modulePath);
4144
+ return ast;
4145
+ }
4146
+ };
4147
+ /**
4148
+ * Discover reachable modules and unbound external refs from a single root.
4149
+ *
4150
+ * Traces the import graph from `rootModuleName`, returning only the reachable
4151
+ * local modules in `weslSrc` and unresolved external references in `unbound`.
4152
+ */
4153
+ function discoverModules(weslSrc, resolver, rootModuleName, packageName = "package") {
4154
+ const tracking = new TrackingResolver(resolver);
4155
+ const rootAst = tracking.resolveModule(rootModuleName);
4156
+ if (!rootAst) throw new Error(`root module not found: '${rootModuleName}'`);
4157
+ const result = bindIdents({
4158
+ rootAst,
4159
+ resolver: tracking,
4160
+ accumulateUnbound: true,
4161
+ discoveryMode: true
4162
+ });
4163
+ const moduleToKey = new Map(Object.keys(weslSrc).map((key) => [fileToModulePath(key, packageName, false), key]));
4164
+ const reachable = [...tracking.visited].map((m) => moduleToKey.get(m)).filter((key) => key !== void 0).map((key) => [key, weslSrc[key]]);
4165
+ const unbound = (result.unbound ?? []).map((ref) => ref.path);
4166
+ return {
4167
+ weslSrc: Object.fromEntries(reachable),
4168
+ unbound
4169
+ };
4170
+ }
4002
4171
 
4003
4172
  //#endregion
4004
4173
  //#region src/discovery/PackageNameUtils.ts
@@ -4169,155 +4338,6 @@ function compilationInfoToErrorMessage(compilationInfo, shaderModule) {
4169
4338
  return result;
4170
4339
  }
4171
4340
 
4172
- //#endregion
4173
- //#region src/PathUtil.ts
4174
- /** simplistic path manipulation utilities */
4175
- /** return path with ./ and foo/.. elements removed */
4176
- function normalize(path) {
4177
- const noDots = path.split("/").filter((s) => s !== ".");
4178
- const noDbl = [];
4179
- noDots.forEach((s) => {
4180
- if (s !== "") if (s === ".." && noDbl.length && noDbl[noDbl.length - 1] !== "..") noDbl.pop();
4181
- else noDbl.push(s);
4182
- });
4183
- return noDbl.join("/");
4184
- }
4185
- /** return path w/o a suffix.
4186
- * e.g. /foo/bar.wgsl => /foo/bar */
4187
- function noSuffix(path) {
4188
- const lastSlash = path.lastIndexOf("/");
4189
- const lastStart = lastSlash === -1 ? 0 : lastSlash + 1;
4190
- const suffix = path.indexOf(".", lastStart);
4191
- const suffixStart = suffix === -1 ? path.length : suffix;
4192
- return path.slice(0, suffixStart);
4193
- }
4194
-
4195
- //#endregion
4196
- //#region src/ModuleResolver.ts
4197
- const libRegex = /^lib\.w[eg]sl$/i;
4198
- /** Module resolver for in-memory source records. Lazy by default. */
4199
- var RecordResolver = class {
4200
- astCache = /* @__PURE__ */ new Map();
4201
- sources;
4202
- packageName;
4203
- debugWeslRoot;
4204
- constructor(sources, options = {}) {
4205
- const { packageName = "package", debugWeslRoot } = options;
4206
- this.sources = sources;
4207
- this.packageName = packageName;
4208
- this.debugWeslRoot = normalizeDebugRoot(debugWeslRoot);
4209
- }
4210
- resolveModule(modulePath) {
4211
- const cached = this.astCache.get(modulePath);
4212
- if (cached) return cached;
4213
- const source = this.findSource(modulePath);
4214
- if (source === void 0) return void 0;
4215
- const ast = parseSrcModule({
4216
- modulePath,
4217
- debugFilePath: this.modulePathToDebugPath(modulePath),
4218
- src: source
4219
- });
4220
- this.astCache.set(modulePath, ast);
4221
- return ast;
4222
- }
4223
- findSource(modulePath) {
4224
- if (this.sources[modulePath] !== void 0) return this.sources[modulePath];
4225
- const filePath = this.moduleToFilePath(modulePath);
4226
- if (filePath === void 0) return void 0;
4227
- return findInVariants(this.sources, filePath);
4228
- }
4229
- /** Convert module path to file path, or undefined if not local. */
4230
- moduleToFilePath(modulePath) {
4231
- return moduleToRelativePath(modulePath, this.packageName);
4232
- }
4233
- modulePathToDebugPath(modulePath) {
4234
- const filePath = this.moduleToFilePath(modulePath) ?? modulePath;
4235
- return this.debugWeslRoot + filePath + ".wesl";
4236
- }
4237
- /** Parse all modules and return entries. */
4238
- allModules() {
4239
- for (const filePath of Object.keys(this.sources)) {
4240
- const treatLibAsRoot = this.packageName !== "package";
4241
- const modulePath = fileToModulePath(filePath, this.packageName, treatLibAsRoot);
4242
- this.resolveModule(modulePath);
4243
- }
4244
- return this.astCache.entries();
4245
- }
4246
- };
4247
- /** Composite resolver that tries each resolver in order until one succeeds. */
4248
- var CompositeResolver = class {
4249
- resolvers;
4250
- constructor(resolvers) {
4251
- this.resolvers = resolvers;
4252
- }
4253
- resolveModule(modulePath) {
4254
- for (const resolver of this.resolvers) {
4255
- const ast = resolver.resolveModule(modulePath);
4256
- if (ast) return ast;
4257
- }
4258
- }
4259
- };
4260
- /** Lazy resolver for WeslBundle library modules. */
4261
- var BundleResolver = class {
4262
- astCache = /* @__PURE__ */ new Map();
4263
- sources;
4264
- packageName;
4265
- debugWeslRoot;
4266
- constructor(bundle, debugWeslRoot) {
4267
- this.sources = bundle.modules;
4268
- this.packageName = bundle.name;
4269
- this.debugWeslRoot = normalizeDebugRoot(debugWeslRoot);
4270
- }
4271
- resolveModule(modulePath) {
4272
- const pkgPrefix = this.packageName + "::";
4273
- if (modulePath !== this.packageName && !modulePath.startsWith(pkgPrefix)) return;
4274
- const cached = this.astCache.get(modulePath);
4275
- if (cached) return cached;
4276
- const source = this.findSource(modulePath);
4277
- if (!source) return void 0;
4278
- const ast = parseSrcModule({
4279
- modulePath,
4280
- debugFilePath: this.modulePathToDebugPath(modulePath),
4281
- src: source
4282
- });
4283
- this.astCache.set(modulePath, ast);
4284
- return ast;
4285
- }
4286
- findSource(modulePath) {
4287
- const filePath = this.moduleToFilePath(modulePath);
4288
- if (modulePath === this.packageName) {
4289
- const libSrc = findInVariants(this.sources, "lib", ["wesl", "wgsl"]);
4290
- if (libSrc) return libSrc;
4291
- }
4292
- return findInVariants(this.sources, filePath);
4293
- }
4294
- moduleToFilePath(modulePath) {
4295
- return moduleToRelativePath(modulePath, this.packageName) ?? modulePath;
4296
- }
4297
- modulePathToDebugPath(modulePath) {
4298
- const filePath = this.moduleToFilePath(modulePath);
4299
- return this.debugWeslRoot + this.packageName + "/" + filePath + ".wesl";
4300
- }
4301
- };
4302
- /** Convert file path to module path (e.g., "foo/bar.wesl" to "package::foo::bar"). */
4303
- function fileToModulePath(filePath, packageName, treatLibAsRoot) {
4304
- if (filePath.includes("::")) return filePath;
4305
- if (treatLibAsRoot && libRegex.test(filePath)) return packageName;
4306
- const moduleSuffix = noSuffix(normalize(filePath)).replaceAll("/", "::");
4307
- return packageName + "::" + moduleSuffix;
4308
- }
4309
- /** Try path variants with/without ./ prefix and extension suffixes. */
4310
- function findInVariants(sources, basePath, extensions = ["wesl", "wgsl"]) {
4311
- for (const prefix of ["", "./"]) {
4312
- const path = prefix + basePath;
4313
- if (sources[path] !== void 0) return sources[path];
4314
- for (const ext of extensions) {
4315
- const withExt = `${path}.${ext}`;
4316
- if (sources[withExt] !== void 0) return sources[withExt];
4317
- }
4318
- }
4319
- }
4320
-
4321
4341
  //#endregion
4322
4342
  //#region src/SrcMap.ts
4323
4343
  /** map text ranges in multiple src texts to a single dest text */
@@ -4972,4 +4992,4 @@ function makeWeslDevice(device) {
4972
4992
  }
4973
4993
 
4974
4994
  //#endregion
4975
- export { BundleResolver, CompositeResolver, LinkedWesl, ParseError, RecordResolver, SrcMap, SrcMapBuilder, WeslParseError, WeslStream, _linkSync, astToString, attributeToString, bindAndTransform, bindIdents, bindIdentsRecursive, bindingStructsPlugin, childIdent, childScope, containsScope, debug, debugContentsToString, emptyScope, errorHighlight, fileToModulePath, filterMap, findAllRootDecls, findMap, findRefsToBindingStructs, findUnboundIdents, findUnboundRefs, findValidRootDecls, flatImports, 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, withLoggerAsync };
4995
+ export { BundleResolver, CompositeResolver, LinkedWesl, ParseError, RecordResolver, SrcMap, SrcMapBuilder, TrackingResolver, WeslParseError, 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, 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wesl",
3
- "version": "0.7.22",
3
+ "version": "0.7.23",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
package/src/BindIdents.ts CHANGED
@@ -16,29 +16,17 @@ import type {
16
16
  LexicalScope,
17
17
  RefIdent,
18
18
  Scope,
19
+ ScopeItem,
19
20
  SrcModule,
20
21
  } from "./Scope.ts";
21
- import { stdEnumerant, stdFn, stdType } from "./StandardTypes.ts";
22
+ import {
23
+ stdEnumerant,
24
+ stdFn,
25
+ stdType,
26
+ wgslStandardAttributes,
27
+ } from "./StandardTypes.ts";
22
28
  import { last } from "./Util.ts";
23
29
 
24
- /** WGSL standard attributes whose params need binding (e.g., @workgroup_size). */
25
- const wgslStandardAttributes = new Set([
26
- "align",
27
- "binding",
28
- "blend_src",
29
- "compute",
30
- "const",
31
- "fragment",
32
- "group",
33
- "id",
34
- "invariant",
35
- "location",
36
- "must_use",
37
- "size",
38
- "vertex",
39
- "workgroup_size",
40
- ]);
41
-
42
30
  /**
43
31
  * BindIdents pass: link reference identifiers to declarations.
44
32
  *
@@ -112,16 +100,22 @@ export interface BindIdentsParams
112
100
 
113
101
  /** If true, accumulate unbound identifiers into BindResults.unbound instead of throwing. */
114
102
  accumulateUnbound?: true;
103
+
104
+ /** Visit all conditional branches (for dependency discovery). */
105
+ discoveryMode?: boolean;
115
106
  }
116
107
 
117
108
  /** Bind ref idents to declarations and mangle global declaration names. */
118
109
  export function bindIdents(params: BindIdentsParams): BindResults {
119
110
  const { rootAst, resolver, virtuals, accumulateUnbound } = params;
120
111
  const { conditions = {}, mangler = minimalMangle } = params;
112
+ const { discoveryMode } = params;
121
113
  const packageName = rootAst.srcModule.modulePath.split("::")[0];
122
114
 
123
- const validRootDecls = findValidRootDecls(rootAst.rootScope, conditions);
124
- const { globalNames, knownDecls } = initRootDecls(validRootDecls);
115
+ const rootDecls = discoveryMode
116
+ ? findAllRootDecls(rootAst.rootScope)
117
+ : findValidRootDecls(rootAst.rootScope, conditions);
118
+ const { globalNames, knownDecls } = initRootDecls(rootDecls);
125
119
 
126
120
  const bindContext = {
127
121
  resolver,
@@ -134,13 +128,13 @@ export function bindIdents(params: BindIdentsParams): BindResults {
134
128
  globalNames,
135
129
  globalStatements: new Map<AbstractElem, EmittableElem>(),
136
130
  unbound: accumulateUnbound ? [] : undefined,
131
+ discoveryMode,
137
132
  };
138
133
 
139
- const decls = new Map(validRootDecls.map(d => [d.originalName, d] as const));
134
+ const decls = new Map(rootDecls.map(d => [d.originalName, d] as const));
140
135
  const liveDecls: LiveDecls = { decls, parent: null };
141
136
 
142
- // Process dependent scopes for all valid root decls (already filtered by conditions)
143
- const fromRootDecls = validRootDecls.flatMap(decl =>
137
+ const fromRootDecls = rootDecls.flatMap(decl =>
144
138
  processDependentScope(decl, bindContext),
145
139
  );
146
140
 
@@ -195,22 +189,12 @@ export function findValidRootDecls(
195
189
  rootScope: Scope,
196
190
  conditions: Conditions,
197
191
  ): DeclIdent[] {
198
- const found: DeclIdent[] = [];
199
- for (const item of validItems(rootScope, conditions)) {
200
- if (item.kind === "decl") found.push(item);
201
- else if (item.kind === "partial") collectDecls(item, found);
202
- }
203
- return found;
192
+ return collectDecls(validItems(rootScope, conditions));
204
193
  }
205
194
 
206
195
  /** Find all declarations at the root level, ignoring conditions. */
207
196
  export function findAllRootDecls(rootScope: Scope): DeclIdent[] {
208
- const found: DeclIdent[] = [];
209
- for (const item of rootScope.contents) {
210
- if (item.kind === "decl") found.push(item);
211
- else if (item.kind === "partial") collectDecls(item, found);
212
- }
213
- return found;
197
+ return collectDecls(rootScope.contents);
214
198
  }
215
199
 
216
200
  /** Find a public declaration with the given original name. */
@@ -353,6 +337,7 @@ function handleRef(
353
337
  failIdent(ident, `unresolved identifier '${ident.originalName}'`);
354
338
  }
355
339
 
340
+ /** Follow new global declarations into their dependent scopes. */
356
341
  function handleDecls(
357
342
  newGlobals: DeclIdent[],
358
343
  bindContext: BindContext,
@@ -360,7 +345,7 @@ function handleDecls(
360
345
  return newGlobals.flatMap(decl => processDependentScope(decl, bindContext));
361
346
  }
362
347
 
363
- /** If found declaration is new, mangle its name. Return if it's a global declaration. */
348
+ /** If found declaration is new, mangle its name. @return the decl if it's global. */
364
349
  function handleNewDecl(
365
350
  refIdent: RefIdent,
366
351
  foundDecl: FoundDecl,
@@ -397,10 +382,8 @@ function findQualifiedImport(
397
382
  ctx: BindContext,
398
383
  ): FoundDecl | undefined {
399
384
  const { conditions, unbound, discoveryMode } = ctx;
400
- const flatImps = flatImports(
401
- refIdent.ast,
402
- discoveryMode ? undefined : conditions,
403
- );
385
+ const conds = discoveryMode ? undefined : conditions;
386
+ const flatImps = flatImports(refIdent.ast, conds);
404
387
  const identParts = refIdent.originalName.split("::");
405
388
  const pathParts =
406
389
  matchingImport(identParts, flatImps) ?? qualifiedIdent(identParts);
@@ -490,9 +473,7 @@ function getValidRootDecls(
490
473
  conditions: Conditions,
491
474
  ): DeclIdent[] {
492
475
  const lexScope = rootScope as LexicalScope;
493
- if (!lexScope._validRootDecls) {
494
- lexScope._validRootDecls = findValidRootDecls(rootScope, conditions);
495
- }
476
+ lexScope._validRootDecls ??= findValidRootDecls(rootScope, conditions);
496
477
  return lexScope._validRootDecls;
497
478
  }
498
479
 
@@ -504,9 +485,7 @@ function rootLiveDecls(
504
485
  assertThatDebug(decl.isGlobal, identToString(decl));
505
486
 
506
487
  let scope = decl.containingScope;
507
- while (scope.parent) {
508
- scope = scope.parent;
509
- }
488
+ while (scope.parent) scope = scope.parent;
510
489
  assertThatDebug(scope.kind === "scope");
511
490
 
512
491
  const root = scope as LexicalScope;
@@ -543,14 +522,17 @@ function stdWgsl(name: string): boolean {
543
522
  return stdType(name) || stdFn(name) || stdEnumerant(name); // TODO add tests for enumerants case (e.g. var x = read;)
544
523
  }
545
524
 
525
+ /** @return identParts if it's a qualified path (has ::). */
546
526
  function qualifiedIdent(identParts: string[]): string[] | undefined {
547
527
  if (identParts.length > 1) return identParts;
548
528
  }
549
529
 
550
- /** Collect all declarations in a scope (used when scope is already validated). */
551
- function collectDecls(scope: Scope, found: DeclIdent[]): void {
552
- for (const item of scope.contents) {
553
- if (item.kind === "decl") found.push(item);
554
- else if (item.kind === "partial") collectDecls(item, found);
555
- }
530
+ /** Collect all declarations from scope items, recursing into partial scopes. */
531
+ function collectDecls(items: Iterable<ScopeItem>): DeclIdent[] {
532
+ return [...items].flatMap(item => {
533
+ const { kind } = item;
534
+ if (kind === "decl") return [item];
535
+ if (kind === "partial") return collectDecls(item.contents);
536
+ return [];
537
+ });
556
538
  }
@@ -18,6 +18,7 @@ import { filterValidElements } from "./Conditions.ts";
18
18
  import { identToString } from "./debug/ScopeToString.ts";
19
19
  import type { Conditions, DeclIdent, Ident } from "./Scope.ts";
20
20
  import type { SrcMapBuilder } from "./SrcMap.ts";
21
+ import { wgslStandardAttributes } from "./StandardTypes.ts";
21
22
 
22
23
  export interface EmitParams {
23
24
  srcBuilder: SrcMapBuilder;
@@ -36,26 +37,6 @@ interface EmitContext {
36
37
  extracting: boolean;
37
38
  }
38
39
 
39
- /** Valid WGSL standard attributes (from spec). Non-WGSL attributes are stripped.
40
- * See: https://www.w3.org/TR/WGSL/#attributes
41
- * Note: @builtin, @diagnostic, @interpolate are parsed as separate attribute types. */
42
- const wgslStandardAttributes = new Set([
43
- "align",
44
- "binding",
45
- "blend_src",
46
- "compute",
47
- "const",
48
- "fragment",
49
- "group",
50
- "id",
51
- "invariant",
52
- "location",
53
- "must_use",
54
- "size",
55
- "vertex",
56
- "workgroup_size",
57
- ]);
58
-
59
40
  /** Traverse the AST, starting from root elements, emitting WGSL for each. */
60
41
  export function lowerAndEmit(params: EmitParams): void {
61
42
  const { srcBuilder, rootElems, conditions } = params;
package/src/Scope.ts CHANGED
@@ -23,6 +23,8 @@ export interface SrcModule {
23
23
  /** a src declaration or reference to an ident */
24
24
  export type Ident = DeclIdent | RefIdent;
25
25
 
26
+ export type ScopeItem = Ident | Scope;
27
+
26
28
  /** LATER change this to a Map, so that `toString` isn't accidentally a condition */
27
29
  export type Conditions = Record<string, boolean>;
28
30
 
@@ -106,7 +108,7 @@ interface ScopeBase {
106
108
  parent: Scope | null;
107
109
 
108
110
  /* Child scopes and idents in lexical order */
109
- contents: (Ident | Scope)[];
111
+ contents: ScopeItem[];
110
112
 
111
113
  /** Conditional attribute (@if or @else) for this scope */
112
114
  condAttribute?: IfAttribute | ElifAttribute | ElseAttribute;
@@ -81,6 +81,25 @@ export const stdEnumerants = `read write read_write
81
81
  the texture format names with e.g. a 'struct rbga8unorm .)
82
82
  */
83
83
 
84
+ /** WGSL standard attributes whose params need binding (e.g., @workgroup_size).
85
+ * See: https://www.w3.org/TR/WGSL/#attributes */
86
+ export const wgslStandardAttributes = new Set([
87
+ "align",
88
+ "binding",
89
+ "blend_src",
90
+ "compute",
91
+ "const",
92
+ "fragment",
93
+ "group",
94
+ "id",
95
+ "invariant",
96
+ "location",
97
+ "must_use",
98
+ "size",
99
+ "vertex",
100
+ "workgroup_size",
101
+ ]);
102
+
84
103
  /** return true if the name is for a built in type (not a user struct) */
85
104
  export function stdType(name: string): boolean {
86
105
  return stdTypes.includes(name);
@@ -1,5 +1,6 @@
1
1
  import type { AbstractElem } from "../AbstractElems.ts";
2
2
  import {
3
+ bindIdents,
3
4
  bindIdentsRecursive,
4
5
  type EmittableElem,
5
6
  findAllRootDecls,
@@ -7,7 +8,12 @@ import {
7
8
  } from "../BindIdents.ts";
8
9
  import { type LiveDecls, makeLiveDecls } from "../LiveDeclarations.ts";
9
10
  import { minimalMangle } from "../Mangler.ts";
10
- import type { BatchModuleResolver } from "../ModuleResolver.ts";
11
+ import {
12
+ type BatchModuleResolver,
13
+ fileToModulePath,
14
+ type ModuleResolver,
15
+ } from "../ModuleResolver.ts";
16
+ import type { WeslAST } from "../ParseWESL.ts";
11
17
  import type { DeclIdent, Scope } from "../Scope.ts";
12
18
  import { filterMap } from "../Util.ts";
13
19
 
@@ -43,18 +49,70 @@ export function findUnboundRefs(resolver: BatchModuleResolver): UnboundRef[] {
43
49
 
44
50
  for (const [, ast] of resolver.allModules()) {
45
51
  const rootDecls = findAllRootDecls(ast.rootScope);
46
- const liveDecls: LiveDecls = {
47
- decls: new Map(rootDecls.map(d => [d.originalName, d] as const)),
48
- parent: null,
49
- };
52
+ const decls = new Map(rootDecls.map(d => [d.originalName, d] as const));
53
+ const liveDecls: LiveDecls = { decls, parent: null };
50
54
  // Process dependent scopes of root decls to find unbound refs in function bodies
51
- const scopes = filterMap(rootDecls, decl => decl.dependentScope);
52
- scopes.forEach(s => {
55
+ for (const s of filterMap(rootDecls, decl => decl.dependentScope)) {
53
56
  bindIdentsRecursive(s, bindContext, makeLiveDecls(liveDecls));
54
- });
57
+ }
55
58
  // Also process refs at root scope level
56
59
  bindIdentsRecursive(ast.rootScope, bindContext, liveDecls);
57
60
  }
58
61
 
59
62
  return bindContext.unbound;
60
63
  }
64
+
65
+ /** Thin decorator that records which modules were resolved. */
66
+ export class TrackingResolver implements ModuleResolver {
67
+ readonly visited = new Set<string>();
68
+ #inner: ModuleResolver;
69
+ constructor(inner: ModuleResolver) {
70
+ this.#inner = inner;
71
+ }
72
+
73
+ resolveModule(modulePath: string): WeslAST | undefined {
74
+ const ast = this.#inner.resolveModule(modulePath);
75
+ if (ast) this.visited.add(modulePath);
76
+ return ast;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Discover reachable modules and unbound external refs from a single root.
82
+ *
83
+ * Traces the import graph from `rootModuleName`, returning only the reachable
84
+ * local modules in `weslSrc` and unresolved external references in `unbound`.
85
+ */
86
+ export function discoverModules(
87
+ weslSrc: Record<string, string>,
88
+ resolver: ModuleResolver,
89
+ rootModuleName: string,
90
+ packageName = "package",
91
+ ): { weslSrc: Record<string, string>; unbound: string[][] } {
92
+ const tracking = new TrackingResolver(resolver);
93
+ const rootAst = tracking.resolveModule(rootModuleName);
94
+ if (!rootAst) {
95
+ throw new Error(`root module not found: '${rootModuleName}'`);
96
+ }
97
+
98
+ const result = bindIdents({
99
+ rootAst,
100
+ resolver: tracking,
101
+ accumulateUnbound: true,
102
+ discoveryMode: true,
103
+ });
104
+
105
+ const moduleToKey = new Map(
106
+ Object.keys(weslSrc).map(
107
+ key => [fileToModulePath(key, packageName, false), key] as const,
108
+ ),
109
+ );
110
+
111
+ const reachable = [...tracking.visited]
112
+ .map(m => moduleToKey.get(m))
113
+ .filter(key => key !== undefined)
114
+ .map(key => [key, weslSrc[key]] as const);
115
+
116
+ const unbound = (result.unbound ?? []).map(ref => ref.path);
117
+ return { weslSrc: Object.fromEntries(reachable), unbound };
118
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./AbstractElems.ts";
2
2
  export * from "./BindIdents.ts";
3
+ export { filterValidElements } from "./Conditions.ts";
3
4
  export * from "./debug/ASTtoString.ts";
4
5
  export * from "./debug/ScopeToString.ts";
5
6
  export * from "./discovery/FindUnboundIdents.ts";
@@ -0,0 +1,113 @@
1
+ import { expect, test } from "vitest";
2
+ import { discoverModules } from "../discovery/FindUnboundIdents.ts";
3
+ import { RecordResolver } from "../ModuleResolver.ts";
4
+
5
+ test("discoverModules returns only reachable modules", () => {
6
+ const weslSrc: Record<string, string> = {
7
+ "main.wesl": `
8
+ import package::util::helper;
9
+ fn main() { helper(); }
10
+ `,
11
+ "util.wesl": `fn helper() {}`,
12
+ "unused.wesl": `fn unused() {}`,
13
+ };
14
+ const result = discoverModules(
15
+ weslSrc,
16
+ new RecordResolver(weslSrc),
17
+ "package::main",
18
+ );
19
+ const keys = Object.keys(result.weslSrc);
20
+
21
+ expect(keys).toContain("main.wesl");
22
+ expect(keys).toContain("util.wesl");
23
+ expect(keys).not.toContain("unused.wesl");
24
+ });
25
+
26
+ test("discoverModules finds unbound external refs", () => {
27
+ const weslSrc: Record<string, string> = {
28
+ "main.wesl": `
29
+ import ext_pkg::dep;
30
+ fn main() { dep(); }
31
+ `,
32
+ };
33
+ const result = discoverModules(
34
+ weslSrc,
35
+ new RecordResolver(weslSrc),
36
+ "package::main",
37
+ );
38
+ expect(result.unbound).toContainEqual(["ext_pkg", "dep"]);
39
+ });
40
+
41
+ test("discoverModules finds refs in conditional branches", () => {
42
+ const weslSrc: Record<string, string> = {
43
+ "main.wesl": `
44
+ import package::a;
45
+ import package::b;
46
+ fn main() {
47
+ @if(feature) a::run();
48
+ @else b::run();
49
+ }
50
+ `,
51
+ "a.wesl": `
52
+ import ext_a::dep;
53
+ fn run() { dep(); }
54
+ `,
55
+ "b.wesl": `
56
+ import ext_b::dep;
57
+ fn run() { dep(); }
58
+ `,
59
+ "unused.wesl": `fn unused() {}`,
60
+ };
61
+ const result = discoverModules(
62
+ weslSrc,
63
+ new RecordResolver(weslSrc),
64
+ "package::main",
65
+ );
66
+ const keys = Object.keys(result.weslSrc);
67
+
68
+ expect(keys).toContain("a.wesl");
69
+ expect(keys).toContain("b.wesl");
70
+ expect(keys).not.toContain("unused.wesl");
71
+
72
+ expect(result.unbound).toContainEqual(["ext_a", "dep"]);
73
+ expect(result.unbound).toContainEqual(["ext_b", "dep"]);
74
+ });
75
+
76
+ test("discoverModules excludes modules only reachable from other roots", () => {
77
+ const weslSrc: Record<string, string> = {
78
+ "main.wesl": `
79
+ import package::shared;
80
+ fn main() { shared::helper(); }
81
+ `,
82
+ "other.wesl": `
83
+ import package::only_other;
84
+ fn other() { only_other::run(); }
85
+ `,
86
+ "shared.wesl": `fn helper() {}`,
87
+ "only_other.wesl": `fn run() {}`,
88
+ };
89
+ const result = discoverModules(
90
+ weslSrc,
91
+ new RecordResolver(weslSrc),
92
+ "package::main",
93
+ );
94
+ const keys = Object.keys(result.weslSrc);
95
+
96
+ expect(keys).toContain("main.wesl");
97
+ expect(keys).toContain("shared.wesl");
98
+ expect(keys).not.toContain("other.wesl");
99
+ expect(keys).not.toContain("only_other.wesl");
100
+ });
101
+
102
+ test("discoverModules with single-file project", () => {
103
+ const weslSrc: Record<string, string> = {
104
+ "main.wesl": `fn main() { let x = 1; }`,
105
+ };
106
+ const result = discoverModules(
107
+ weslSrc,
108
+ new RecordResolver(weslSrc),
109
+ "package::main",
110
+ );
111
+ expect(Object.keys(result.weslSrc)).toEqual(["main.wesl"]);
112
+ expect(result.unbound).toEqual([]);
113
+ });