wesl 0.7.14 → 0.7.15
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 +111 -77
- package/dist/index.js +43 -20
- package/package.json +2 -2
- package/src/BindIdents.ts +29 -5
- package/src/Logging.ts +2 -2
- package/src/ParseWESL.ts +8 -2
- package/src/SrcMap.ts +2 -1
- package/src/Util.ts +2 -4
- package/src/discovery/FindUnboundIdents.ts +7 -1
- package/src/index.ts +1 -0
- package/src/parse/ParseLoop.ts +6 -3
- package/src/parse/ParseSimpleStatement.ts +3 -1
- package/src/parse/ParseUtil.ts +1 -0
- package/src/parse/ParseWesl.ts +11 -5
- package/src/parse/ParsingContext.ts +13 -1
- package/src/test/BindWESLV2.test.ts +3 -2
- package/src/test/ParseErrorV2.test.ts +1 -2
- package/src/test/SrcMap.test.ts +69 -0
package/dist/index.d.ts
CHANGED
|
@@ -27,6 +27,85 @@ declare class ParseError extends Error {
|
|
|
27
27
|
constructor(msg: string, span: Span);
|
|
28
28
|
}
|
|
29
29
|
//#endregion
|
|
30
|
+
//#region src/Stream.d.ts
|
|
31
|
+
/**
|
|
32
|
+
* Interface for a tokenizer. Returns a "next token", and can be reset to
|
|
33
|
+
* previously saved positions (checkpoints).
|
|
34
|
+
*/
|
|
35
|
+
interface Stream<T extends Token> {
|
|
36
|
+
/** Returns the current position */
|
|
37
|
+
checkpoint(): number;
|
|
38
|
+
/** Restores a position */
|
|
39
|
+
reset(position: number): void;
|
|
40
|
+
/**
|
|
41
|
+
* Returns the next token, or `null` if the end of the stream has been reached.
|
|
42
|
+
* Always leaves `checkpoint` right after the token.
|
|
43
|
+
*/
|
|
44
|
+
nextToken(): T | null;
|
|
45
|
+
/** src text */
|
|
46
|
+
src: string;
|
|
47
|
+
}
|
|
48
|
+
/** A text token */
|
|
49
|
+
interface Token {
|
|
50
|
+
kind: string;
|
|
51
|
+
text: string;
|
|
52
|
+
span: Span;
|
|
53
|
+
}
|
|
54
|
+
interface TypedToken<Kind extends string> extends Token {
|
|
55
|
+
kind: Kind;
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/parse/WeslStream.d.ts
|
|
59
|
+
type WeslTokenKind = "word" | "keyword" | "number" | "symbol";
|
|
60
|
+
type WeslToken<Kind extends WeslTokenKind = WeslTokenKind> = TypedToken<Kind>;
|
|
61
|
+
/** A stream that produces WESL tokens, skipping over comments and white space */
|
|
62
|
+
declare class WeslStream implements Stream<WeslToken> {
|
|
63
|
+
private stream;
|
|
64
|
+
/** New line */
|
|
65
|
+
private eolPattern;
|
|
66
|
+
private blockCommentPattern;
|
|
67
|
+
src: string;
|
|
68
|
+
constructor(src: string);
|
|
69
|
+
checkpoint(): number;
|
|
70
|
+
reset(position: number): void;
|
|
71
|
+
nextToken(): WeslToken | null;
|
|
72
|
+
/** Peek at the next token without consuming it */
|
|
73
|
+
peek(): WeslToken | null;
|
|
74
|
+
/** Consume token if text matches, otherwise leave position unchanged */
|
|
75
|
+
matchText(text: string): WeslToken | null;
|
|
76
|
+
/** Consume token if kind matches (and optionally text), otherwise leave position unchanged */
|
|
77
|
+
matchKind<K extends WeslTokenKind>(kind: K, text?: string): WeslToken<K> | null;
|
|
78
|
+
/** Consume token if predicate matches, otherwise leave position unchanged */
|
|
79
|
+
nextIf(predicate: (token: WeslToken) => boolean): WeslToken | null;
|
|
80
|
+
/** Match a sequence of tokens by text. Resets and returns null if any fails. */
|
|
81
|
+
matchSequence(...texts: string[]): WeslToken[] | null;
|
|
82
|
+
private skipToEol;
|
|
83
|
+
private skipBlockComment;
|
|
84
|
+
/**
|
|
85
|
+
* Only matches the `<` token if it is a template
|
|
86
|
+
* Precondition: An ident was parsed right before this.
|
|
87
|
+
* Runs the [template list discovery algorithm](https://www.w3.org/TR/WGSL/#template-list-discovery).
|
|
88
|
+
*/
|
|
89
|
+
nextTemplateStartToken(): (WeslToken & {
|
|
90
|
+
kind: "symbol";
|
|
91
|
+
}) | null;
|
|
92
|
+
nextTemplateEndToken(): (WeslToken & {
|
|
93
|
+
kind: "symbol";
|
|
94
|
+
}) | null;
|
|
95
|
+
private isTemplateStart;
|
|
96
|
+
/**
|
|
97
|
+
* Call this after consuming an opening bracket.
|
|
98
|
+
* Skips until a closing bracket. This also consumes the closing bracket.
|
|
99
|
+
*/
|
|
100
|
+
private skipBracketsTo;
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/parse/ParsingContext.d.ts
|
|
104
|
+
interface ParseOptions {
|
|
105
|
+
/** Store expression AST nodes in statement contents (for tooling/validation). */
|
|
106
|
+
preserveExpressions?: boolean;
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
30
109
|
//#region src/ParseWESL.d.ts
|
|
31
110
|
/** Partial element being constructed during parsing. */
|
|
32
111
|
type OpenElem<T extends ContainerElem = ContainerElem> = Pick<T, "kind" | "contents">;
|
|
@@ -77,7 +156,7 @@ declare class WeslParseError extends Error {
|
|
|
77
156
|
});
|
|
78
157
|
}
|
|
79
158
|
/** Parse a WESL file. */
|
|
80
|
-
declare function parseSrcModule(srcModule: SrcModule): WeslAST;
|
|
159
|
+
declare function parseSrcModule(srcModule: SrcModule, options?: ParseOptions): WeslAST;
|
|
81
160
|
/** @return flattened form of import tree for binding idents. */
|
|
82
161
|
declare function flatImports(ast: BindingAST, conditions?: Conditions): FlatImport[];
|
|
83
162
|
//#endregion
|
|
@@ -860,8 +939,19 @@ interface BindResults {
|
|
|
860
939
|
decls: DeclIdent[];
|
|
861
940
|
/** Additional global statements to emit (e.g., const_assert). */
|
|
862
941
|
newStatements: EmittableElem[];
|
|
863
|
-
/** Unbound
|
|
864
|
-
unbound?:
|
|
942
|
+
/** Unbound identifiers with position info (only if accumulateUnbound is true). */
|
|
943
|
+
unbound?: UnboundRef[];
|
|
944
|
+
}
|
|
945
|
+
/** An unresolved reference with position info for error reporting. */
|
|
946
|
+
interface UnboundRef {
|
|
947
|
+
/** Module path that couldn't be resolved (e.g., ["package", "foo", "bar"]). */
|
|
948
|
+
path: string[];
|
|
949
|
+
/** Source module containing this reference. */
|
|
950
|
+
srcModule: SrcModule;
|
|
951
|
+
/** Start offset in the source. */
|
|
952
|
+
start: number;
|
|
953
|
+
/** End offset in the source. */
|
|
954
|
+
end: number;
|
|
865
955
|
}
|
|
866
956
|
/** An element that can be directly emitted into the linked result. */
|
|
867
957
|
interface EmittableElem {
|
|
@@ -905,7 +995,7 @@ interface BindContext {
|
|
|
905
995
|
mangler: ManglerFn;
|
|
906
996
|
virtuals?: VirtualLibrarySet;
|
|
907
997
|
/** Unbound identifiers if accumulateUnbound is true. */
|
|
908
|
-
unbound?:
|
|
998
|
+
unbound?: UnboundRef[];
|
|
909
999
|
/** Don't follow references from declarations (for library dependency detection). */
|
|
910
1000
|
dontFollowDecls?: boolean;
|
|
911
1001
|
}
|
|
@@ -941,6 +1031,8 @@ declare function identToString(ident?: Ident): string;
|
|
|
941
1031
|
* (e.g., [['foo', 'bar', 'baz'], ['other', 'pkg']])
|
|
942
1032
|
*/
|
|
943
1033
|
declare function findUnboundIdents(resolver: BatchModuleResolver): string[][];
|
|
1034
|
+
/** Find unbound references with full position info. */
|
|
1035
|
+
declare function findUnboundRefs(resolver: BatchModuleResolver): UnboundRef[];
|
|
944
1036
|
//#endregion
|
|
945
1037
|
//#region src/discovery/PackageNameUtils.d.ts
|
|
946
1038
|
/** Package name sanitization for WESL.
|
|
@@ -1029,78 +1121,20 @@ declare function normalize(path: string): string;
|
|
|
1029
1121
|
* e.g. /foo/bar.wgsl => /foo/bar */
|
|
1030
1122
|
declare function noSuffix(path: string): string;
|
|
1031
1123
|
//#endregion
|
|
1032
|
-
//#region src/
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
nextToken(): T | null;
|
|
1047
|
-
/** src text */
|
|
1048
|
-
src: string;
|
|
1049
|
-
}
|
|
1050
|
-
/** A text token */
|
|
1051
|
-
interface Token {
|
|
1052
|
-
kind: string;
|
|
1053
|
-
text: string;
|
|
1054
|
-
span: Span;
|
|
1055
|
-
}
|
|
1056
|
-
interface TypedToken<Kind extends string> extends Token {
|
|
1057
|
-
kind: Kind;
|
|
1058
|
-
}
|
|
1059
|
-
//#endregion
|
|
1060
|
-
//#region src/parse/WeslStream.d.ts
|
|
1061
|
-
type WeslTokenKind = "word" | "keyword" | "number" | "symbol";
|
|
1062
|
-
type WeslToken<Kind extends WeslTokenKind = WeslTokenKind> = TypedToken<Kind>;
|
|
1063
|
-
/** A stream that produces WESL tokens, skipping over comments and white space */
|
|
1064
|
-
declare class WeslStream implements Stream<WeslToken> {
|
|
1065
|
-
private stream;
|
|
1066
|
-
/** New line */
|
|
1067
|
-
private eolPattern;
|
|
1068
|
-
private blockCommentPattern;
|
|
1069
|
-
src: string;
|
|
1070
|
-
constructor(src: string);
|
|
1071
|
-
checkpoint(): number;
|
|
1072
|
-
reset(position: number): void;
|
|
1073
|
-
nextToken(): WeslToken | null;
|
|
1074
|
-
/** Peek at the next token without consuming it */
|
|
1075
|
-
peek(): WeslToken | null;
|
|
1076
|
-
/** Consume token if text matches, otherwise leave position unchanged */
|
|
1077
|
-
matchText(text: string): WeslToken | null;
|
|
1078
|
-
/** Consume token if kind matches (and optionally text), otherwise leave position unchanged */
|
|
1079
|
-
matchKind<K extends WeslTokenKind>(kind: K, text?: string): WeslToken<K> | null;
|
|
1080
|
-
/** Consume token if predicate matches, otherwise leave position unchanged */
|
|
1081
|
-
nextIf(predicate: (token: WeslToken) => boolean): WeslToken | null;
|
|
1082
|
-
/** Match a sequence of tokens by text. Resets and returns null if any fails. */
|
|
1083
|
-
matchSequence(...texts: string[]): WeslToken[] | null;
|
|
1084
|
-
private skipToEol;
|
|
1085
|
-
private skipBlockComment;
|
|
1086
|
-
/**
|
|
1087
|
-
* Only matches the `<` token if it is a template
|
|
1088
|
-
* Precondition: An ident was parsed right before this.
|
|
1089
|
-
* Runs the [template list discovery algorithm](https://www.w3.org/TR/WGSL/#template-list-discovery).
|
|
1090
|
-
*/
|
|
1091
|
-
nextTemplateStartToken(): (WeslToken & {
|
|
1092
|
-
kind: "symbol";
|
|
1093
|
-
}) | null;
|
|
1094
|
-
nextTemplateEndToken(): (WeslToken & {
|
|
1095
|
-
kind: "symbol";
|
|
1096
|
-
}) | null;
|
|
1097
|
-
private isTemplateStart;
|
|
1098
|
-
/**
|
|
1099
|
-
* Call this after consuming an opening bracket.
|
|
1100
|
-
* Skips until a closing bracket. This also consumes the closing bracket.
|
|
1101
|
-
*/
|
|
1102
|
-
private skipBracketsTo;
|
|
1103
|
-
}
|
|
1124
|
+
//#region src/StandardTypes.d.ts
|
|
1125
|
+
declare const stdFns: string[];
|
|
1126
|
+
declare const sampledTextureTypes = "\n texture_1d texture_2d texture_2d_array texture_3d \n texture_cube texture_cube_array\n";
|
|
1127
|
+
declare const multisampledTextureTypes = "\n texture_multisampled_2d texture_depth_multisampled_2d\n";
|
|
1128
|
+
declare const textureStorageTypes = "\n texture_storage_1d texture_storage_2d texture_storage_2d_array \n texture_storage_3d\n";
|
|
1129
|
+
declare const stdTypes: string[];
|
|
1130
|
+
/** https://www.w3.org/TR/WGSL/#predeclared-enumerants */
|
|
1131
|
+
declare const stdEnumerants: string[];
|
|
1132
|
+
/** return true if the name is for a built in type (not a user struct) */
|
|
1133
|
+
declare function stdType(name: string): boolean;
|
|
1134
|
+
/** return true if the name is for a built in fn (not a user function) */
|
|
1135
|
+
declare function stdFn(name: string): boolean;
|
|
1136
|
+
/** return true if the name is for a built in enumerant */
|
|
1137
|
+
declare function stdEnumerant(name: string): boolean;
|
|
1104
1138
|
//#endregion
|
|
1105
1139
|
//#region src/TransformBindingStructs.d.ts
|
|
1106
1140
|
declare function bindingStructsPlugin(): WeslJsPlugin;
|
|
@@ -1197,4 +1231,4 @@ declare function offsetToLineNumber(offset: number, text: string): [lineNum: num
|
|
|
1197
1231
|
*/
|
|
1198
1232
|
declare function errorHighlight(source: string, span: Span): [string, string];
|
|
1199
1233
|
//#endregion
|
|
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 };
|
|
1234
|
+
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, 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 };
|
package/dist/index.js
CHANGED
|
@@ -46,9 +46,9 @@ function logInternalSrc(logFn, src, pos, ...msgs) {
|
|
|
46
46
|
logFn(carets(linePos, linePos2));
|
|
47
47
|
}
|
|
48
48
|
function carets(linePos, linePos2) {
|
|
49
|
-
const indent = " ".repeat(linePos);
|
|
49
|
+
const indent = " ".repeat(Math.max(0, linePos));
|
|
50
50
|
const numCarets = linePos2 ? linePos2 - linePos : 1;
|
|
51
|
-
return indent + "^".repeat(numCarets);
|
|
51
|
+
return indent + "^".repeat(Math.max(1, numCarets));
|
|
52
52
|
}
|
|
53
53
|
const startCache = /* @__PURE__ */ new Map();
|
|
54
54
|
/** return the line in the src containing a given character position */
|
|
@@ -235,12 +235,12 @@ function offsetToLineNumber(offset, text) {
|
|
|
235
235
|
*/
|
|
236
236
|
function errorHighlight(source, span) {
|
|
237
237
|
let lineStartOffset = source.lastIndexOf("\n", span[0]);
|
|
238
|
-
|
|
238
|
+
lineStartOffset = lineStartOffset === -1 ? 0 : lineStartOffset + 1;
|
|
239
239
|
let lineEndOffset = source.indexOf("\n", span[0]);
|
|
240
240
|
if (lineEndOffset === -1) lineEndOffset = source.length;
|
|
241
241
|
const errorLength = span[1] - span[0];
|
|
242
242
|
const caretCount = Math.max(1, errorLength);
|
|
243
|
-
const linePos = span[0] - lineStartOffset;
|
|
243
|
+
const linePos = Math.max(0, span[0] - lineStartOffset);
|
|
244
244
|
return [source.slice(lineStartOffset, lineEndOffset), " ".repeat(linePos) + "^".repeat(caretCount)];
|
|
245
245
|
}
|
|
246
246
|
|
|
@@ -1443,6 +1443,7 @@ function expectWord(stream, errorMsg) {
|
|
|
1443
1443
|
function expectExpression(ctx, errorMsg = "Expected expression") {
|
|
1444
1444
|
const expr = parseExpression(ctx);
|
|
1445
1445
|
if (!expr) throwParseError(ctx.stream, errorMsg);
|
|
1446
|
+
if (ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
1446
1447
|
return expr;
|
|
1447
1448
|
}
|
|
1448
1449
|
/** Throw a ParseError at the current/next token position. */
|
|
@@ -2481,7 +2482,8 @@ function parseReturnStmt(ctx, startPos, attributes) {
|
|
|
2481
2482
|
const { stream } = ctx;
|
|
2482
2483
|
if (!stream.matchText("return")) return null;
|
|
2483
2484
|
beginElem(ctx, "statement", attributes);
|
|
2484
|
-
parseExpression(ctx);
|
|
2485
|
+
const expr = parseExpression(ctx);
|
|
2486
|
+
if (expr && ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
2485
2487
|
expect(stream, ";", "return statement");
|
|
2486
2488
|
return finishBlockStatement(startPos, ctx, attributes);
|
|
2487
2489
|
}
|
|
@@ -2532,11 +2534,13 @@ function parsePhonyAssignment(ctx, startPos, attributes) {
|
|
|
2532
2534
|
function parseExpressionStmt(ctx, startPos, attributes) {
|
|
2533
2535
|
const { stream } = ctx;
|
|
2534
2536
|
beginElem(ctx, "statement", attributes);
|
|
2535
|
-
|
|
2537
|
+
const expr = parseExpression(ctx);
|
|
2538
|
+
if (!expr) {
|
|
2536
2539
|
finishContents(ctx, startPos, startPos);
|
|
2537
2540
|
stream.reset(startPos);
|
|
2538
2541
|
return null;
|
|
2539
2542
|
}
|
|
2543
|
+
if (ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
2540
2544
|
if (!parseIncDecOperator(stream)) parseAssignmentRhs(ctx);
|
|
2541
2545
|
expect(stream, ";", "expression");
|
|
2542
2546
|
return finishBlockStatement(startPos, ctx, attributes);
|
|
@@ -2569,7 +2573,8 @@ function parseForStatement(ctx, attributes) {
|
|
|
2569
2573
|
ctx.pushScope();
|
|
2570
2574
|
expect(stream, "(", "'for'");
|
|
2571
2575
|
parseForInit(ctx);
|
|
2572
|
-
parseExpression(ctx);
|
|
2576
|
+
const cond = parseExpression(ctx);
|
|
2577
|
+
if (cond && ctx.options.preserveExpressions) ctx.addElem(cond);
|
|
2573
2578
|
expect(stream, ";", "for loop condition");
|
|
2574
2579
|
parseForUpdate(ctx);
|
|
2575
2580
|
expect(stream, ")", "for loop header");
|
|
@@ -2611,14 +2616,16 @@ function parseForInit(ctx) {
|
|
|
2611
2616
|
const varDecl = parseLocalVarDecl(ctx);
|
|
2612
2617
|
if (varDecl) ctx.addElem(varDecl);
|
|
2613
2618
|
else {
|
|
2614
|
-
parseExpression(ctx);
|
|
2619
|
+
const expr = parseExpression(ctx);
|
|
2620
|
+
if (expr && ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
2615
2621
|
expect(stream, ";", "for loop init");
|
|
2616
2622
|
}
|
|
2617
2623
|
}
|
|
2618
2624
|
/** Grammar: for_update : variable_updating_statement | func_call_statement
|
|
2619
2625
|
* variable_updating_statement : assignment_statement | increment_statement | decrement_statement */
|
|
2620
2626
|
function parseForUpdate(ctx) {
|
|
2621
|
-
parseExpression(ctx);
|
|
2627
|
+
const expr = parseExpression(ctx);
|
|
2628
|
+
if (expr && ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
2622
2629
|
parseIncDecOperator(ctx.stream) || parseAssignmentRhs(ctx);
|
|
2623
2630
|
}
|
|
2624
2631
|
|
|
@@ -3096,11 +3103,13 @@ var ParsingContext = class {
|
|
|
3096
3103
|
srcModule;
|
|
3097
3104
|
stream;
|
|
3098
3105
|
state;
|
|
3099
|
-
|
|
3106
|
+
options;
|
|
3107
|
+
constructor(stream, state, options) {
|
|
3100
3108
|
this.stream = stream;
|
|
3101
3109
|
this.state = state;
|
|
3102
3110
|
this.srcModule = state.stable.srcModule;
|
|
3103
3111
|
this.src = this.srcModule.src;
|
|
3112
|
+
this.options = options ?? {};
|
|
3104
3113
|
}
|
|
3105
3114
|
position() {
|
|
3106
3115
|
return this.stream.checkpoint();
|
|
@@ -3509,8 +3518,8 @@ var WeslStream = class {
|
|
|
3509
3518
|
//#endregion
|
|
3510
3519
|
//#region src/parse/ParseWesl.ts
|
|
3511
3520
|
/** Parse a WESL source module into an AST. */
|
|
3512
|
-
function parseWesl(srcModule) {
|
|
3513
|
-
const { ctx, state } = createParseState(srcModule);
|
|
3521
|
+
function parseWesl(srcModule, options) {
|
|
3522
|
+
const { ctx, state } = createParseState(srcModule, options);
|
|
3514
3523
|
try {
|
|
3515
3524
|
beginElem(ctx, "module");
|
|
3516
3525
|
parseModule(ctx);
|
|
@@ -3529,7 +3538,7 @@ function parseWesl(srcModule) {
|
|
|
3529
3538
|
}
|
|
3530
3539
|
}
|
|
3531
3540
|
/** Initialize parse state: token stream, root scope, and module element. */
|
|
3532
|
-
function createParseState(srcModule) {
|
|
3541
|
+
function createParseState(srcModule, options) {
|
|
3533
3542
|
const stream = new WeslStream(srcModule.src);
|
|
3534
3543
|
const rootScope = emptyScope(null);
|
|
3535
3544
|
const moduleElem = {
|
|
@@ -3551,7 +3560,7 @@ function createParseState(srcModule) {
|
|
|
3551
3560
|
}
|
|
3552
3561
|
};
|
|
3553
3562
|
return {
|
|
3554
|
-
ctx: new ParsingContext(stream, state),
|
|
3563
|
+
ctx: new ParsingContext(stream, state, options),
|
|
3555
3564
|
state
|
|
3556
3565
|
};
|
|
3557
3566
|
}
|
|
@@ -3574,8 +3583,8 @@ var WeslParseError = class extends Error {
|
|
|
3574
3583
|
}
|
|
3575
3584
|
};
|
|
3576
3585
|
/** Parse a WESL file. */
|
|
3577
|
-
function parseSrcModule(srcModule) {
|
|
3578
|
-
return parseWesl(srcModule);
|
|
3586
|
+
function parseSrcModule(srcModule, options) {
|
|
3587
|
+
return parseWesl(srcModule, options);
|
|
3579
3588
|
}
|
|
3580
3589
|
/** @return flattened form of import tree for binding idents. */
|
|
3581
3590
|
function flatImports(ast, conditions) {
|
|
@@ -3849,14 +3858,24 @@ function findQualifiedImport(refIdent, ctx) {
|
|
|
3849
3858
|
const identParts = refIdent.originalName.split("::");
|
|
3850
3859
|
const pathParts = matchingImport(identParts, flatImps) ?? qualifiedIdent(identParts);
|
|
3851
3860
|
if (!pathParts) {
|
|
3852
|
-
if (unbound && !stdWgsl(refIdent.originalName)) unbound
|
|
3861
|
+
if (unbound && !stdWgsl(refIdent.originalName)) pushUnbound(unbound, identParts, refIdent);
|
|
3853
3862
|
return;
|
|
3854
3863
|
}
|
|
3855
3864
|
const result = findExport(pathParts, refIdent.ast.srcModule, ctx);
|
|
3856
|
-
if (!result) if (unbound) unbound
|
|
3865
|
+
if (!result) if (unbound) pushUnbound(unbound, pathParts, refIdent);
|
|
3857
3866
|
else failIdent(refIdent, `module not found for '${pathParts.join("::")}'`);
|
|
3858
3867
|
return result;
|
|
3859
3868
|
}
|
|
3869
|
+
/** Add an unbound reference with position info. */
|
|
3870
|
+
function pushUnbound(unbound, path, refIdent) {
|
|
3871
|
+
const { srcModule, start, end } = refIdent.refIdentElem;
|
|
3872
|
+
unbound.push({
|
|
3873
|
+
path,
|
|
3874
|
+
srcModule,
|
|
3875
|
+
start,
|
|
3876
|
+
end
|
|
3877
|
+
});
|
|
3878
|
+
}
|
|
3860
3879
|
/** Find an import statement that matches a provided identifier. */
|
|
3861
3880
|
function matchingImport(identParts, imports) {
|
|
3862
3881
|
const flat = imports.find((f) => f.importPath.at(-1) === identParts[0]);
|
|
@@ -3940,6 +3959,10 @@ function collectDecls(scope, found) {
|
|
|
3940
3959
|
* (e.g., [['foo', 'bar', 'baz'], ['other', 'pkg']])
|
|
3941
3960
|
*/
|
|
3942
3961
|
function findUnboundIdents(resolver) {
|
|
3962
|
+
return findUnboundRefs(resolver).map((ref) => ref.path);
|
|
3963
|
+
}
|
|
3964
|
+
/** Find unbound references with full position info. */
|
|
3965
|
+
function findUnboundRefs(resolver) {
|
|
3943
3966
|
const bindContext = {
|
|
3944
3967
|
resolver,
|
|
3945
3968
|
conditions: {},
|
|
@@ -4308,7 +4331,7 @@ var SrcMap = class SrcMap {
|
|
|
4308
4331
|
const newEntries = [prev];
|
|
4309
4332
|
for (let i = 1; i < this.entries.length; i++) {
|
|
4310
4333
|
const e = this.entries[i];
|
|
4311
|
-
if (e.src.path === prev.src.path && e.src.text === prev.src.text && prev.destEnd === e.destStart && prev.srcEnd === e.srcStart) {
|
|
4334
|
+
if (e.src.path === prev.src.path && e.src.text === prev.src.text && prev.destEnd === e.destStart && prev.srcEnd === e.srcStart && prev.srcEnd - prev.srcStart === prev.destEnd - prev.destStart) {
|
|
4312
4335
|
prev.destEnd = e.destEnd;
|
|
4313
4336
|
prev.srcEnd = e.srcEnd;
|
|
4314
4337
|
} else {
|
|
@@ -4937,4 +4960,4 @@ function makeWeslDevice(device) {
|
|
|
4937
4960
|
}
|
|
4938
4961
|
|
|
4939
4962
|
//#endregion
|
|
4940
|
-
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 };
|
|
4963
|
+
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, 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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wesl",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"wrangler": "^4.22.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"random_wgsl": "^0.6.
|
|
30
|
+
"random_wgsl": "^0.6.64"
|
|
31
31
|
},
|
|
32
32
|
"peerDependenciesMeta": {
|
|
33
33
|
"random_wgsl": {
|
package/src/BindIdents.ts
CHANGED
|
@@ -70,8 +70,20 @@ export interface BindResults {
|
|
|
70
70
|
/** Additional global statements to emit (e.g., const_assert). */
|
|
71
71
|
newStatements: EmittableElem[];
|
|
72
72
|
|
|
73
|
-
/** Unbound
|
|
74
|
-
unbound?:
|
|
73
|
+
/** Unbound identifiers with position info (only if accumulateUnbound is true). */
|
|
74
|
+
unbound?: UnboundRef[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** An unresolved reference with position info for error reporting. */
|
|
78
|
+
export interface UnboundRef {
|
|
79
|
+
/** Module path that couldn't be resolved (e.g., ["package", "foo", "bar"]). */
|
|
80
|
+
path: string[];
|
|
81
|
+
/** Source module containing this reference. */
|
|
82
|
+
srcModule: SrcModule;
|
|
83
|
+
/** Start offset in the source. */
|
|
84
|
+
start: number;
|
|
85
|
+
/** End offset in the source. */
|
|
86
|
+
end: number;
|
|
75
87
|
}
|
|
76
88
|
|
|
77
89
|
/** An element that can be directly emitted into the linked result. */
|
|
@@ -223,7 +235,7 @@ interface BindContext {
|
|
|
223
235
|
virtuals?: VirtualLibrarySet;
|
|
224
236
|
|
|
225
237
|
/** Unbound identifiers if accumulateUnbound is true. */
|
|
226
|
-
unbound?:
|
|
238
|
+
unbound?: UnboundRef[];
|
|
227
239
|
|
|
228
240
|
/** Don't follow references from declarations (for library dependency detection). */
|
|
229
241
|
dontFollowDecls?: boolean;
|
|
@@ -370,18 +382,30 @@ function findQualifiedImport(
|
|
|
370
382
|
matchingImport(identParts, flatImps) ?? qualifiedIdent(identParts);
|
|
371
383
|
|
|
372
384
|
if (!pathParts) {
|
|
373
|
-
if (unbound && !stdWgsl(refIdent.originalName))
|
|
385
|
+
if (unbound && !stdWgsl(refIdent.originalName)) {
|
|
386
|
+
pushUnbound(unbound, identParts, refIdent);
|
|
387
|
+
}
|
|
374
388
|
return undefined;
|
|
375
389
|
}
|
|
376
390
|
|
|
377
391
|
const result = findExport(pathParts, refIdent.ast.srcModule, ctx);
|
|
378
392
|
if (!result) {
|
|
379
|
-
if (unbound) unbound
|
|
393
|
+
if (unbound) pushUnbound(unbound, pathParts, refIdent);
|
|
380
394
|
else failIdent(refIdent, `module not found for '${pathParts.join("::")}'`);
|
|
381
395
|
}
|
|
382
396
|
return result;
|
|
383
397
|
}
|
|
384
398
|
|
|
399
|
+
/** Add an unbound reference with position info. */
|
|
400
|
+
function pushUnbound(
|
|
401
|
+
unbound: UnboundRef[],
|
|
402
|
+
path: string[],
|
|
403
|
+
refIdent: RefIdent,
|
|
404
|
+
): void {
|
|
405
|
+
const { srcModule, start, end } = refIdent.refIdentElem;
|
|
406
|
+
unbound.push({ path, srcModule, start, end });
|
|
407
|
+
}
|
|
408
|
+
|
|
385
409
|
/** Find an import statement that matches a provided identifier. */
|
|
386
410
|
function matchingImport(
|
|
387
411
|
identParts: string[],
|
package/src/Logging.ts
CHANGED
|
@@ -88,9 +88,9 @@ function logInternalSrc(
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
function carets(linePos: number, linePos2?: number): string {
|
|
91
|
-
const indent = " ".repeat(linePos);
|
|
91
|
+
const indent = " ".repeat(Math.max(0, linePos));
|
|
92
92
|
const numCarets = linePos2 ? linePos2 - linePos : 1;
|
|
93
|
-
const caretStr = "^".repeat(numCarets);
|
|
93
|
+
const caretStr = "^".repeat(Math.max(1, numCarets));
|
|
94
94
|
return indent + caretStr;
|
|
95
95
|
}
|
|
96
96
|
|
package/src/ParseWESL.ts
CHANGED
|
@@ -9,10 +9,13 @@ import { filterValidElements } from "./Conditions.ts";
|
|
|
9
9
|
import { type FlatImport, flattenTreeImport } from "./FlattenTreeImport.ts";
|
|
10
10
|
import type { ParseError } from "./ParseError.ts";
|
|
11
11
|
import { parseWesl } from "./parse/ParseWesl.ts";
|
|
12
|
+
import type { ParseOptions } from "./parse/ParsingContext.ts";
|
|
12
13
|
import type { Conditions, Scope, SrcModule } from "./Scope.ts";
|
|
13
14
|
import type { Span } from "./Span.ts";
|
|
14
15
|
import { errorHighlight, offsetToLineNumber } from "./Util.ts";
|
|
15
16
|
|
|
17
|
+
export type { ParseOptions };
|
|
18
|
+
|
|
16
19
|
/** Partial element being constructed during parsing. */
|
|
17
20
|
export type OpenElem<T extends ContainerElem = ContainerElem> = Pick<
|
|
18
21
|
T,
|
|
@@ -78,8 +81,11 @@ export class WeslParseError extends Error {
|
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
/** Parse a WESL file. */
|
|
81
|
-
export function parseSrcModule(
|
|
82
|
-
|
|
84
|
+
export function parseSrcModule(
|
|
85
|
+
srcModule: SrcModule,
|
|
86
|
+
options?: ParseOptions,
|
|
87
|
+
): WeslAST {
|
|
88
|
+
return parseWesl(srcModule, options);
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
/** @return flattened form of import tree for binding idents. */
|
package/src/SrcMap.ts
CHANGED
|
@@ -50,7 +50,8 @@ export class SrcMap {
|
|
|
50
50
|
e.src.path === prev.src.path &&
|
|
51
51
|
e.src.text === prev.src.text &&
|
|
52
52
|
prev.destEnd === e.destStart &&
|
|
53
|
-
prev.srcEnd === e.srcStart
|
|
53
|
+
prev.srcEnd === e.srcStart &&
|
|
54
|
+
prev.srcEnd - prev.srcStart === prev.destEnd - prev.destStart
|
|
54
55
|
) {
|
|
55
56
|
// combine adjacent range entries into one
|
|
56
57
|
prev.destEnd = e.destEnd;
|
package/src/Util.ts
CHANGED
|
@@ -175,9 +175,7 @@ export function offsetToLineNumber(
|
|
|
175
175
|
*/
|
|
176
176
|
export function errorHighlight(source: string, span: Span): [string, string] {
|
|
177
177
|
let lineStartOffset = source.lastIndexOf("\n", span[0]);
|
|
178
|
-
|
|
179
|
-
lineStartOffset = 0;
|
|
180
|
-
}
|
|
178
|
+
lineStartOffset = lineStartOffset === -1 ? 0 : lineStartOffset + 1;
|
|
181
179
|
let lineEndOffset = source.indexOf("\n", span[0]);
|
|
182
180
|
if (lineEndOffset === -1) {
|
|
183
181
|
lineEndOffset = source.length;
|
|
@@ -186,7 +184,7 @@ export function errorHighlight(source: string, span: Span): [string, string] {
|
|
|
186
184
|
// LATER Handle multiline spans
|
|
187
185
|
const errorLength = span[1] - span[0];
|
|
188
186
|
const caretCount = Math.max(1, errorLength);
|
|
189
|
-
const linePos = span[0] - lineStartOffset;
|
|
187
|
+
const linePos = Math.max(0, span[0] - lineStartOffset);
|
|
190
188
|
return [
|
|
191
189
|
source.slice(lineStartOffset, lineEndOffset),
|
|
192
190
|
" ".repeat(linePos) + "^".repeat(caretCount),
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
bindIdentsRecursive,
|
|
4
4
|
type EmittableElem,
|
|
5
5
|
findValidRootDecls,
|
|
6
|
+
type UnboundRef,
|
|
6
7
|
} from "../BindIdents.ts";
|
|
7
8
|
import { type LiveDecls, makeLiveDecls } from "../LiveDeclarations.ts";
|
|
8
9
|
import { minimalMangle } from "../Mangler.ts";
|
|
@@ -21,6 +22,11 @@ import { filterMap } from "../Util.ts";
|
|
|
21
22
|
* (e.g., [['foo', 'bar', 'baz'], ['other', 'pkg']])
|
|
22
23
|
*/
|
|
23
24
|
export function findUnboundIdents(resolver: BatchModuleResolver): string[][] {
|
|
25
|
+
return findUnboundRefs(resolver).map(ref => ref.path);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Find unbound references with full position info. */
|
|
29
|
+
export function findUnboundRefs(resolver: BatchModuleResolver): UnboundRef[] {
|
|
24
30
|
const bindContext = {
|
|
25
31
|
resolver,
|
|
26
32
|
conditions: {},
|
|
@@ -29,7 +35,7 @@ export function findUnboundIdents(resolver: BatchModuleResolver): string[][] {
|
|
|
29
35
|
globalNames: new Set<string>(),
|
|
30
36
|
globalStatements: new Map<AbstractElem, EmittableElem>(),
|
|
31
37
|
mangler: minimalMangle,
|
|
32
|
-
unbound: [] as
|
|
38
|
+
unbound: [] as UnboundRef[],
|
|
33
39
|
dontFollowDecls: true,
|
|
34
40
|
};
|
|
35
41
|
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ export { WeslStream } from "./parse/WeslStream.ts";
|
|
|
18
18
|
export * from "./Scope.ts";
|
|
19
19
|
export * from "./Span.ts";
|
|
20
20
|
export * from "./SrcMap.ts";
|
|
21
|
+
export * from "./StandardTypes.ts";
|
|
21
22
|
export * from "./TransformBindingStructs.ts";
|
|
22
23
|
export * from "./Util.ts";
|
|
23
24
|
export * from "./WeslBundle.ts";
|
package/src/parse/ParseLoop.ts
CHANGED
|
@@ -33,7 +33,8 @@ export function parseForStatement(
|
|
|
33
33
|
expect(stream, "(", "'for'");
|
|
34
34
|
|
|
35
35
|
parseForInit(ctx);
|
|
36
|
-
parseExpression(ctx); // returns null if empty condition
|
|
36
|
+
const cond = parseExpression(ctx); // returns null if empty condition
|
|
37
|
+
if (cond && ctx.options.preserveExpressions) ctx.addElem(cond);
|
|
37
38
|
expect(stream, ";", "for loop condition");
|
|
38
39
|
parseForUpdate(ctx);
|
|
39
40
|
expect(stream, ")", "for loop header");
|
|
@@ -99,7 +100,8 @@ function parseForInit(ctx: ParsingContext): void {
|
|
|
99
100
|
ctx.addElem(varDecl);
|
|
100
101
|
// parseLocalVarDecl already consumed the ';'
|
|
101
102
|
} else {
|
|
102
|
-
parseExpression(ctx); // returns null for empty case
|
|
103
|
+
const expr = parseExpression(ctx); // returns null for empty case
|
|
104
|
+
if (expr && ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
103
105
|
expect(stream, ";", "for loop init");
|
|
104
106
|
}
|
|
105
107
|
}
|
|
@@ -107,6 +109,7 @@ function parseForInit(ctx: ParsingContext): void {
|
|
|
107
109
|
/** Grammar: for_update : variable_updating_statement | func_call_statement
|
|
108
110
|
* variable_updating_statement : assignment_statement | increment_statement | decrement_statement */
|
|
109
111
|
function parseForUpdate(ctx: ParsingContext): void {
|
|
110
|
-
parseExpression(ctx);
|
|
112
|
+
const expr = parseExpression(ctx);
|
|
113
|
+
if (expr && ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
111
114
|
parseIncDecOperator(ctx.stream) || parseAssignmentRhs(ctx);
|
|
112
115
|
}
|
|
@@ -57,7 +57,8 @@ function parseReturnStmt(
|
|
|
57
57
|
const { stream } = ctx;
|
|
58
58
|
if (!stream.matchText("return")) return null;
|
|
59
59
|
beginElem(ctx, "statement", attributes);
|
|
60
|
-
parseExpression(ctx);
|
|
60
|
+
const expr = parseExpression(ctx);
|
|
61
|
+
if (expr && ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
61
62
|
expect(stream, ";", "return statement");
|
|
62
63
|
return finishBlockStatement(startPos, ctx, attributes);
|
|
63
64
|
}
|
|
@@ -138,6 +139,7 @@ function parseExpressionStmt(
|
|
|
138
139
|
stream.reset(startPos);
|
|
139
140
|
return null;
|
|
140
141
|
}
|
|
142
|
+
if (ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
141
143
|
|
|
142
144
|
if (!parseIncDecOperator(stream)) parseAssignmentRhs(ctx);
|
|
143
145
|
expect(stream, ";", "expression");
|
package/src/parse/ParseUtil.ts
CHANGED
package/src/parse/ParseWesl.ts
CHANGED
|
@@ -6,12 +6,15 @@ import type { SrcModule } from "../Scope.ts";
|
|
|
6
6
|
import { emptyScope } from "../Scope.ts";
|
|
7
7
|
import { beginElem, finishContents } from "./ContentsHelpers.ts";
|
|
8
8
|
import { parseModule } from "./ParseModule.ts";
|
|
9
|
-
import { ParsingContext } from "./ParsingContext.ts";
|
|
9
|
+
import { type ParseOptions, ParsingContext } from "./ParsingContext.ts";
|
|
10
10
|
import { WeslStream } from "./WeslStream.ts";
|
|
11
11
|
|
|
12
12
|
/** Parse a WESL source module into an AST. */
|
|
13
|
-
export function parseWesl(
|
|
14
|
-
|
|
13
|
+
export function parseWesl(
|
|
14
|
+
srcModule: SrcModule,
|
|
15
|
+
options?: ParseOptions,
|
|
16
|
+
): WeslAST {
|
|
17
|
+
const { ctx, state } = createParseState(srcModule, options);
|
|
15
18
|
try {
|
|
16
19
|
beginElem(ctx, "module");
|
|
17
20
|
parseModule(ctx);
|
|
@@ -30,7 +33,10 @@ export function parseWesl(srcModule: SrcModule): WeslAST {
|
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
/** Initialize parse state: token stream, root scope, and module element. */
|
|
33
|
-
function createParseState(
|
|
36
|
+
function createParseState(
|
|
37
|
+
srcModule: SrcModule,
|
|
38
|
+
options?: ParseOptions,
|
|
39
|
+
): {
|
|
34
40
|
ctx: ParsingContext;
|
|
35
41
|
state: WeslParseState;
|
|
36
42
|
} {
|
|
@@ -46,6 +52,6 @@ function createParseState(srcModule: SrcModule): {
|
|
|
46
52
|
context: { scope: rootScope, openElems: [] },
|
|
47
53
|
stable: { srcModule, moduleElem, rootScope, imports: [] },
|
|
48
54
|
};
|
|
49
|
-
const ctx = new ParsingContext(stream, state);
|
|
55
|
+
const ctx = new ParsingContext(stream, state, options);
|
|
50
56
|
return { ctx, state };
|
|
51
57
|
}
|
|
@@ -11,18 +11,30 @@ import {
|
|
|
11
11
|
} from "../Scope.ts";
|
|
12
12
|
import type { WeslStream } from "./WeslStream.ts";
|
|
13
13
|
|
|
14
|
+
export interface ParseOptions {
|
|
15
|
+
/** Store expression AST nodes in statement contents (for tooling/validation). */
|
|
16
|
+
// LATER we'll always store expressions in the AST, (but this partial support is for wgsl-edit validation)
|
|
17
|
+
preserveExpressions?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
/** Context for parsers to build AST and manage scopes. */
|
|
15
21
|
export class ParsingContext {
|
|
16
22
|
src: string;
|
|
17
23
|
srcModule: SrcModule;
|
|
18
24
|
stream: WeslStream;
|
|
19
25
|
state: WeslParseState;
|
|
26
|
+
options: ParseOptions;
|
|
20
27
|
|
|
21
|
-
constructor(
|
|
28
|
+
constructor(
|
|
29
|
+
stream: WeslStream,
|
|
30
|
+
state: WeslParseState,
|
|
31
|
+
options?: ParseOptions,
|
|
32
|
+
) {
|
|
22
33
|
this.stream = stream;
|
|
23
34
|
this.state = state;
|
|
24
35
|
this.srcModule = state.stable.srcModule;
|
|
25
36
|
this.src = this.srcModule.src;
|
|
37
|
+
this.options = options ?? {};
|
|
26
38
|
}
|
|
27
39
|
|
|
28
40
|
position(): number {
|
|
@@ -79,8 +79,9 @@ test("collect unbound references", async () => {
|
|
|
79
79
|
const bindResult = bindIdents({ resolver, rootAst, accumulateUnbound: true });
|
|
80
80
|
|
|
81
81
|
const expected = ["pkg1::bar::baz", "pkg2::foo"];
|
|
82
|
-
const
|
|
83
|
-
|
|
82
|
+
const expectedPaths = expected.map(s => s.split("::")).sort();
|
|
83
|
+
const unboundPaths = bindResult.unbound?.map(ref => ref.path).sort();
|
|
84
|
+
expect(unboundPaths).deep.equal(expectedPaths);
|
|
84
85
|
});
|
|
85
86
|
|
|
86
87
|
test("publicDecl finds valid conditional declaration", () => {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import { SrcMap, type SrcMapEntry, type SrcWithPath } from "../SrcMap.ts";
|
|
3
|
+
|
|
4
|
+
const src: SrcWithPath = {
|
|
5
|
+
text: "let x = lygia::math::consts::PI;\n let x = 7;",
|
|
6
|
+
path: "test.wesl",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function makeMap(entries: SrcMapEntry[]): SrcMap {
|
|
10
|
+
const dest: SrcWithPath = { text: "PI;\n let x = 7;" };
|
|
11
|
+
return new SrcMap(dest, entries);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
test("compact does not merge entries with different src/dest lengths", () => {
|
|
15
|
+
// Entry A: src "lygia::math::consts::PI" (25 chars) -> dest "PI" (2 chars)
|
|
16
|
+
// Entry B: src ";\n let x = 7;" (14 chars) -> dest ";\n let x = 7;" (14 chars)
|
|
17
|
+
const entries: SrcMapEntry[] = [
|
|
18
|
+
{ src, srcStart: 8, srcEnd: 33, destStart: 0, destEnd: 2 },
|
|
19
|
+
{ src, srcStart: 33, srcEnd: 47, destStart: 2, destEnd: 16 },
|
|
20
|
+
];
|
|
21
|
+
const map = makeMap(entries);
|
|
22
|
+
map.compact();
|
|
23
|
+
expect(map.entries).toHaveLength(2);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("compact merges adjacent entries with equal src/dest lengths", () => {
|
|
27
|
+
const entries: SrcMapEntry[] = [
|
|
28
|
+
{ src, srcStart: 0, srcEnd: 5, destStart: 0, destEnd: 5 },
|
|
29
|
+
{ src, srcStart: 5, srcEnd: 10, destStart: 5, destEnd: 10 },
|
|
30
|
+
];
|
|
31
|
+
const map = makeMap(entries);
|
|
32
|
+
map.compact();
|
|
33
|
+
expect(map.entries).toHaveLength(1);
|
|
34
|
+
expect(map.entries[0]).toMatchObject({
|
|
35
|
+
srcStart: 0,
|
|
36
|
+
srcEnd: 10,
|
|
37
|
+
destStart: 0,
|
|
38
|
+
destEnd: 10,
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("destToSrc returns correct position after compact with mixed-length entries", () => {
|
|
43
|
+
// dest: "PI;\n let x = 7;"
|
|
44
|
+
// ^^ entry A (2 chars dest, 25 chars src)
|
|
45
|
+
// ^^^^^^^^^^^^^^ entry B (14 chars dest, 14 chars src)
|
|
46
|
+
const entries: SrcMapEntry[] = [
|
|
47
|
+
{ src, srcStart: 8, srcEnd: 33, destStart: 0, destEnd: 2 },
|
|
48
|
+
{ src, srcStart: 33, srcEnd: 47, destStart: 2, destEnd: 16 },
|
|
49
|
+
];
|
|
50
|
+
const map = makeMap(entries);
|
|
51
|
+
map.compact();
|
|
52
|
+
|
|
53
|
+
// "x" in "let x = 7" is at dest offset 10 (inside entry B)
|
|
54
|
+
const result = map.destToSrc(10);
|
|
55
|
+
expect(result.position).toBe(41); // srcStart 33 + (10 - 2) = 41
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("destToSrc for unmapped position falls back to dest identity", () => {
|
|
59
|
+
const entries: SrcMapEntry[] = [
|
|
60
|
+
{ src, srcStart: 0, srcEnd: 3, destStart: 0, destEnd: 3 },
|
|
61
|
+
{ src, srcStart: 10, srcEnd: 13, destStart: 10, destEnd: 13 },
|
|
62
|
+
];
|
|
63
|
+
const map = makeMap(entries);
|
|
64
|
+
|
|
65
|
+
// position 5 is in the gap between entries
|
|
66
|
+
const result = map.destToSrc(5);
|
|
67
|
+
expect(result.src).toBe(map.dest);
|
|
68
|
+
expect(result.position).toBe(5);
|
|
69
|
+
});
|