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 +36 -2
- package/dist/index.js +299 -279
- package/package.json +1 -1
- package/src/BindIdents.ts +35 -53
- package/src/LowerAndEmit.ts +1 -20
- package/src/Scope.ts +3 -1
- package/src/StandardTypes.ts +19 -0
- package/src/discovery/FindUnboundIdents.ts +66 -8
- package/src/index.ts +1 -0
- package/src/test/DiscoverModules.test.ts +113 -0
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:
|
|
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/
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
|
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
|
|
3706
|
-
const { globalNames, knownDecls } = initRootDecls(
|
|
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(
|
|
3704
|
+
decls: new Map(rootDecls.map((d) => [d.originalName, d])),
|
|
3721
3705
|
parent: null
|
|
3722
3706
|
};
|
|
3723
|
-
const fromRootDecls =
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
3954
|
-
function collectDecls(
|
|
3955
|
-
|
|
3956
|
-
|
|
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)
|
|
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
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 {
|
|
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
|
|
124
|
-
|
|
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(
|
|
134
|
+
const decls = new Map(rootDecls.map(d => [d.originalName, d] as const));
|
|
140
135
|
const liveDecls: LiveDecls = { decls, parent: null };
|
|
141
136
|
|
|
142
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
401
|
-
|
|
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
|
-
|
|
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
|
|
551
|
-
function collectDecls(
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
}
|
package/src/LowerAndEmit.ts
CHANGED
|
@@ -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:
|
|
111
|
+
contents: ScopeItem[];
|
|
110
112
|
|
|
111
113
|
/** Conditional attribute (@if or @else) for this scope */
|
|
112
114
|
condAttribute?: IfAttribute | ElifAttribute | ElseAttribute;
|
package/src/StandardTypes.ts
CHANGED
|
@@ -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
|
|
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
|
|
47
|
-
|
|
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
|
|
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
|
@@ -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
|
+
});
|