wesl 0.6.0-pre10

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 (141) hide show
  1. package/README.md +31 -0
  2. package/dist/index.js +4468 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/minified.js +3426 -0
  5. package/dist/minified.js.map +1 -0
  6. package/dist/tools/packages/wesl/src/AbstractElems.d.ts +322 -0
  7. package/dist/tools/packages/wesl/src/Assertions.d.ts +27 -0
  8. package/dist/tools/packages/wesl/src/BindIdents.d.ts +70 -0
  9. package/dist/tools/packages/wesl/src/Conditions.d.ts +6 -0
  10. package/dist/tools/packages/wesl/src/FlattenTreeImport.d.ts +11 -0
  11. package/dist/tools/packages/wesl/src/LinkedWesl.d.ts +50 -0
  12. package/dist/tools/packages/wesl/src/Linker.d.ts +87 -0
  13. package/dist/tools/packages/wesl/src/LinkerUtil.d.ts +3 -0
  14. package/dist/tools/packages/wesl/src/LiveDeclarations.d.ts +12 -0
  15. package/dist/tools/packages/wesl/src/LowerAndEmit.d.ts +31 -0
  16. package/dist/tools/packages/wesl/src/Mangler.d.ts +39 -0
  17. package/dist/tools/packages/wesl/src/ParseWESL.d.ts +60 -0
  18. package/dist/tools/packages/wesl/src/ParsedRegistry.d.ts +29 -0
  19. package/dist/tools/packages/wesl/src/PathUtil.d.ts +6 -0
  20. package/dist/tools/packages/wesl/src/RawEmit.d.ts +6 -0
  21. package/dist/tools/packages/wesl/src/Reflection.d.ts +45 -0
  22. package/dist/tools/packages/wesl/src/Scope.d.ts +81 -0
  23. package/dist/tools/packages/wesl/src/StandardTypes.d.ts +13 -0
  24. package/dist/tools/packages/wesl/src/TransformBindingStructs.d.ts +52 -0
  25. package/dist/tools/packages/wesl/src/Util.d.ts +43 -0
  26. package/dist/tools/packages/wesl/src/WESLCollect.d.ts +94 -0
  27. package/dist/tools/packages/wesl/src/WeslBundle.d.ts +13 -0
  28. package/dist/tools/packages/wesl/src/WeslDevice.d.ts +25 -0
  29. package/dist/tools/packages/wesl/src/debug/ASTtoString.d.ts +5 -0
  30. package/dist/tools/packages/wesl/src/debug/ImportToString.d.ts +2 -0
  31. package/dist/tools/packages/wesl/src/debug/LineWrapper.d.ts +21 -0
  32. package/dist/tools/packages/wesl/src/debug/ScopeToString.d.ts +6 -0
  33. package/dist/tools/packages/wesl/src/index.d.ts +11 -0
  34. package/dist/tools/packages/wesl/src/parse/ImportGrammar.d.ts +5 -0
  35. package/dist/tools/packages/wesl/src/parse/Keywords.d.ts +4 -0
  36. package/dist/tools/packages/wesl/src/parse/WeslBaseGrammar.d.ts +5 -0
  37. package/dist/tools/packages/wesl/src/parse/WeslExpression.d.ts +13 -0
  38. package/dist/tools/packages/wesl/src/parse/WeslGrammar.d.ts +80 -0
  39. package/dist/tools/packages/wesl/src/parse/WeslStream.d.ts +44 -0
  40. package/dist/tools/packages/wesl/src/test/BindWESL.test.d.ts +1 -0
  41. package/dist/tools/packages/wesl/src/test/ConditionLinking.test.d.ts +1 -0
  42. package/dist/tools/packages/wesl/src/test/ConditionalTranslationCases.test.d.ts +1 -0
  43. package/dist/tools/packages/wesl/src/test/ErrorLogging.test.d.ts +1 -0
  44. package/dist/tools/packages/wesl/src/test/Expression.test.d.ts +1 -0
  45. package/dist/tools/packages/wesl/src/test/FlattenTreeImport.test.d.ts +1 -0
  46. package/dist/tools/packages/wesl/src/test/ImportCases.test.d.ts +1 -0
  47. package/dist/tools/packages/wesl/src/test/ImportSyntaxCases.test.d.ts +1 -0
  48. package/dist/tools/packages/wesl/src/test/LinkGlob.test.d.ts +1 -0
  49. package/dist/tools/packages/wesl/src/test/LinkPackage.test.d.ts +1 -0
  50. package/dist/tools/packages/wesl/src/test/Linker.test.d.ts +1 -0
  51. package/dist/tools/packages/wesl/src/test/Mangling.test.d.ts +1 -0
  52. package/dist/tools/packages/wesl/src/test/ParseComments.test.d.ts +1 -0
  53. package/dist/tools/packages/wesl/src/test/ParseConditions.test.d.ts +1 -0
  54. package/dist/tools/packages/wesl/src/test/ParseError.test.d.ts +1 -0
  55. package/dist/tools/packages/wesl/src/test/ParseWESL.test.d.ts +1 -0
  56. package/dist/tools/packages/wesl/src/test/PathUtil.test.d.ts +1 -0
  57. package/dist/tools/packages/wesl/src/test/PrettyGrammar.test.d.ts +1 -0
  58. package/dist/tools/packages/wesl/src/test/Reflection.test.d.ts +1 -0
  59. package/dist/tools/packages/wesl/src/test/ScopeWESL.test.d.ts +1 -0
  60. package/dist/tools/packages/wesl/src/test/TestLink.d.ts +21 -0
  61. package/dist/tools/packages/wesl/src/test/TestSetup.d.ts +1 -0
  62. package/dist/tools/packages/wesl/src/test/TestUtil.d.ts +40 -0
  63. package/dist/tools/packages/wesl/src/test/Tokenizer.test.d.ts +1 -0
  64. package/dist/tools/packages/wesl/src/test/TransformBindingStructs.test.d.ts +1 -0
  65. package/dist/tools/packages/wesl/src/test/Util.test.d.ts +1 -0
  66. package/dist/tools/packages/wesl/src/test/VirtualModules.test.d.ts +1 -0
  67. package/dist/tools/packages/wesl/src/test/WeslDevice.test.d.ts +1 -0
  68. package/dist/tools/packages/wesl/src/test/WgslTests.d.ts +0 -0
  69. package/dist/tools/packages/wesl/src/vlq/vlq.d.ts +11 -0
  70. package/package.json +46 -0
  71. package/src/AbstractElems.ts +446 -0
  72. package/src/Assertions.ts +51 -0
  73. package/src/BindIdents.ts +523 -0
  74. package/src/Conditions.ts +74 -0
  75. package/src/FlattenTreeImport.ts +55 -0
  76. package/src/LinkedWesl.ts +184 -0
  77. package/src/Linker.ts +284 -0
  78. package/src/LinkerUtil.ts +29 -0
  79. package/src/LiveDeclarations.ts +31 -0
  80. package/src/LowerAndEmit.ts +413 -0
  81. package/src/Mangler.ts +94 -0
  82. package/src/ParseWESL.ts +157 -0
  83. package/src/ParsedRegistry.ts +120 -0
  84. package/src/PathUtil.ts +31 -0
  85. package/src/RawEmit.ts +102 -0
  86. package/src/Reflection.ts +334 -0
  87. package/src/Scope.ts +162 -0
  88. package/src/StandardTypes.ts +97 -0
  89. package/src/TransformBindingStructs.ts +319 -0
  90. package/src/Util.ts +194 -0
  91. package/src/WESLCollect.ts +614 -0
  92. package/src/WeslBundle.ts +16 -0
  93. package/src/WeslDevice.ts +209 -0
  94. package/src/debug/ASTtoString.ts +290 -0
  95. package/src/debug/ImportToString.ts +29 -0
  96. package/src/debug/LineWrapper.ts +70 -0
  97. package/src/debug/ScopeToString.ts +79 -0
  98. package/src/index.ts +11 -0
  99. package/src/parse/ImportGrammar.ts +157 -0
  100. package/src/parse/Keywords.ts +26 -0
  101. package/src/parse/WeslBaseGrammar.ts +8 -0
  102. package/src/parse/WeslExpression.ts +207 -0
  103. package/src/parse/WeslGrammar.ts +856 -0
  104. package/src/parse/WeslStream.ts +279 -0
  105. package/src/test/BindWESL.test.ts +57 -0
  106. package/src/test/ConditionLinking.test.ts +91 -0
  107. package/src/test/ConditionalTranslationCases.test.ts +56 -0
  108. package/src/test/ErrorLogging.test.ts +30 -0
  109. package/src/test/Expression.test.ts +22 -0
  110. package/src/test/FlattenTreeImport.test.ts +74 -0
  111. package/src/test/ImportCases.test.ts +56 -0
  112. package/src/test/ImportSyntaxCases.test.ts +24 -0
  113. package/src/test/LinkGlob.test.ts +25 -0
  114. package/src/test/LinkPackage.test.ts +26 -0
  115. package/src/test/Linker.test.ts +125 -0
  116. package/src/test/Mangling.test.ts +45 -0
  117. package/src/test/ParseComments.test.ts +36 -0
  118. package/src/test/ParseConditions.test.ts +183 -0
  119. package/src/test/ParseError.test.ts +36 -0
  120. package/src/test/ParseWESL.test.ts +1572 -0
  121. package/src/test/PathUtil.test.ts +34 -0
  122. package/src/test/PrettyGrammar.test.ts +20 -0
  123. package/src/test/Reflection.test.ts +172 -0
  124. package/src/test/ScopeWESL.test.ts +462 -0
  125. package/src/test/TestLink.ts +82 -0
  126. package/src/test/TestSetup.ts +4 -0
  127. package/src/test/TestUtil.ts +126 -0
  128. package/src/test/Tokenizer.test.ts +135 -0
  129. package/src/test/TransformBindingStructs.test.ts +230 -0
  130. package/src/test/Util.test.ts +22 -0
  131. package/src/test/VirtualModules.test.ts +37 -0
  132. package/src/test/WeslDevice.test.ts +265 -0
  133. package/src/test/WgslTests.ts +0 -0
  134. package/src/test/__snapshots__/ParseDirectives.test.ts.snap +25 -0
  135. package/src/test/__snapshots__/ParseWESL.test.ts.snap +119 -0
  136. package/src/test/__snapshots__/RustDirective.test.ts.snap +359 -0
  137. package/src/test/wgsl_1/main.wgsl +3 -0
  138. package/src/test/wgsl_1/util.wgsl +1 -0
  139. package/src/test/wgsl_2/main2.wgsl +3 -0
  140. package/src/test/wgsl_2/util2.wgsl +1 -0
  141. package/src/vlq/vlq.ts +94 -0
