wesl 0.7.26 → 0.7.28

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.
Files changed (67) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +283 -147
  3. package/dist/index.js +1765 -1143
  4. package/package.json +2 -2
  5. package/src/AbstractElems.ts +264 -82
  6. package/src/ClickableError.ts +8 -1
  7. package/src/Linker.ts +63 -67
  8. package/src/LinkerUtil.ts +141 -7
  9. package/src/LowerAndEmit.ts +660 -304
  10. package/src/Mangler.ts +1 -1
  11. package/src/ModuleResolver.ts +15 -4
  12. package/src/ParseWESL.ts +21 -33
  13. package/src/SrcMap.ts +7 -19
  14. package/src/StandardTypes.ts +1 -1
  15. package/src/WeslDevice.ts +1 -0
  16. package/src/debug/ASTtoString.ts +92 -76
  17. package/src/index.ts +1 -1
  18. package/src/parse/AttachComments.ts +289 -0
  19. package/src/parse/ExpressionUtil.ts +3 -3
  20. package/src/parse/Keywords.ts +1 -1
  21. package/src/parse/ParseAttribute.ts +49 -71
  22. package/src/parse/ParseCall.ts +3 -4
  23. package/src/parse/ParseControlFlow.ts +100 -56
  24. package/src/parse/ParseDirective.ts +9 -8
  25. package/src/parse/ParseDoBlock.ts +63 -0
  26. package/src/parse/ParseExpression.ts +11 -10
  27. package/src/parse/ParseFn.ts +11 -33
  28. package/src/parse/ParseGlobalVar.ts +36 -27
  29. package/src/parse/ParseIdent.ts +4 -5
  30. package/src/parse/ParseLocalVar.ts +16 -12
  31. package/src/parse/ParseLoop.ts +76 -39
  32. package/src/parse/ParseModule.ts +65 -19
  33. package/src/parse/ParseSimpleStatement.ts +112 -66
  34. package/src/parse/ParseStatement.ts +40 -67
  35. package/src/parse/ParseStruct.ts +8 -22
  36. package/src/parse/ParseType.ts +10 -13
  37. package/src/parse/ParseUtil.ts +26 -12
  38. package/src/parse/ParseValueDeclaration.ts +18 -31
  39. package/src/parse/ParseWesl.ts +11 -14
  40. package/src/parse/ParsingContext.ts +11 -9
  41. package/src/parse/WeslStream.ts +138 -121
  42. package/src/parse/stream/RegexMatchers.ts +45 -0
  43. package/src/parse/stream/WeslLexer.ts +249 -0
  44. package/src/test/BevyLink.test.ts +18 -11
  45. package/src/test/ConditionalElif.test.ts +4 -2
  46. package/src/test/DeclCommentEmit.test.ts +122 -0
  47. package/src/test/DoBlock.test.ts +164 -0
  48. package/src/test/FilterValidElements.test.ts +4 -6
  49. package/src/test/Linker.test.ts +23 -7
  50. package/src/test/Mangling.test.ts +8 -4
  51. package/src/test/ParseComments.test.ts +234 -6
  52. package/src/test/ParseConditionsV2.test.ts +47 -175
  53. package/src/test/ParseElifV2.test.ts +12 -33
  54. package/src/test/ParseErrorV2.test.ts +0 -0
  55. package/src/test/ParseWeslV2.test.ts +247 -626
  56. package/src/test/StatementEmit.test.ts +143 -0
  57. package/src/test/StripWesl.ts +24 -3
  58. package/src/test/TestLink.ts +19 -3
  59. package/src/test/TestUtil.ts +18 -8
  60. package/src/test/Tokenizer.test.ts +96 -0
  61. package/src/test/__snapshots__/ParseWeslV2.test.ts.snap +10 -42
  62. package/src/RawEmit.ts +0 -103
  63. package/src/Reflection.ts +0 -336
  64. package/src/TransformBindingStructs.ts +0 -320
  65. package/src/parse/ContentsHelpers.ts +0 -70
  66. package/src/parse/stream/CachingStream.ts +0 -48
  67. package/src/parse/stream/MatchersStream.ts +0 -85
package/src/Linker.ts CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  type ModuleResolver,
15
15
  RecordResolver,
