wesl 0.7.0 → 0.7.2
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 +8 -11
- package/dist/index.js +82 -36
- package/package.json +1 -1
- package/src/BindIdents.ts +23 -2
- package/src/ClickableError.ts +3 -1
- package/src/LowerAndEmit.ts +55 -30
- package/src/ModulePathUtil.ts +16 -14
- package/src/Scope.ts +3 -0
- package/src/parse/ParseAttribute.ts +2 -0
- package/src/parse/ParsingContext.ts +6 -1
- package/src/test/Linker.test.ts +30 -0
package/dist/index.d.ts
CHANGED
|
@@ -104,6 +104,8 @@ interface RefIdent extends IdentBase {
|
|
|
104
104
|
std?: true;
|
|
105
105
|
/** True for identifiers in @if/@elif conditions. Binding skips these (for now). */
|
|
106
106
|
conditionRef?: true;
|
|
107
|
+
/** Attribute name if this ref is inside an attribute param (for skip-binding check). */
|
|
108
|
+
attrParam?: string;
|
|
107
109
|
ast: WeslAST;
|
|
108
110
|
refIdentElem: RefIdentElem;
|
|
109
111
|
}
|
|
@@ -1003,24 +1005,19 @@ declare function srcLog(src: string | SrcMap, pos: number | [number, number], ..
|
|
|
1003
1005
|
/** WESL module path utilities for converting between :: and / separators. */
|
|
1004
1006
|
/**
|
|
1005
1007
|
* Convert module path segments to relative file path.
|
|
1006
|
-
*
|
|
1008
|
+
* Resolves package:: and super:: via srcModuleParts context.
|
|
1007
1009
|
*
|
|
1008
|
-
* @param parts - module path as array e.g., ["package", "utils", "helper"]
|
|
1009
|
-
* @param packageName - the current package's name (required)
|
|
1010
|
-
* @param srcModuleParts - source module path for super:: resolution (optional)
|
|
1011
1010
|
* @returns relative file path e.g., "utils/helper", or undefined if external
|
|
1012
1011
|
*/
|
|
1013
1012
|
declare function modulePartsToRelativePath(parts: string[], packageName: string, srcModuleParts?: string[]): string | undefined;
|
|
1014
1013
|
/** String variant of modulePartsToRelativePath. */
|
|
1015
1014
|
declare function moduleToRelativePath(modulePath: string, packageName: string, srcModulePath?: string): string | undefined;
|
|
1016
1015
|
/**
|
|
1017
|
-
* Resolve super::
|
|
1018
|
-
*
|
|
1019
|
-
*
|
|
1020
|
-
* @param srcModuleParts - source module path for context
|
|
1021
|
-
* @returns absolute module path parts (no super:: elements)
|
|
1016
|
+
* Resolve package:: and super:: to absolute module path.
|
|
1017
|
+
* - package:: replaced with actual package name from source module
|
|
1018
|
+
* - super:: climbs up the module path hierarchy
|
|
1022
1019
|
*/
|
|
1023
|
-
declare function
|
|
1020
|
+
declare function resolveModulePath(parts: string[], srcModuleParts: string[]): string[];
|
|
1024
1021
|
/** Normalize debug root to end with / or be empty. */
|
|
1025
1022
|
declare function normalizeDebugRoot(root?: string): string;
|
|
1026
1023
|
//#endregion
|
|
@@ -1200,4 +1197,4 @@ declare function offsetToLineNumber(offset: number, text: string): [lineNum: num
|
|
|
1200
1197
|
*/
|
|
1201
1198
|
declare function errorHighlight(source: string, span: Span): [string, string];
|
|
1202
1199
|
//#endregion
|
|
1203
|
-
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, 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, 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, findMap, findRefsToBindingStructs, findUnboundIdents, findValidRootDecls, flatImports, groupBy, grouped, identToString, last, lengthPrefixMangle, link, linkRegistry, liveDeclsToString, log, lowerBindingStructs, makeLiveDecls, makeWeslDevice, mapForward, mapValues, markBindingStructs, markEntryTypes, mergeScope, minimalMangle, minimallyMangledName, modulePartsToRelativePath, moduleToRelativePath, multiKeySet, nextIdentId, noSuffix, normalize, normalizeDebugRoot, normalizeModuleName, npmNameVariations, offsetToLineNumber, overlapTail, parseSrcModule, partition, publicDecl, replaceWords, requestWeslDevice, resetScopeIds,
|
|
1200
|
+
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, 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, 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, findMap, findRefsToBindingStructs, findUnboundIdents, findValidRootDecls, flatImports, groupBy, grouped, identToString, last, lengthPrefixMangle, link, linkRegistry, liveDeclsToString, log, lowerBindingStructs, makeLiveDecls, makeWeslDevice, mapForward, mapValues, markBindingStructs, markEntryTypes, mergeScope, minimalMangle, minimallyMangledName, modulePartsToRelativePath, moduleToRelativePath, multiKeySet, nextIdentId, noSuffix, normalize, normalizeDebugRoot, normalizeModuleName, npmNameVariations, offsetToLineNumber, overlapTail, parseSrcModule, partition, publicDecl, replaceWords, requestWeslDevice, resetScopeIds, resolveModulePath, sanitizePackageName, scan, scopeToString, scopeToStringLong, srcLog, transformBindingReference, transformBindingStruct, underscoreMangle, validation, withLoggerAsync };
|
package/dist/index.js
CHANGED
|
@@ -283,10 +283,11 @@ function encode_integer(num) {
|
|
|
283
283
|
|
|
284
284
|
//#endregion
|
|
285
285
|
//#region src/ClickableError.ts
|
|
286
|
+
const isBrowser = "document" in globalThis;
|
|
286
287
|
/** Throw an error with an embedded source map so that browser users can
|
|
287
288
|
* click on the error in the browser debug console and see the wesl source code. */
|
|
288
289
|
function throwClickableError(params) {
|
|
289
|
-
if (!debug) throw params.error;
|
|
290
|
+
if (!debug || !isBrowser) throw params.error;
|
|
290
291
|
const { url, text, lineNumber, lineColumn, length, error } = params;
|
|
291
292
|
const mappings = encodeVlq([
|
|
292
293
|
0,
|
|
@@ -496,6 +497,25 @@ function childIdent(child) {
|
|
|
496
497
|
|
|
497
498
|
//#endregion
|
|
498
499
|
//#region src/LowerAndEmit.ts
|
|
500
|
+
/** Valid WGSL standard attributes (from spec). Non-WGSL attributes are stripped.
|
|
501
|
+
* See: https://www.w3.org/TR/WGSL/#attributes
|
|
502
|
+
* Note: @builtin, @diagnostic, @interpolate are parsed as separate attribute types. */
|
|
503
|
+
const wgslStandardAttributes$1 = new Set([
|
|
504
|
+
"align",
|
|
505
|
+
"binding",
|
|
506
|
+
"blend_src",
|
|
507
|
+
"compute",
|
|
508
|
+
"const",
|
|
509
|
+
"fragment",
|
|
510
|
+
"group",
|
|
511
|
+
"id",
|
|
512
|
+
"invariant",
|
|
513
|
+
"location",
|
|
514
|
+
"must_use",
|
|
515
|
+
"size",
|
|
516
|
+
"vertex",
|
|
517
|
+
"workgroup_size"
|
|
518
|
+
]);
|
|
499
519
|
/** Traverse the AST, starting from root elements, emitting WGSL for each. */
|
|
500
520
|
function lowerAndEmit(params) {
|
|
501
521
|
const { srcBuilder, rootElems, conditions } = params;
|
|
@@ -505,9 +525,8 @@ function lowerAndEmit(params) {
|
|
|
505
525
|
srcBuilder,
|
|
506
526
|
extracting
|
|
507
527
|
};
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
});
|
|
528
|
+
const validElements = skipConditionalFiltering ? rootElems : filterValidElements(rootElems, conditions);
|
|
529
|
+
for (const e of validElements) lowerAndEmitElem(e, emitContext);
|
|
511
530
|
}
|
|
512
531
|
function lowerAndEmitElem(e, ctx) {
|
|
513
532
|
switch (e.kind) {
|
|
@@ -682,9 +701,8 @@ function emitSynthetic(e, ctx) {
|
|
|
682
701
|
ctx.srcBuilder.addSynthetic(text, text, 0, text.length);
|
|
683
702
|
}
|
|
684
703
|
function emitContents(elem, ctx) {
|
|
685
|
-
filterValidElements(elem.contents, ctx.conditions)
|
|
686
|
-
|
|
687
|
-
});
|
|
704
|
+
const validElements = filterValidElements(elem.contents, ctx.conditions);
|
|
705
|
+
for (const e of validElements) lowerAndEmitElem(e, ctx);
|
|
688
706
|
}
|
|
689
707
|
/** Emit contents with leading/trailing whitespace trimming (V2 parser). */
|
|
690
708
|
function emitContentsWithTrimming(elem, ctx) {
|
|
@@ -777,6 +795,7 @@ function emitAttribute(e, ctx) {
|
|
|
777
795
|
const { kind } = e.attribute;
|
|
778
796
|
if (kind === "@if" || kind === "@elif" || kind === "@else") return false;
|
|
779
797
|
if (kind === "@attribute") {
|
|
798
|
+
if (!wgslStandardAttributes$1.has(e.attribute.name)) return false;
|
|
780
799
|
emitStandardAttribute(e, ctx);
|
|
781
800
|
return true;
|
|
782
801
|
}
|
|
@@ -816,18 +835,25 @@ function diagnosticControlToString(severity, rule) {
|
|
|
816
835
|
}
|
|
817
836
|
function expressionToString(elem) {
|
|
818
837
|
const { kind } = elem;
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
838
|
+
switch (kind) {
|
|
839
|
+
case "binary-expression": {
|
|
840
|
+
const left = expressionToString(elem.left);
|
|
841
|
+
const right = expressionToString(elem.right);
|
|
842
|
+
return `${left} ${elem.operator.value} ${right}`;
|
|
843
|
+
}
|
|
844
|
+
case "unary-expression": return `${elem.operator.value}${expressionToString(elem.expression)}`;
|
|
845
|
+
case "ref": return elem.ident.originalName;
|
|
846
|
+
case "literal": return elem.value;
|
|
847
|
+
case "parenthesized-expression": return `(${expressionToString(elem.expression)})`;
|
|
848
|
+
case "component-expression": return `${expressionToString(elem.base)}[${elem.access}]`;
|
|
849
|
+
case "component-member-expression": return `${expressionToString(elem.base)}.${elem.access}`;
|
|
850
|
+
case "call-expression": {
|
|
851
|
+
const fn = elem.function;
|
|
852
|
+
return `${fn.kind === "ref" ? fn.ident.originalName : fn.name.originalName}${elem.templateArgs ? `<...>` : ""}(${elem.arguments.map(expressionToString).join(", ")})`;
|
|
853
|
+
}
|
|
854
|
+
case "type": return elem.name.originalName;
|
|
855
|
+
default: assertUnreachable(kind);
|
|
856
|
+
}
|
|
831
857
|
}
|
|
832
858
|
function emitDirective(e, ctx) {
|
|
833
859
|
const { directive } = e;
|
|
@@ -1197,15 +1223,12 @@ function minimallyMangledName(proposedName, globalNames) {
|
|
|
1197
1223
|
/** WESL module path utilities for converting between :: and / separators. */
|
|
1198
1224
|
/**
|
|
1199
1225
|
* Convert module path segments to relative file path.
|
|
1200
|
-
*
|
|
1226
|
+
* Resolves package:: and super:: via srcModuleParts context.
|
|
1201
1227
|
*
|
|
1202
|
-
* @param parts - module path as array e.g., ["package", "utils", "helper"]
|
|
1203
|
-
* @param packageName - the current package's name (required)
|
|
1204
|
-
* @param srcModuleParts - source module path for super:: resolution (optional)
|
|
1205
1228
|
* @returns relative file path e.g., "utils/helper", or undefined if external
|
|
1206
1229
|
*/
|
|
1207
1230
|
function modulePartsToRelativePath(parts, packageName, srcModuleParts) {
|
|
1208
|
-
const resolved = srcModuleParts ?
|
|
1231
|
+
const resolved = srcModuleParts ? resolveModulePath(parts, srcModuleParts) : parts;
|
|
1209
1232
|
const rootSegment = resolved[0];
|
|
1210
1233
|
if (rootSegment === "package" || rootSegment === packageName) return resolved.slice(1).join("/");
|
|
1211
1234
|
}
|
|
@@ -1215,16 +1238,15 @@ function moduleToRelativePath(modulePath, packageName, srcModulePath) {
|
|
|
1215
1238
|
return modulePartsToRelativePath(modulePath.split("::"), packageName, srcParts);
|
|
1216
1239
|
}
|
|
1217
1240
|
/**
|
|
1218
|
-
* Resolve super::
|
|
1219
|
-
*
|
|
1220
|
-
*
|
|
1221
|
-
* @param srcModuleParts - source module path for context
|
|
1222
|
-
* @returns absolute module path parts (no super:: elements)
|
|
1241
|
+
* Resolve package:: and super:: to absolute module path.
|
|
1242
|
+
* - package:: replaced with actual package name from source module
|
|
1243
|
+
* - super:: climbs up the module path hierarchy
|
|
1223
1244
|
*/
|
|
1224
|
-
function
|
|
1225
|
-
const
|
|
1226
|
-
|
|
1227
|
-
|
|
1245
|
+
function resolveModulePath(parts, srcModuleParts) {
|
|
1246
|
+
const resolved = parts[0] === "package" ? [srcModuleParts[0], ...parts.slice(1)] : parts;
|
|
1247
|
+
const lastSuper = resolved.lastIndexOf("super");
|
|
1248
|
+
if (lastSuper === -1) return resolved;
|
|
1249
|
+
return [...srcModuleParts.slice(0, -(lastSuper + 1)), ...resolved.slice(lastSuper + 1)];
|
|
1228
1250
|
}
|
|
1229
1251
|
/** Normalize debug root to end with / or be empty. */
|
|
1230
1252
|
function normalizeDebugRoot(root) {
|
|
@@ -1992,7 +2014,9 @@ function parseStandardAttribute(ctx) {
|
|
|
1992
2014
|
if (name === "diagnostic") return parseDiagnosticAttribute(ctx, startPos);
|
|
1993
2015
|
let params;
|
|
1994
2016
|
if (stream.matchText("(")) {
|
|
2017
|
+
ctx.parsingAttrParam = name;
|
|
1995
2018
|
params = parseAttributeParams(ctx);
|
|
2019
|
+
ctx.parsingAttrParam = void 0;
|
|
1996
2020
|
expect(stream, ")", "attribute parameters");
|
|
1997
2021
|
}
|
|
1998
2022
|
if (name === "must_use" && params !== void 0) throw new ParseError("@must_use does not accept parameters", [startPos, stream.checkpoint()]);
|
|
@@ -3097,14 +3121,18 @@ var ParsingContext = class {
|
|
|
3097
3121
|
while (scope.kind === "partial" && scope.parent) scope = scope.parent;
|
|
3098
3122
|
return scope.parent === null;
|
|
3099
3123
|
}
|
|
3124
|
+
/** Attribute name being parsed (for marking refs in attr params). */
|
|
3125
|
+
parsingAttrParam;
|
|
3100
3126
|
createRefIdent(name) {
|
|
3101
|
-
|
|
3127
|
+
const ref = {
|
|
3102
3128
|
kind: "ref",
|
|
3103
3129
|
originalName: name,
|
|
3104
3130
|
ast: this.state.stable,
|
|
3105
3131
|
id: nextIdentId(),
|
|
3106
3132
|
refIdentElem: null
|
|
3107
3133
|
};
|
|
3134
|
+
if (this.parsingAttrParam) ref.attrParam = this.parsingAttrParam;
|
|
3135
|
+
return ref;
|
|
3108
3136
|
}
|
|
3109
3137
|
createDeclIdent(name, isGlobal = false) {
|
|
3110
3138
|
return {
|
|
@@ -3635,6 +3663,23 @@ function stdEnumerant(name) {
|
|
|
3635
3663
|
|
|
3636
3664
|
//#endregion
|
|
3637
3665
|
//#region src/BindIdents.ts
|
|
3666
|
+
/** WGSL standard attributes whose params need binding (e.g., @workgroup_size). */
|
|
3667
|
+
const wgslStandardAttributes = new Set([
|
|
3668
|
+
"align",
|
|
3669
|
+
"binding",
|
|
3670
|
+
"blend_src",
|
|
3671
|
+
"compute",
|
|
3672
|
+
"const",
|
|
3673
|
+
"fragment",
|
|
3674
|
+
"group",
|
|
3675
|
+
"id",
|
|
3676
|
+
"invariant",
|
|
3677
|
+
"location",
|
|
3678
|
+
"must_use",
|
|
3679
|
+
"size",
|
|
3680
|
+
"vertex",
|
|
3681
|
+
"workgroup_size"
|
|
3682
|
+
]);
|
|
3638
3683
|
/** Bind ref idents to declarations and mangle global declaration names. */
|
|
3639
3684
|
function bindIdents(params) {
|
|
3640
3685
|
const { rootAst, resolver, virtuals, accumulateUnbound } = params;
|
|
@@ -3750,6 +3795,7 @@ function processDependentScope(decl, ctx) {
|
|
|
3750
3795
|
function handleRef(ident$1, liveDecls, bindContext) {
|
|
3751
3796
|
if (ident$1.refersTo || ident$1.std) return;
|
|
3752
3797
|
if (ident$1.conditionRef) return;
|
|
3798
|
+
if (ident$1.attrParam && !wgslStandardAttributes.has(ident$1.attrParam)) return;
|
|
3753
3799
|
const foundDecl = findDeclInModule(ident$1, liveDecls) ?? findQualifiedImport(ident$1, bindContext);
|
|
3754
3800
|
if (foundDecl) {
|
|
3755
3801
|
ident$1.refersTo = foundDecl.decl;
|
|
@@ -3811,7 +3857,7 @@ function matchingImport(identParts, imports) {
|
|
|
3811
3857
|
/** @return an exported root declIdent for the provided path. */
|
|
3812
3858
|
function findExport(pathParts, srcModule, ctx) {
|
|
3813
3859
|
const { resolver, conditions, virtuals } = ctx;
|
|
3814
|
-
const modulePath =
|
|
3860
|
+
const modulePath = resolveModulePath(pathParts, srcModule.modulePath.split("::")).slice(0, -1).join("::");
|
|
3815
3861
|
const moduleAst = resolver.resolveModule(modulePath) ?? virtualModule(pathParts[0], conditions, virtuals);
|
|
3816
3862
|
if (!moduleAst) return void 0;
|
|
3817
3863
|
const decl = publicDecl(moduleAst.rootScope, last(pathParts), conditions);
|
|
@@ -4883,4 +4929,4 @@ function makeWeslDevice(device) {
|
|
|
4883
4929
|
}
|
|
4884
4930
|
|
|
4885
4931
|
//#endregion
|
|
4886
|
-
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, findMap, findRefsToBindingStructs, findUnboundIdents, findValidRootDecls, flatImports, groupBy, grouped, identToString, last, lengthPrefixMangle, link, linkRegistry, liveDeclsToString, log, lowerBindingStructs, makeLiveDecls, makeWeslDevice, mapForward, mapValues, markBindingStructs, markEntryTypes, mergeScope, minimalMangle, minimallyMangledName, modulePartsToRelativePath, moduleToRelativePath, multiKeySet, nextIdentId, noSuffix, normalize, normalizeDebugRoot, normalizeModuleName, npmNameVariations, offsetToLineNumber, overlapTail, parseSrcModule, partition, publicDecl, replaceWords, requestWeslDevice, resetScopeIds,
|
|
4932
|
+
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, findMap, findRefsToBindingStructs, findUnboundIdents, findValidRootDecls, flatImports, groupBy, grouped, identToString, last, lengthPrefixMangle, link, linkRegistry, liveDeclsToString, log, lowerBindingStructs, makeLiveDecls, makeWeslDevice, mapForward, mapValues, markBindingStructs, markEntryTypes, mergeScope, minimalMangle, minimallyMangledName, modulePartsToRelativePath, moduleToRelativePath, multiKeySet, nextIdentId, noSuffix, normalize, normalizeDebugRoot, normalizeModuleName, npmNameVariations, offsetToLineNumber, overlapTail, parseSrcModule, partition, publicDecl, replaceWords, requestWeslDevice, resetScopeIds, resolveModulePath, sanitizePackageName, scan, scopeToString, scopeToStringLong, srcLog, transformBindingReference, transformBindingStruct, underscoreMangle, validation, withLoggerAsync };
|
package/package.json
CHANGED
package/src/BindIdents.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { FlatImport } from "./FlattenTreeImport.ts";
|
|
|
7
7
|
import type { LinkRegistryParams, VirtualLibraryFn } from "./Linker.ts";
|
|
8
8
|
import { type LiveDecls, makeLiveDecls } from "./LiveDeclarations.ts";
|
|
9
9
|
import { type ManglerFn, minimalMangle } from "./Mangler.ts";
|
|
10
|
-
import {
|
|
10
|
+
import { resolveModulePath } from "./ModulePathUtil.ts";
|
|
11
11
|
import type { ModuleResolver } from "./ModuleResolver.ts";
|
|
12
12
|
import { flatImports, parseSrcModule, type WeslAST } from "./ParseWESL.ts";
|
|
13
13
|
import type {
|
|
@@ -21,6 +21,24 @@ import type {
|
|
|
21
21
|
import { stdEnumerant, stdFn, stdType } from "./StandardTypes.ts";
|
|
22
22
|
import { last } from "./Util.ts";
|
|
23
23
|
|
|
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
|
+
|
|
24
42
|
/**
|
|
25
43
|
* BindIdents pass: link reference identifiers to declarations.
|
|
26
44
|
*
|
|
@@ -281,6 +299,9 @@ function handleRef(
|
|
|
281
299
|
// Skip binding for condition refs - they resolve via Conditions map (for now)
|
|
282
300
|
if (ident.conditionRef) return;
|
|
283
301
|
|
|
302
|
+
// Skip binding for refs in non-WGSL attribute params (e.g., @test(description))
|
|
303
|
+
if (ident.attrParam && !wgslStandardAttributes.has(ident.attrParam)) return;
|
|
304
|
+
|
|
284
305
|
const foundDecl =
|
|
285
306
|
findDeclInModule(ident, liveDecls) ??
|
|
286
307
|
findQualifiedImport(ident, bindContext);
|
|
@@ -385,7 +406,7 @@ function findExport(
|
|
|
385
406
|
): FoundDecl | undefined {
|
|
386
407
|
const { resolver, conditions, virtuals } = ctx;
|
|
387
408
|
const srcParts = srcModule.modulePath.split("::");
|
|
388
|
-
const fqParts =
|
|
409
|
+
const fqParts = resolveModulePath(pathParts, srcParts);
|
|
389
410
|
const modulePath = fqParts.slice(0, -1).join("::");
|
|
390
411
|
|
|
391
412
|
const moduleAst =
|
package/src/ClickableError.ts
CHANGED
|
@@ -23,10 +23,12 @@ export interface ClickableErrorParams {
|
|
|
23
23
|
error: Error;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
const isBrowser = "document" in globalThis;
|
|
27
|
+
|
|
26
28
|
/** Throw an error with an embedded source map so that browser users can
|
|
27
29
|
* click on the error in the browser debug console and see the wesl source code. */
|
|
28
30
|
export function throwClickableError(params: ClickableErrorParams): void {
|
|
29
|
-
if (!debug) throw params.error;
|
|
31
|
+
if (!debug || !isBrowser) throw params.error;
|
|
30
32
|
const { url, text, lineNumber, lineColumn, length, error } = params;
|
|
31
33
|
|
|
32
34
|
// Based on https://stackoverflow.com/questions/65274147/sourceurl-for-css
|
package/src/LowerAndEmit.ts
CHANGED
|
@@ -36,6 +36,26 @@ interface EmitContext {
|
|
|
36
36
|
extracting: boolean;
|
|
37
37
|
}
|
|
38
38
|
|
|
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
|
+
|
|
39
59
|
/** Traverse the AST, starting from root elements, emitting WGSL for each. */
|
|
40
60
|
export function lowerAndEmit(params: EmitParams): void {
|
|
41
61
|
const { srcBuilder, rootElems, conditions } = params;
|
|
@@ -45,9 +65,7 @@ export function lowerAndEmit(params: EmitParams): void {
|
|
|
45
65
|
const validElements = skipConditionalFiltering
|
|
46
66
|
? rootElems
|
|
47
67
|
: filterValidElements(rootElems, conditions);
|
|
48
|
-
validElements
|
|
49
|
-
lowerAndEmitElem(e, emitContext);
|
|
50
|
-
});
|
|
68
|
+
for (const e of validElements) lowerAndEmitElem(e, emitContext);
|
|
51
69
|
}
|
|
52
70
|
|
|
53
71
|
function lowerAndEmitElem(e: AbstractElem, ctx: EmitContext): void {
|
|
@@ -303,9 +321,7 @@ function emitSynthetic(e: SyntheticElem, ctx: EmitContext): void {
|
|
|
303
321
|
|
|
304
322
|
function emitContents(elem: ContainerElem, ctx: EmitContext): void {
|
|
305
323
|
const validElements = filterValidElements(elem.contents, ctx.conditions);
|
|
306
|
-
validElements
|
|
307
|
-
lowerAndEmitElem(e, ctx);
|
|
308
|
-
});
|
|
324
|
+
for (const e of validElements) lowerAndEmitElem(e, ctx);
|
|
309
325
|
}
|
|
310
326
|
|
|
311
327
|
/** Emit contents with leading/trailing whitespace trimming (V2 parser). */
|
|
@@ -444,6 +460,9 @@ function emitAttribute(e: AttributeElem, ctx: EmitContext): boolean {
|
|
|
444
460
|
}
|
|
445
461
|
|
|
446
462
|
if (kind === "@attribute") {
|
|
463
|
+
if (!wgslStandardAttributes.has(e.attribute.name)) {
|
|
464
|
+
return false; // non-WGSL attribute, dropped from output
|
|
465
|
+
}
|
|
447
466
|
emitStandardAttribute(e, ctx);
|
|
448
467
|
return true;
|
|
449
468
|
}
|
|
@@ -503,30 +522,36 @@ export function diagnosticControlToString(
|
|
|
503
522
|
|
|
504
523
|
export function expressionToString(elem: ExpressionElem): string {
|
|
505
524
|
const { kind } = elem;
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
525
|
+
switch (kind) {
|
|
526
|
+
case "binary-expression": {
|
|
527
|
+
const left = expressionToString(elem.left);
|
|
528
|
+
const right = expressionToString(elem.right);
|
|
529
|
+
return `${left} ${elem.operator.value} ${right}`;
|
|
530
|
+
}
|
|
531
|
+
case "unary-expression":
|
|
532
|
+
return `${elem.operator.value}${expressionToString(elem.expression)}`;
|
|
533
|
+
case "ref":
|
|
534
|
+
return elem.ident.originalName;
|
|
535
|
+
case "literal":
|
|
536
|
+
return elem.value;
|
|
537
|
+
case "parenthesized-expression":
|
|
538
|
+
return `(${expressionToString(elem.expression)})`;
|
|
539
|
+
case "component-expression":
|
|
540
|
+
return `${expressionToString(elem.base)}[${elem.access}]`;
|
|
541
|
+
case "component-member-expression":
|
|
542
|
+
return `${expressionToString(elem.base)}.${elem.access}`;
|
|
543
|
+
case "call-expression": {
|
|
544
|
+
const fn = elem.function;
|
|
545
|
+
const name =
|
|
546
|
+
fn.kind === "ref" ? fn.ident.originalName : fn.name.originalName;
|
|
547
|
+
const targs = elem.templateArgs ? `<...>` : "";
|
|
548
|
+
const args = elem.arguments.map(expressionToString).join(", ");
|
|
549
|
+
return `${name}${targs}(${args})`;
|
|
550
|
+
}
|
|
551
|
+
case "type":
|
|
552
|
+
return elem.name.originalName;
|
|
553
|
+
default:
|
|
554
|
+
assertUnreachable(kind);
|
|
530
555
|
}
|
|
531
556
|
}
|
|
532
557
|
|
package/src/ModulePathUtil.ts
CHANGED
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Convert module path segments to relative file path.
|
|
5
|
-
*
|
|
5
|
+
* Resolves package:: and super:: via srcModuleParts context.
|
|
6
6
|
*
|
|
7
|
-
* @param parts - module path as array e.g., ["package", "utils", "helper"]
|
|
8
|
-
* @param packageName - the current package's name (required)
|
|
9
|
-
* @param srcModuleParts - source module path for super:: resolution (optional)
|
|
10
7
|
* @returns relative file path e.g., "utils/helper", or undefined if external
|
|
11
8
|
*/
|
|
12
9
|
export function modulePartsToRelativePath(
|
|
@@ -14,7 +11,9 @@ export function modulePartsToRelativePath(
|
|
|
14
11
|
packageName: string,
|
|
15
12
|
srcModuleParts?: string[],
|
|
16
13
|
): string | undefined {
|
|
17
|
-
const resolved = srcModuleParts
|
|
14
|
+
const resolved = srcModuleParts
|
|
15
|
+
? resolveModulePath(parts, srcModuleParts)
|
|
16
|
+
: parts;
|
|
18
17
|
|
|
19
18
|
const rootSegment = resolved[0];
|
|
20
19
|
if (rootSegment === "package" || rootSegment === packageName) {
|
|
@@ -35,20 +34,23 @@ export function moduleToRelativePath(
|
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
/**
|
|
38
|
-
* Resolve super::
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* @param srcModuleParts - source module path for context
|
|
42
|
-
* @returns absolute module path parts (no super:: elements)
|
|
37
|
+
* Resolve package:: and super:: to absolute module path.
|
|
38
|
+
* - package:: replaced with actual package name from source module
|
|
39
|
+
* - super:: climbs up the module path hierarchy
|
|
43
40
|
*/
|
|
44
|
-
export function
|
|
41
|
+
export function resolveModulePath(
|
|
45
42
|
parts: string[],
|
|
46
43
|
srcModuleParts: string[],
|
|
47
44
|
): string[] {
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
// Handle package:: - replace with actual package name from source module
|
|
46
|
+
const resolved =
|
|
47
|
+
parts[0] === "package" ? [srcModuleParts[0], ...parts.slice(1)] : parts;
|
|
48
|
+
|
|
49
|
+
// Handle super:: - climb up the module path
|
|
50
|
+
const lastSuper = resolved.lastIndexOf("super");
|
|
51
|
+
if (lastSuper === -1) return resolved;
|
|
50
52
|
const base = srcModuleParts.slice(0, -(lastSuper + 1));
|
|
51
|
-
return [...base, ...
|
|
53
|
+
return [...base, ...resolved.slice(lastSuper + 1)];
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
/** Normalize debug root to end with / or be empty. */
|
package/src/Scope.ts
CHANGED
|
@@ -41,6 +41,9 @@ export interface RefIdent extends IdentBase {
|
|
|
41
41
|
/** True for identifiers in @if/@elif conditions. Binding skips these (for now). */
|
|
42
42
|
conditionRef?: true;
|
|
43
43
|
|
|
44
|
+
/** Attribute name if this ref is inside an attribute param (for skip-binding check). */
|
|
45
|
+
attrParam?: string;
|
|
46
|
+
|
|
44
47
|
// LATER consider tracking the current ast in BindIdents so that this field is unnecessary
|
|
45
48
|
ast: WeslAST; // AST from module that contains this ident (to find imports during decl binding)
|
|
46
49
|
|
|
@@ -154,7 +154,9 @@ function parseStandardAttribute(ctx: ParsingContext): AttributeElem | null {
|
|
|
154
154
|
|
|
155
155
|
let params: UnknownExpressionElem[] | undefined;
|
|
156
156
|
if (stream.matchText("(")) {
|
|
157
|
+
ctx.parsingAttrParam = name;
|
|
157
158
|
params = parseAttributeParams(ctx);
|
|
159
|
+
ctx.parsingAttrParam = undefined;
|
|
158
160
|
expect(stream, ")", "attribute parameters");
|
|
159
161
|
}
|
|
160
162
|
|
|
@@ -65,14 +65,19 @@ export class ParsingContext {
|
|
|
65
65
|
return scope.parent === null;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/** Attribute name being parsed (for marking refs in attr params). */
|
|
69
|
+
parsingAttrParam?: string;
|
|
70
|
+
|
|
68
71
|
createRefIdent(name: string): RefIdent {
|
|
69
|
-
|
|
72
|
+
const ref: RefIdent = {
|
|
70
73
|
kind: "ref",
|
|
71
74
|
originalName: name,
|
|
72
75
|
ast: this.state.stable,
|
|
73
76
|
id: nextIdentId(),
|
|
74
77
|
refIdentElem: null as any, // linked by caller
|
|
75
78
|
};
|
|
79
|
+
if (this.parsingAttrParam) ref.attrParam = this.parsingAttrParam;
|
|
80
|
+
return ref;
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
createDeclIdent(name: string, isGlobal = false): DeclIdent {
|
package/src/test/Linker.test.ts
CHANGED
|
@@ -20,6 +20,36 @@ test("@diagnostic attribute on statement", async () => {
|
|
|
20
20
|
expectTokenMatch(result, src);
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
+
test("non-WGSL attribute @test stripped from output", async () => {
|
|
24
|
+
const src = `
|
|
25
|
+
@test
|
|
26
|
+
fn myTest() { }
|
|
27
|
+
`;
|
|
28
|
+
const expected = `fn myTest() { }`;
|
|
29
|
+
const result = await linkTest(src);
|
|
30
|
+
expectTokenMatch(result, expected);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("non-WGSL attribute @test with params stripped", async () => {
|
|
34
|
+
const src = `
|
|
35
|
+
@test(my_test_name)
|
|
36
|
+
fn myTest() { }
|
|
37
|
+
`;
|
|
38
|
+
const expected = `fn myTest() { }`;
|
|
39
|
+
const result = await linkTest(src);
|
|
40
|
+
expectTokenMatch(result, expected);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("non-WGSL attribute @custom stripped from output", async () => {
|
|
44
|
+
const src = `
|
|
45
|
+
@custom
|
|
46
|
+
fn myFn() { }
|
|
47
|
+
`;
|
|
48
|
+
const expected = `fn myFn() { }`;
|
|
49
|
+
const result = await linkTest(src);
|
|
50
|
+
expectTokenMatch(result, expected);
|
|
51
|
+
});
|
|
52
|
+
|
|
23
53
|
test("link an alias", async () => {
|
|
24
54
|
const src = /* wgsl */ `
|
|
25
55
|
alias Num = f32;
|