@@ -0,0 +1,184 @@
1
+ import { SrcMap } from "mini-parse";
2
+ import { assertThat } from "../../mini-parse/src/Assertions";
3
+ import { errorHighlight, offsetToLineNumber } from "./Util";
4
+ import type { WeslDevice } from "./WeslDevice";
5
+
6
+ /** Results of shader compilation. Has {@link WeslGPUCompilationMessage}
7
+ * which are aware of the WESL module that an error was thrown from. */
8
+ export interface WeslGPUCompilationInfo extends GPUCompilationInfo {
9
+ messages: WeslGPUCompilationMessage[];
10
+ }
11
+
12
+ export interface WeslGPUCompilationMessage extends GPUCompilationMessage {
13
+ module: {
14
+ // LATER this should be a qualified module path.
15
+ // And something else should map it to a URL that is relative to the correct place.
16
+ url: string;
17
+ // LATER: I don't think that the text should be a part of the compilation message.
18
+ // Instead the module url should be usable as a key.
19
+ text?: string;
20
+ };
21
+ }
22
+
23
+ /**
24
+ * A {@link GPUValidationError} with an inner error (for a stack trace).
25
+ * Can also point at a WESL source file.
26
+ */
27
+ export interface ExtendedGPUValidationError extends GPUValidationError {
28
+ cause?: Error;
29
+ compilationInfo?: WeslGPUCompilationInfo;
30
+ }
31
+
32
+ /**
33
+ * Multiple WESL files that have been linked together to produce WGSL code.
34
+ *
35
+ * Call {@link LinkedWesl.createShaderModule} on a {@link WeslDevice}
36
+ * to make the error reporting aware of the WESL code.
37
+ */
38
+ export class LinkedWesl {
39
+ constructor(public sourceMap: SrcMap) {}
40
+
41
+ /**
42
+ * Creates a {@link GPUShaderModule}.
43
+ * When errors occur, they will point at the original WESL source code.
44
+ *
45
+ * The compilation info {@link GPUShaderModule.getCompilationInfo}
46
+ * can be remapped with {@link mapGPUCompilationInfo}
47
+ * @param device GPUDevice. Preferably a {@link WeslDevice} for better error reporting.
48
+ * @param descriptor - Description of the {@link GPUShaderModule} to create.
49
+ */
50
+ createShaderModule(
51
+ device: GPUDevice | WeslDevice,
52
+ descriptor: Omit<GPUShaderModuleDescriptor, "code">,
53
+ ): GPUShaderModule {
54
+ // Skip the custom behaviour if we do not have a WESL device.
55
+ if (!("injectError" in device)) {
56
+ return device.createShaderModule({
57
+ ...descriptor,
58
+ code: this.dest,
59
+ });
60
+ }
61
+
62
+ device.pushErrorScope("validation"); // Suppress the normal error
63
+ const module = device.createShaderModule({
64
+ ...descriptor,
65
+ code: this.dest,
66
+ });
67
+ device.popErrorScope();
68
+ // And report the error!
69
+ let { promise, resolve } = Promise.withResolvers<GPUError | null>();
70
+ device.injectError("validation", promise); // Inject our custom error
71
+ module.getCompilationInfo().then(compilationInfo => {
72
+ if (compilationInfo.messages.length === 0) {
73
+ resolve(null);
74
+ return;
75
+ }
76
+
77
+ const mappedCompilationInfo = this.mapGPUCompilationInfo(compilationInfo);
78
+ const errorMessage = compilationInfoToErrorMessage(
79
+ mappedCompilationInfo,
80
+ module,
81
+ );
82
+ // Error message cannot be null, since we're passing at least one message to it.
83
+ assertThat(errorMessage !== null);
84
+ const error: ExtendedGPUValidationError = new GPUValidationError(
85
+ errorMessage,
86
+ );
87
+ error.cause = new Error("createShaderModule failed");
88
+ error.compilationInfo = mappedCompilationInfo;
89
+ resolve(error);
90
+ });
91
+ return module;
92
+ }
93
+
94
+ /**
95
+ * Use {@link LinkedWesl.createShaderModule} for a
96
+ * better error reporting experience.
97
+ */
98
+ get dest() {
99
+ return this.sourceMap.dest.text;
100
+ }
101
+
102
+ /** Turns raw compilation info into compilation info
103
+ * that points at the WESL sources. */
104
+ public mapGPUCompilationInfo(
105
+ compilationInfo: GPUCompilationInfo,
106
+ ): WeslGPUCompilationInfo {
107
+ return {
108
+ __brand: compilationInfo.__brand,
109
+ messages: compilationInfo.messages.map(v =>
110
+ this.mapGPUCompilationMessage(v),
111
+ ),
112
+ };
113
+ }
114
+
115
+ private mapGPUCompilationMessage(
116
+ message: GPUCompilationMessage,
117
+ ): WeslGPUCompilationMessage {
118
+ const srcMap = this.sourceMap;
119
+ const srcPosition = srcMap.destToSrc(message.offset);
120
+ // LATER what if this gets mapped to a completely different place?
121
+ const srcEndPosition =
122
+ message.length > 0 ?
123
+ srcMap.destToSrc(message.offset + message.length)
124
+ : srcPosition;
125
+ const length = srcEndPosition.position - srcPosition.position;
126
+
127
+ let [lineNum, linePos] = offsetToLineNumber(
128
+ srcPosition.position,
129
+ srcPosition.src.text,
130
+ );
131
+
132
+ return {
133
+ __brand: message.__brand,
134
+ type: message.type,
135
+ message: message.message,
136
+ offset: srcPosition.position,
137
+ length,
138
+ lineNum,
139
+ linePos,
140
+ module: {
141
+ url: srcPosition.src.path ?? "",
142
+ text: srcPosition.src.text,
143
+ },
144
+ };
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Tries to imitate the way the browser logs the compilation info.
150
+ * Does not do the remapping.
151
+ * @returns A string with errors, or `null` if there were no compilation messages.
152
+ */
153
+ function compilationInfoToErrorMessage(
154
+ compilationInfo: WeslGPUCompilationInfo,
155
+ shaderModule: GPUShaderModule,
156
+ ): string | null {
157
+ if (compilationInfo.messages.length === 0) return null;
158
+
159
+ let result = `Compilation log for [Invalid ShaderModule (${
160
+ shaderModule.label || "unlabled"
161
+ })]:\n`;
162
+ let errorCount = compilationInfo.messages.filter(
163
+ v => v.type === "error",
164
+ ).length;
165
+ if (errorCount > 0) {
166
+ result += `${errorCount} error(s) generated while compiling the shader:\n`;
167
+ }
168
+ for (const message of compilationInfo.messages) {
169
+ const { lineNum, linePos } = message;
170
+
171
+ result += `${message.module.url}:${lineNum}:${linePos}`;
172
+ result += ` ${message.type}: ${message.message}\n`;
173
+ // LATER unmangle code snippets in the message
174
+
175
+ const source = message.module.text;
176
+ if (source) {
177
+ result += errorHighlight(source, [
178
+ message.offset,
179
+ message.offset + message.length,
180
+ ]).join("\n");
181
+ }
182
+ }
183
+ return result;
184
+ }
package/src/Linker.ts ADDED
@@ -0,0 +1,284 @@
1
+ import { SrcMap, SrcMapBuilder, tracing } from "mini-parse";
2
+ import { AbstractElem, ModuleElem } from "./AbstractElems.ts";
3
+ import { bindIdents, EmittableElem } from "./BindIdents.ts";
4
+ import { LinkedWesl } from "./LinkedWesl.ts";
5
+ import { lowerAndEmit } from "./LowerAndEmit.ts";
6
+ import { ManglerFn } from "./Mangler.ts";
7
+ import {
8
+ parsedRegistry,
9
+ ParsedRegistry,
10
+ parseIntoRegistry,
11
+ parseLibsIntoRegistry,
12
+ selectModule,
13
+ } from "./ParsedRegistry.ts";
14
+ import { WeslAST } from "./ParseWESL.ts";
15
+ import { Conditions, DeclIdent, SrcModule } from "./Scope.ts";
16
+ import { filterMap, mapValues } from "./Util.ts";
17
+ import { WeslBundle } from "./WeslBundle.ts";
18
+
19
+ type LinkerTransform = (boundAST: TransformedAST) => TransformedAST;
20
+
21
+ export interface WeslJsPlugin {
22
+ transform?: LinkerTransform;
23
+ }
24
+
25
+ export interface TransformedAST
26
+ extends Pick<WeslAST, "srcModule" | "moduleElem"> {
27
+ globalNames: Set<string>;
28
+ notableElems: Record<string, AbstractElem[]>;
29
+ }
30
+
31
+ export interface LinkConfig {
32
+ plugins?: WeslJsPlugin[];
33
+ }
34
+
35
+ export interface LinkParams {
36
+ /** record of file paths and wesl text for modules.
37
+ * key is module path or file path
38
+ * `package::foo::bar`, or './foo/bar.wesl', or './foo/bar'
39
+ * value is wesl src
40
+ *
41
+ *
42
+ * Only accepts unix-style, relative filesystem paths that are valid WGSL identifiers
43
+ * - Unix-style: Slashes as separators.
44
+ * - Valid WGSL identifiers: No backslashes, no `..`, or other non-identifier symbols.
45
+ * - Relative paths: They have to be relative to the wesl root.
46
+ */
47
+ weslSrc: Record<string, string>;
48
+
49
+ /** name of root wesl module
50
+ * for an app, the root module normally contains the '@compute', '@vertex' or '@fragment' entry points
51
+ * for a library, the root module defines the public api fo the library
52
+ * can be specified as file path (./main.wesl), a module path (package::main), or just a module name (main)
53
+ */
54
+ rootModuleName?: string;
55
+
56
+ /** For debug logging. Will be prepended to file paths. */
57
+ debugWeslRoot?: string;
58
+
59
+ /** runtime conditions for conditional compiling with @if and friends */
60
+ conditions?: Conditions;
61
+
62
+ /** libraries available for the link */
63
+ libs?: WeslBundle[];
64
+
65
+ /** generate wesl from code at runtime */
66
+ virtualLibs?: Record<string, VirtualLibraryFn>;
67
+
68
+ /** plugins and other configuration to use while linking */
69
+ config?: LinkConfig;
70
+
71
+ /** Host (ts/js) provided wgsl constants.
72
+ * Users can import the values from wesl code via the `constants' virtual library:
73
+ * `import constants::num_lights;` */
74
+ constants?: Record<string, string | number>;
75
+
76
+ /** function to construct globally unique wgsl identifiers */
77
+ mangler?: ManglerFn;
78
+ }
79
+
80
+ /** Generate a virtual WESL module based on a set of conditions. */
81
+ export type VirtualLibraryFn = (conditions: Conditions) => string;
82
+
83
+ /**
84
+ * Link a set of WESL source modules (typically the text from .wesl files) into a single WGSL string.
85
+ * Linking starts with a specified 'root' source module, and recursively incorporates code
86
+ * referenced from other modules (in local files or libraries).
87
+ *
88
+ * Unreferenced (dead) code outside the root module is not included in the output WGSL.
89
+ * Additionally the caller can specify conditions for to control conditional compilation.
90
+ * Only code that is valid with the current conditions is included in the output.
91
+ */
92
+ export async function link(params: LinkParams): Promise<LinkedWesl> {
93
+ const { weslSrc, debugWeslRoot, libs = [] } = params;
94
+ const registry = parsedRegistry();
95
+ parseIntoRegistry(weslSrc, registry, "package", debugWeslRoot);
96
+ parseLibsIntoRegistry(libs, registry);
97
+ return new LinkedWesl(linkRegistry({ registry, ...params }));
98
+ }
99
+
100
+ export interface LinkRegistryParams
101
+ extends Pick<
102
+ LinkParams,
103
+ | "rootModuleName"
104
+ | "conditions"
105
+ | "virtualLibs"
106
+ | "config"
107
+ | "constants"
108
+ | "mangler"
109
+ > {
110
+ registry: ParsedRegistry;
111
+ }
112
+
113
+ /** Link wesl from a registry of already parsed modules.
114
+ *
115
+ * This entry point is intended for users who want to link multiple times
116
+ * from the same sources. (e.g. linking with different conditions
117
+ * each time, or perhaps to produce multiple wgsl shaders
118
+ * that share some sources.)
119
+ */
120
+ export function linkRegistry(params: LinkRegistryParams): SrcMap {
121
+ const bound = bindAndTransform(params);
122
+ const { transformedAst, newDecls, newStatements } = bound;
123
+
124
+ return SrcMapBuilder.build(
125
+ emitWgsl(
126
+ transformedAst.moduleElem,
127
+ transformedAst.srcModule,
128
+ newDecls,
129
+ newStatements,
130
+ params.conditions,
131
+ ),
132
+ );
133
+ }
134
+
135
+ export interface BoundAndTransformed {
136
+ transformedAst: TransformedAST;
137
+ newDecls: DeclIdent[];
138
+ newStatements: EmittableElem[];
139
+ }
140
+
141
+ /** bind identifers and apply any transform plugins */
142
+ export function bindAndTransform(
143
+ params: LinkRegistryParams,
144
+ ): BoundAndTransformed {
145
+ const { registry, mangler } = params;
146
+ const { rootModuleName = "main", conditions = {} } = params;
147
+ const rootAst = getRootModule(registry, rootModuleName);
148
+
149
+ // setup virtual modules from code generation or host constants provided by the user
150
+ const { constants, config } = params;
151
+ let { virtualLibs } = params;
152
+ if (constants) {
153
+ virtualLibs = { ...virtualLibs, constants: constantsGenerator(constants) };
154
+ }
155
+ let virtuals = virtualLibs && mapValues(virtualLibs, fn => ({ fn }));
156
+
157
+ /* --- Step #2 Binding Idents --- */
158
+ // link active Ident references to declarations, and uniquify global declarations
159
+ const bindParams = { rootAst, registry, conditions, virtuals, mangler };
160
+ const bindResults = bindIdents(bindParams);
161
+ const { globalNames, decls: newDecls, newStatements } = bindResults;
162
+
163
+ const transformedAst = applyTransformPlugins(rootAst, globalNames, config);
164
+ return { transformedAst, newDecls, newStatements };
165
+ }
166
+
167
+ function constantsGenerator(
168
+ constants: Record<string, string | number>,
169
+ ): () => string {
170
+ return () =>
171
+ Object.entries(constants)
172
+ .map(([name, value]) => `const ${name} = ${value};`)
173
+ .join("\n");
174
+ }
175
+
176
+ /** get a reference to the root module, selecting by module name */
177
+ function getRootModule(
178
+ parsed: ParsedRegistry,
179
+ rootModuleName: string,
180
+ ): WeslAST {
181
+ const rootModule = selectModule(parsed, rootModuleName);
182
+ if (!rootModule) {
183
+ if (tracing) {
184
+ console.log(`parsed modules: ${Object.keys(parsed.modules)}`);
185
+ console.log(`root module not found: ${rootModuleName}`);
186
+ }
187
+ throw new Error(`Root module not found: ${rootModuleName}`);
188
+ }
189
+ return rootModule;
190
+ }
191
+
192
+ /** run any plugins that transform the AST */
193
+ function applyTransformPlugins(
194
+ rootModule: WeslAST,
195
+ globalNames: Set<string>,
196
+ config?: LinkConfig,
197
+ ): TransformedAST {
198
+ const { moduleElem, srcModule } = rootModule;
199
+
200
+ // for now only transform the root module
201
+ const startAst = { moduleElem, srcModule, globalNames, notableElems: {} };
202
+ const plugins = config?.plugins ?? [];
203
+ const transforms = filterMap(plugins, plugin => plugin.transform);
204
+ const transformedAst = transforms.reduce(
205
+ (ast, transform) => transform(ast),
206
+ startAst,
207
+ );
208
+
209
+ return transformedAst;
210
+ }
211
+
212
+ /** traverse the AST and emit WGSL */
213
+ function emitWgsl(
214
+ rootModuleElem: ModuleElem,
215
+ srcModule: SrcModule,
216
+ newDecls: DeclIdent[],
217
+ newStatements: EmittableElem[],
218
+ conditions: Conditions = {},
219
+ ): SrcMapBuilder[] {
220
+ /* --- Step #3 Writing WGSL --- */ // note doesn't require the scope tree anymore
221
+
222
+ // emit any new statements (module level const asserts)
223
+ const prologueBuilders = newStatements.map(s => {
224
+ const { elem, srcModule } = s;
225
+ const { src: text, debugFilePath: path } = srcModule;
226
+ const builder = new SrcMapBuilder({ text, path });
227
+ lowerAndEmit(builder, [elem], conditions);
228
+ builder.addNl();
229
+ return builder;
230
+ });
231
+
232
+ const rootBuilder = new SrcMapBuilder({
233
+ text: srcModule.src,
234
+ path: srcModule.debugFilePath,
235
+ });
236
+ lowerAndEmit(rootBuilder, [rootModuleElem], conditions, false); // emit the entire root module
237
+
238
+ const declBuilders = newDecls.map(decl => {
239
+ const builder = new SrcMapBuilder({
240
+ text: decl.srcModule.src,
241
+ path: decl.srcModule.debugFilePath,
242
+ });
243
+ lowerAndEmit(builder, [decl.declElem!], conditions); // emit referenced declarations from other modules
244
+ return builder;
245
+ });
246
+
247
+ return [...prologueBuilders, rootBuilder, ...declBuilders];
248
+ }
249
+
250
+ /* ---- Commentary on present and future features ---- */
251
+ /*
252
+
253
+ TODO
254
+ - distinguish between global and local declaration idents (only global ones need be uniquified)
255
+
256
+ Conditions
257
+ - conditions are attached to the AST elements where they are defined
258
+ - only conditionally valid elements are emitted
259
+ - consolidated conditions are attached to Idents
260
+ - only conditionally valid ref Idents are bound, and only to conditionaly valid declarations
261
+ - a condition stack (akin to the scope stack) is maintained while parsing to attach consolidated conditions to Idents
262
+ - re-linking with new conditions, conservatively
263
+ - clear all mutated Ident fields (refersTo and mangled links)
264
+ - re-bind Idents, re-emit
265
+
266
+ Generics & specialization
267
+ - attach generic parameters to ref and decl Idents, effectively creating a new Ident for each specialization
268
+ - generate specialized elements at emit time, by checking the generic parameters of the decl ident
269
+
270
+ Incrementally rebuilding
271
+ - unchanged files don't need to be reparsed, only reparse dirty files.
272
+ - support reflection only mode? no need to bind idents or emit for e.g. vite/IDE plugin generating reflection types
273
+
274
+ Parallel Processing (coarse grained via webworkers)
275
+ - Parsing each module can be done in parallel
276
+ - binding could be done partially in parallel? (esbuild doesn't parallelize here though)
277
+ - finding the declaration for each local ident could be done in parallel by module
278
+ - matching
279
+ - Emitting could be easily modified to be done in partially in parallel
280
+ - traversing the AST to list the top level elements to emit could be done serially
281
+ - the text for each top level element could be emitted in parallel (presumably the bulk of the work)
282
+ - the merged text can be assembled serially
283
+
284
+ */
@@ -0,0 +1,29 @@
1
+ import { srcLog } from "mini-parse";
2
+ import {
3
+ AbstractElem,
4
+ ContainerElem,
5
+ DeclIdentElem,
6
+ RefIdentElem,
7
+ } from "./AbstractElems.ts";
8
+
9
+ export function visitAst(
10
+ elem: AbstractElem,
11
+ visitor: (elem: AbstractElem) => void,
12
+ ) {
13
+ visitor(elem);
14
+ if ((elem as ContainerElem).contents) {
15
+ const container = elem as ContainerElem;
16
+ container.contents.forEach(child => visitAst(child, visitor));
17
+ }
18
+ }
19
+
20
+ export function identElemLog(
21
+ identElem: DeclIdentElem | RefIdentElem,
22
+ ...messages: any[]
23
+ ): void {
24
+ srcLog(
25
+ identElem.srcModule.src,
26
+ [identElem.start, identElem.end],
27
+ ...messages,
28
+ );
29
+ }
@@ -0,0 +1,31 @@
1
+ import { identToString } from "./debug/ScopeToString.ts";
2
+ import { DeclIdent } from "./Scope.ts";
3
+
4
+ /** decls currently visible in this scope */
5
+ export interface LiveDecls {
6
+ /** decls currently visible in this scope */
7
+ decls: Map<string, DeclIdent>;
8
+
9
+ /** live decls in the parent scope. null for the modue root scope */
10
+ parent?: LiveDecls | null;
11
+ }
12
+
13
+ /** create a LiveDecls */
14
+ export function makeLiveDecls(parent: LiveDecls | null = null): LiveDecls {
15
+ return { decls: new Map<string, DeclIdent>(), parent };
16
+ }
17
+
18
+ /** debug routine for logging LiveDecls */
19
+ export function liveDeclsToString(liveDecls: LiveDecls): string {
20
+ const { decls, parent } = liveDecls;
21
+ const declsStr = Array.from(decls.entries())
22
+ .map(([name, decl]) => `${name}:${identToString(decl)}`)
23
+ .join(", ");
24
+ const parentStr = parent ? liveDeclsToString(parent) : "null";
25
+ return `decls: { ${declsStr} }, parent: ${parentStr}`;
26
+ }
27
+
28
+ /*
29
+ LATER try not creating a map for small scopes.
30
+ Instead just track the current live index in the scope array.
31
+ */