16
16
  } from "./ModuleResolver.ts";
17
- import type { WeslAST } from "./ParseWESL.ts";
17
+ import type { WeslAST, WeslExtensions } from "./ParseWESL.ts";
18
18
  import type { Conditions, DeclIdent, SrcModule } from "./Scope.ts";
19
19
  import { type SrcMap, SrcMapBuilder } from "./SrcMap.ts";
20
20
  import { filterMap, mapValues } from "./Util.ts";
@@ -37,29 +37,17 @@ export interface LinkConfig {
37
37
  }
38
38
 
39
39
  export interface LinkParams {
40
- /** Module resolver for lazy loading (NEW API - preferred)
41
- * Replaces weslSrc for lazy module loading.
42
- * If provided, weslSrc will be ignored. */
40
+ /** Module resolver for lazy loading. If provided, weslSrc is ignored. */
43
41
  resolver?: ModuleResolver;
44
42
 
45
- /** record of file paths and wesl text for modules (LEGACY API - still supported).
46
- * key is module path or file path
47
- * `package::foo::bar`, or './foo/bar.wesl', or './foo/bar'
48
- * value is wesl src
49
- *
50
- *
51
- * Only accepts unix-style, relative filesystem paths that are valid WGSL identifiers
52
- * - Unix-style: Slashes as separators.
53
- * - Valid WGSL identifiers: No backslashes, no `..`, or other non-identifier symbols.
54
- * - Relative paths: They have to be relative to the wesl root.
55
- */
43
+ /** Record of module sources keyed by module path (`package::foo::bar`)
44
+ * or relative file path (`./foo/bar.wesl`). Paths must be unix-style,
45
+ * relative to the wesl root, and valid WGSL identifiers. */
56
46
  weslSrc?: Record<string, string>;
57
47
 
58
- /** name of root wesl module
59
- * for an app, the root module normally contains the '@compute', '@vertex' or '@fragment' entry points
60
- * for a library, the root module defines the public api fo the library
61
- * can be specified as file path (./main.wesl), a module path (package::main), or just a module name (main)
62
- */
48
+ /** Root module name: file path (`./main.wesl`), module path (`package::main`), or bare name (`main`).
49
+ * For apps, the root module contains entry points (`@compute`, `@vertex`, `@fragment`).
50
+ * For libraries, it defines the public API. */
63
51
  rootModuleName?: string;
64
52
 
65
53
  /** For debug logging. Will be prepended to file paths. */
@@ -89,6 +77,11 @@ export interface LinkParams {
89
77
 
90
78
  /** function to construct globally unique wgsl identifiers */
91
79
  mangler?: ManglerFn;
80
+
81
+ /** opt-in parsing of experimental, not-yet-spec'd syntax extensions.
82
+ * Only applied to the local source resolver built from `weslSrc`; callers
83
+ * that supply their own `resolver` must set it on that resolver. */
84
+ weslExtensions?: WeslExtensions;
92
85
  }
93
86
 
94
87
  /** Project config for web components and tools. */
@@ -100,7 +93,11 @@ export type WeslProject = Pick<
100
93
  | "constants"
101
94
  | "libs"
102
95
  | "packageName"
103
- >;
96
+ | "weslExtensions"
97
+ > & {
98
+ /** Shader directory relative to project root (set by ?link from wesl.toml). */
99
+ shaderRoot?: string;
100
+ };
104
101
 
105
102
  /** Context passed to virtual library generators. */
106
103
  export interface VirtualLibContext {
@@ -127,26 +124,26 @@ export async function link(params: LinkParams): Promise<LinkedWesl> {
127
124
 
128
125
  /** linker api for benchmarking */
129
126
  export function _linkSync(params: LinkParams): SrcMap {
130
- const { weslSrc, libs = [], packageName, debugWeslRoot } = params;
131
- const { resolver } = params;
132
-
133
- const resolvers: ModuleResolver[] = [];
127
+ const { weslSrc, libs = [], packageName, debugWeslRoot, resolver } = params;
128
+ const { weslExtensions } = params;
134
129
 
135
- if (resolver) {
136
- resolvers.push(resolver);
137
- } else if (weslSrc) {
138
- resolvers.push(new RecordResolver(weslSrc, { packageName, debugWeslRoot }));
139
- } else {
130
+ if (!resolver && !weslSrc) {
140
131
  throw new Error("Either resolver or weslSrc must be provided");
141
132
  }
142
-
143
- if (libs.length > 0) {
144
- const libResolvers = createLibraryResolvers(libs, debugWeslRoot);
145
- resolvers.push(...libResolvers);
146
- }
147
-
133
+ const primaryResolver =
134
+ resolver ??
135
+ new RecordResolver(weslSrc!, {
136
+ packageName,
137
+ debugWeslRoot,
138
+ weslExtensions,
139
+ });
140
+
141
+ const libResolvers = createLibraryResolvers(libs, debugWeslRoot);
142
+ const allResolvers = [primaryResolver, ...libResolvers];
148
143
  const finalResolver =
149
- resolvers.length === 1 ? resolvers[0] : new CompositeResolver(resolvers);
144
+ allResolvers.length === 1
145
+ ? allResolvers[0]
146
+ : new CompositeResolver(allResolvers);
150
147
 
151
148
  return linkRegistry({ ...params, resolver: finalResolver });
152
149
  }
@@ -200,11 +197,8 @@ export interface LinkRegistryParams
200
197
  * that share some sources.)
201
198
  */
202
199
  export function linkRegistry(params: LinkRegistryParams): SrcMap {
203
- const {
204
- transformedAst: ast,
205
- newDecls,
206
- newStatements,
207
- } = bindAndTransform(params);
200
+ const bound = bindAndTransform(params);
201
+ const { transformedAst: ast, newDecls, newStatements } = bound;
208
202
  const builders = emitWgsl(
209
203
  ast.moduleElem,
210
204
  ast.srcModule,
@@ -230,7 +224,6 @@ export function bindAndTransform(
230
224
 
231
225
  const modulePath = normalizeModuleName(rootModuleName);
232
226
  const rootAst = getRootModule(resolver, modulePath, rootModuleName);
233
-
234
227
  const virtuals = setupVirtualLibs(params.virtualLibs, constants);
235
228
 
236
229
  const bound = bindIdents({
@@ -291,14 +284,14 @@ function setupVirtualLibs(
291
284
  return libs && mapValues(libs, fn => ({ fn }));
292
285
  }
293
286
 
287
+ /** Run registered transform plugins over the bound AST. */
294
288
  function applyTransformPlugins(
295
289
  rootModule: WeslAST,
296
290
  globalNames: Set<string>,
297
291
  config?: LinkConfig,
298
292
  ): TransformedAST {
299
- const { moduleElem, srcModule } = rootModule;
300
-
301
293
  // for now only transform the root module
294
+ const { moduleElem, srcModule } = rootModule;
302
295
  const startAst = { moduleElem, srcModule, globalNames, notableElems: {} };
303
296
  const plugins = config?.plugins ?? [];
304
297
  const transforms = filterMap(plugins, plugin => plugin.transform);
@@ -313,12 +306,11 @@ function emitWgsl(
313
306
  newStatements: EmittableElem[],
314
307
  conditions: Conditions = {},
315
308
  ): SrcMapBuilder[] {
316
- const prologueBuilders = newStatements.map(s => emitStatement(s, conditions));
309
+ const prologueBuilders = newStatements.map(s =>
310
+ emitElem(s.srcModule, s.elem, conditions, { addNl: true }),
311
+ );
317
312
 
318
- const rootBuilder = new SrcMapBuilder({
319
- text: srcModule.src,
320
- path: srcModule.debugFilePath,
321
- });
313
+ const rootBuilder = builderFromModule(srcModule);
322
314
  lowerAndEmit({
323
315
  srcBuilder: rootBuilder,
324
316
  rootElems: [rootModuleElem],
@@ -326,36 +318,40 @@ function emitWgsl(
326
318
  extracting: false,
327
319
  });
328
320
 
329
- const declBuilders = newDecls.map(decl => emitDecl(decl, conditions));
321
+ const declBuilders = newDecls.map(decl =>
322
+ emitElem(decl.srcModule, decl.declElem!, conditions, {
323
+ skipConditionalFiltering: true,
324
+ }),
325
+ );
330
326
 
331
327
  return [...prologueBuilders, rootBuilder, ...declBuilders];
332
328
  }
333
329
 
334
- function emitStatement(
335
- s: EmittableElem,
330
+ /** Emit a single element (prologue statement or imported declaration) into a SrcMapBuilder. */
331
+ function emitElem(
332
+ srcModule: SrcModule,
333
+ elem: AbstractElem,
336
334
  conditions: Conditions,
335
+ opts: { addNl?: boolean; skipConditionalFiltering?: boolean } = {},
337
336
  ): SrcMapBuilder {
338
- const { elem, srcModule } = s;
339
- const { src: text, debugFilePath: path } = srcModule;
340
- const builder = new SrcMapBuilder({ text, path });
341
- lowerAndEmit({ srcBuilder: builder, rootElems: [elem], conditions });
342
- builder.addNl();
343
- return builder;
344
- }
345
-
346
- /** Skip conditional filtering because findValidRootDecls already validated these declarations */
347
- function emitDecl(decl: DeclIdent, conditions: Conditions): SrcMapBuilder {
348
- const { src: text, debugFilePath: path } = decl.srcModule;
349
- const builder = new SrcMapBuilder({ text, path });
337
+ const builder = builderFromModule(srcModule);
350
338
  lowerAndEmit({
351
339
  srcBuilder: builder,
352
- rootElems: [decl.declElem!],
340
+ rootElems: [elem],
353
341
  conditions,
354
- skipConditionalFiltering: true,
342
+ skipConditionalFiltering: opts.skipConditionalFiltering,
355
343
  });
344
+ if (opts.addNl) builder.addNl();
356
345
  return builder;
357
346
  }
358
347
 
348
+ function builderFromModule(srcModule: SrcModule): SrcMapBuilder {
349
+ return new SrcMapBuilder({
350
+ text: srcModule.src,
351
+ path: srcModule.debugFilePath,
352
+ });
353
+ }
354
+
359
355
  /*
360
356
 
361
357
  LATER
package/src/LinkerUtil.ts CHANGED
@@ -1,22 +1,40 @@
1
1
  import type {
2
2
  AbstractElem,
3
- ContainerElem,
4
3
  DeclIdentElem,
4
+ ExpressionElem,
5
+ HasAttributes,
6
+ ModuleElem,
5
7
  RefIdentElem,
6
8
  } from "./AbstractElems.ts";
9
+ import { assertUnreachableSilent } from "./Assertions.ts";
7
10
  import { srcLog } from "./Logging.ts";
8
11
 
12
+ /** A module's top-level declarations of a given kind, narrowed to that elem type. */
13
+ export function declsOfKind<K extends AbstractElem["kind"]>(
14
+ moduleElem: ModuleElem,
15
+ kind: K,
16
+ ): Extract<AbstractElem, { kind: K }>[] {
17
+ return moduleElem.decls.filter(
18
+ (e): e is Extract<AbstractElem, { kind: K }> => e.kind === kind,
19
+ );
20
+ }
21
+
22
+ /** Visit an elem and all its descendants, parent before children (pre-order). */
9
23
  export function visitAst(
10
24
  elem: AbstractElem,
11
25
  visitor: (elem: AbstractElem) => void,
12
26
  ) {
13
27
  visitor(elem);
14
- if ((elem as ContainerElem).contents) {
15
- const container = elem as ContainerElem;
16
- container.contents.forEach(child => {
17
- visitAst(child, visitor);
18
- });
19
- }
28
+ for (const child of childElems(elem)) visitAst(child, visitor);
29
+ }
30
+
31
+ /**
32
+ * Child elems of any AST node: the typed structural fields (attributes plus
33
+ * body / condition / decls / ...) in source order. Returns [] for leaf elems.
34
+ */
35
+ export function childElems(elem: AbstractElem): readonly AbstractElem[] {
36
+ const attributes = (elem as HasAttributes).attributes ?? [];
37
+ return [...attributes, ...structuralFields(elem)];
20
38
  }
21
39
 
22
40
  export function identElemLog(
@@ -29,3 +47,119 @@ export function identElemLog(
29
47
  ...messages,
30
48
  );
31
49
  }
50
+
51
+ /** The child elems held in a node's typed fields, in source order (attributes
52
+ * are added separately by {@link childElems}). Empty for leaf kinds. The
53
+ * switch is exhaustive: a new elem kind without a case fails to compile in the
54
+ * default, so children can't be silently dropped. */
55
+ function structuralFields(elem: AbstractElem): AbstractElem[] {
56
+ switch (elem.kind) {
57
+ case "module":
58
+ return elem.decls;
59
+ case "var":
60
+ case "gvar":
61
+ return [
62
+ ...(elem.template ?? []),
63
+ elem.name,
64
+ ...(elem.init ? [elem.init] : []),
65
+ ];
66
+ case "let":
67
+ case "const":
68
+ case "override":
69
+ return [elem.name, ...(elem.init ? [elem.init] : [])];
70
+ case "alias":
71
+ return [elem.name, elem.typeRef];
72
+ case "assert":
73
+ return [elem.expression];
74
+ case "struct":
75
+ return [elem.name, ...elem.members];
76
+ case "member":
77
+ return [elem.name, elem.typeRef];
78
+ case "type":
79
+ return [elem.name.refIdentElem, ...(elem.templateParams ?? [])];
80
+ case "expression":
81
+ return [elem.expression];
82
+ case "binary-expression":
83
+ return [elem.left, elem.right];
84
+ case "unary-expression":
85
+ case "parenthesized-expression":
86
+ return [elem.expression];
87
+ case "component-expression":
88
+ return [elem.base, elem.access];
89
+ case "component-member-expression":
90
+ return [elem.base, elem.access];
91
+ case "call-expression":
92
+ return [elem.function, ...(elem.templateArgs ?? []), ...elem.arguments];
93
+ case "param":
94
+ return [elem.name];
95
+ case "typeDecl":
96
+ return elem.typeRef ? [elem.decl, elem.typeRef] : [elem.decl];
97
+ case "fn":
98
+ return [
99
+ elem.name,
100
+ ...elem.params,
101
+ ...(elem.returnType ? [elem.returnType] : []),
102
+ elem.body,
103
+ ];
104
+ case "block":
105
+ return elem.body;
106
+ case "if":
107
+ return [elem.condition, elem.body, ...(elem.else ? [elem.else] : [])];
108
+ case "for":
109
+ return [elem.init, elem.condition, elem.update, elem.body].filter(
110
+ isDefined,
111
+ );
112
+ case "while":
113
+ return [elem.condition, elem.body];
114
+ case "loop":
115
+ case "continuing":
116
+ return [elem.body];
117
+ case "switch":
118
+ return [elem.selector, ...(elem.bodyAttributes ?? []), ...elem.clauses];
119
+ case "switch-clause":
120
+ return [...exprSelectors(elem.selectors), elem.body];
121
+ case "return":
122
+ return elem.value ? [elem.value] : [];
123
+ case "break":
124
+ return elem.condition ? [elem.condition] : [];
125
+ case "assign":
126
+ return elem.lhs.kind === "phony" ? [elem.rhs] : [elem.lhs, elem.rhs];
127
+ case "increment":
128
+ case "decrement":
129
+ return [elem.target];
130
+ case "call":
131
+ return [elem.call];
132
+ case "continue":
133
+ case "discard":
134
+ case "empty":
135
+ return [];
136
+ case "do":
137
+ return [elem.name, ...elem.params, elem.body];
138
+ // import/directive hold no child elems in typed fields (their payload isn't
139
+ // an AbstractElem), but they carry attributes that childElems prepends.
140
+ case "import":
141
+ case "directive":
142
+ // leaf kinds: no child elems and no attributes.
143
+ case "attribute":
144
+ case "name":
145
+ case "literal":
146
+ case "ref":
147
+ case "decl":
148
+ case "synthetic":
149
+ return [];
150
+ default:
151
+ assertUnreachableSilent(elem);
152
+ return [];
153
+ }
154
+ }
155
+
156
+ function isDefined<T>(value: T | undefined): value is T {
157
+ return value !== undefined;
158
+ }
159
+
160
+ /** Drop the `"default"` sentinel, keeping only real case-selector expressions. */
161
+ function exprSelectors(
162
+ selectors: (ExpressionElem | "default")[],
163
+ ): ExpressionElem[] {
164
+ return selectors.filter((s): s is ExpressionElem => s !== "default");
165
+ }