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.
- package/README.md +31 -0
- package/dist/index.js +4468 -0
- package/dist/index.js.map +1 -0
- package/dist/minified.js +3426 -0
- package/dist/minified.js.map +1 -0
- package/dist/tools/packages/wesl/src/AbstractElems.d.ts +322 -0
- package/dist/tools/packages/wesl/src/Assertions.d.ts +27 -0
- package/dist/tools/packages/wesl/src/BindIdents.d.ts +70 -0
- package/dist/tools/packages/wesl/src/Conditions.d.ts +6 -0
- package/dist/tools/packages/wesl/src/FlattenTreeImport.d.ts +11 -0
- package/dist/tools/packages/wesl/src/LinkedWesl.d.ts +50 -0
- package/dist/tools/packages/wesl/src/Linker.d.ts +87 -0
- package/dist/tools/packages/wesl/src/LinkerUtil.d.ts +3 -0
- package/dist/tools/packages/wesl/src/LiveDeclarations.d.ts +12 -0
- package/dist/tools/packages/wesl/src/LowerAndEmit.d.ts +31 -0
- package/dist/tools/packages/wesl/src/Mangler.d.ts +39 -0
- package/dist/tools/packages/wesl/src/ParseWESL.d.ts +60 -0
- package/dist/tools/packages/wesl/src/ParsedRegistry.d.ts +29 -0
- package/dist/tools/packages/wesl/src/PathUtil.d.ts +6 -0
- package/dist/tools/packages/wesl/src/RawEmit.d.ts +6 -0
- package/dist/tools/packages/wesl/src/Reflection.d.ts +45 -0
- package/dist/tools/packages/wesl/src/Scope.d.ts +81 -0
- package/dist/tools/packages/wesl/src/StandardTypes.d.ts +13 -0
- package/dist/tools/packages/wesl/src/TransformBindingStructs.d.ts +52 -0
- package/dist/tools/packages/wesl/src/Util.d.ts +43 -0
- package/dist/tools/packages/wesl/src/WESLCollect.d.ts +94 -0
- package/dist/tools/packages/wesl/src/WeslBundle.d.ts +13 -0
- package/dist/tools/packages/wesl/src/WeslDevice.d.ts +25 -0
- package/dist/tools/packages/wesl/src/debug/ASTtoString.d.ts +5 -0
- package/dist/tools/packages/wesl/src/debug/ImportToString.d.ts +2 -0
- package/dist/tools/packages/wesl/src/debug/LineWrapper.d.ts +21 -0
- package/dist/tools/packages/wesl/src/debug/ScopeToString.d.ts +6 -0
- package/dist/tools/packages/wesl/src/index.d.ts +11 -0
- package/dist/tools/packages/wesl/src/parse/ImportGrammar.d.ts +5 -0
- package/dist/tools/packages/wesl/src/parse/Keywords.d.ts +4 -0
- package/dist/tools/packages/wesl/src/parse/WeslBaseGrammar.d.ts +5 -0
- package/dist/tools/packages/wesl/src/parse/WeslExpression.d.ts +13 -0
- package/dist/tools/packages/wesl/src/parse/WeslGrammar.d.ts +80 -0
- package/dist/tools/packages/wesl/src/parse/WeslStream.d.ts +44 -0
- package/dist/tools/packages/wesl/src/test/BindWESL.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ConditionLinking.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ConditionalTranslationCases.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ErrorLogging.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/Expression.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/FlattenTreeImport.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ImportCases.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ImportSyntaxCases.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/LinkGlob.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/LinkPackage.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/Linker.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/Mangling.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ParseComments.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ParseConditions.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ParseError.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ParseWESL.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/PathUtil.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/PrettyGrammar.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/Reflection.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/ScopeWESL.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/TestLink.d.ts +21 -0
- package/dist/tools/packages/wesl/src/test/TestSetup.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/TestUtil.d.ts +40 -0
- package/dist/tools/packages/wesl/src/test/Tokenizer.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/TransformBindingStructs.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/Util.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/VirtualModules.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/WeslDevice.test.d.ts +1 -0
- package/dist/tools/packages/wesl/src/test/WgslTests.d.ts +0 -0
- package/dist/tools/packages/wesl/src/vlq/vlq.d.ts +11 -0
- package/package.json +46 -0
- package/src/AbstractElems.ts +446 -0
- package/src/Assertions.ts +51 -0
- package/src/BindIdents.ts +523 -0
- package/src/Conditions.ts +74 -0
- package/src/FlattenTreeImport.ts +55 -0
- package/src/LinkedWesl.ts +184 -0
- package/src/Linker.ts +284 -0
- package/src/LinkerUtil.ts +29 -0
- package/src/LiveDeclarations.ts +31 -0
- package/src/LowerAndEmit.ts +413 -0
- package/src/Mangler.ts +94 -0
- package/src/ParseWESL.ts +157 -0
- package/src/ParsedRegistry.ts +120 -0
- package/src/PathUtil.ts +31 -0
- package/src/RawEmit.ts +102 -0
- package/src/Reflection.ts +334 -0
- package/src/Scope.ts +162 -0
- package/src/StandardTypes.ts +97 -0
- package/src/TransformBindingStructs.ts +319 -0
- package/src/Util.ts +194 -0
- package/src/WESLCollect.ts +614 -0
- package/src/WeslBundle.ts +16 -0
- package/src/WeslDevice.ts +209 -0
- package/src/debug/ASTtoString.ts +290 -0
- package/src/debug/ImportToString.ts +29 -0
- package/src/debug/LineWrapper.ts +70 -0
- package/src/debug/ScopeToString.ts +79 -0
- package/src/index.ts +11 -0
- package/src/parse/ImportGrammar.ts +157 -0
- package/src/parse/Keywords.ts +26 -0
- package/src/parse/WeslBaseGrammar.ts +8 -0
- package/src/parse/WeslExpression.ts +207 -0
- package/src/parse/WeslGrammar.ts +856 -0
- package/src/parse/WeslStream.ts +279 -0
- package/src/test/BindWESL.test.ts +57 -0
- package/src/test/ConditionLinking.test.ts +91 -0
- package/src/test/ConditionalTranslationCases.test.ts +56 -0
- package/src/test/ErrorLogging.test.ts +30 -0
- package/src/test/Expression.test.ts +22 -0
- package/src/test/FlattenTreeImport.test.ts +74 -0
- package/src/test/ImportCases.test.ts +56 -0
- package/src/test/ImportSyntaxCases.test.ts +24 -0
- package/src/test/LinkGlob.test.ts +25 -0
- package/src/test/LinkPackage.test.ts +26 -0
- package/src/test/Linker.test.ts +125 -0
- package/src/test/Mangling.test.ts +45 -0
- package/src/test/ParseComments.test.ts +36 -0
- package/src/test/ParseConditions.test.ts +183 -0
- package/src/test/ParseError.test.ts +36 -0
- package/src/test/ParseWESL.test.ts +1572 -0
- package/src/test/PathUtil.test.ts +34 -0
- package/src/test/PrettyGrammar.test.ts +20 -0
- package/src/test/Reflection.test.ts +172 -0
- package/src/test/ScopeWESL.test.ts +462 -0
- package/src/test/TestLink.ts +82 -0
- package/src/test/TestSetup.ts +4 -0
- package/src/test/TestUtil.ts +126 -0
- package/src/test/Tokenizer.test.ts +135 -0
- package/src/test/TransformBindingStructs.test.ts +230 -0
- package/src/test/Util.test.ts +22 -0
- package/src/test/VirtualModules.test.ts +37 -0
- package/src/test/WeslDevice.test.ts +265 -0
- package/src/test/WgslTests.ts +0 -0
- package/src/test/__snapshots__/ParseDirectives.test.ts.snap +25 -0
- package/src/test/__snapshots__/ParseWESL.test.ts.snap +119 -0
- package/src/test/__snapshots__/RustDirective.test.ts.snap +359 -0
- package/src/test/wgsl_1/main.wgsl +3 -0
- package/src/test/wgsl_1/util.wgsl +1 -0
- package/src/test/wgsl_2/main2.wgsl +3 -0
- package/src/test/wgsl_2/util2.wgsl +1 -0
- 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
|
+
*/
|