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,209 @@
|
|
|
1
|
+
import { ExtendedGPUValidationError } from "./LinkedWesl";
|
|
2
|
+
import { encodeVlq } from "./vlq/vlq";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* We want the WebGPU compilation errors to point at WESL code.
|
|
6
|
+
* The native facilities are `device.pushErrorScope`, `device.popErrorScope`
|
|
7
|
+
* and `device.addEventListener("uncapturederror", (ev) => {})`
|
|
8
|
+
*
|
|
9
|
+
* So we track the error scopes.
|
|
10
|
+
* Then, when creating a shader module from WESL code, we forcibly capture the errors.
|
|
11
|
+
* And then re-emit them to the nearest validation error scope.
|
|
12
|
+
* If there isn't one, we throw it as an uncapturederror
|
|
13
|
+
*/
|
|
14
|
+
type ErrorScope = {
|
|
15
|
+
filter: GPUErrorFilter;
|
|
16
|
+
errors: Promise<GPUError | null>[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A {@link GPUDevice} with extensions for WESL. Created with {@link makeWeslDevice}.
|
|
21
|
+
* Used to make error reporting point at the orignal WESL sources.
|
|
22
|
+
*/
|
|
23
|
+
export interface WeslDevice extends GPUDevice {
|
|
24
|
+
/**
|
|
25
|
+
* Attaches an error to the current error scope (created by {@link GPUDevice.pushErrorScope}).
|
|
26
|
+
* If there is no error scope, it reports the error as a `'uncapturederror'`
|
|
27
|
+
*/
|
|
28
|
+
injectError(type: GPUErrorFilter, error: Promise<GPUError | null>): void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Mutates a {@link GPUDevice} for usage with WESL. Does not impact your existing code, wherever a {@link GPUDevice} can be used, a {@link WeslDevice} is also valid.
|
|
33
|
+
*
|
|
34
|
+
* WESL uses this to display errors pointing at the WESL source instead of pointing at generated code.
|
|
35
|
+
*/
|
|
36
|
+
export function makeWeslDevice(device: GPUDevice): WeslDevice {
|
|
37
|
+
const errorScopeStack: ErrorScope[] = [];
|
|
38
|
+
|
|
39
|
+
(device as WeslDevice).injectError = (type, error) => {
|
|
40
|
+
const errorScope = errorScopeStack.findLast(v => v.filter === type);
|
|
41
|
+
if (errorScope !== undefined) {
|
|
42
|
+
errorScope.errors.push(error);
|
|
43
|
+
} else {
|
|
44
|
+
error.then(e => {
|
|
45
|
+
if (e !== null) {
|
|
46
|
+
dispatchError(e);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function dispatchError(e: GPUError) {
|
|
53
|
+
// If there's no scope, we throw an error through the WebGPU facilities
|
|
54
|
+
// Only dispatching an error doesn't result in a browser log message, so we implement that ourselves
|
|
55
|
+
// We also make sure to first go through the normal "uncapturederror" process. Since this is the last `addEventListener`, it will get called at the very end.
|
|
56
|
+
device.addEventListener(
|
|
57
|
+
"uncapturederror",
|
|
58
|
+
ev => {
|
|
59
|
+
if (!ev.defaultPrevented) {
|
|
60
|
+
if ("compilationInfo" in ev.error) {
|
|
61
|
+
const error = ev.error as ExtendedGPUValidationError;
|
|
62
|
+
// A custom mode with clickable sources. Uses https://stackoverflow.com/a/79467192/3492994
|
|
63
|
+
if (error.compilationInfo) {
|
|
64
|
+
for (const message of error.compilationInfo.messages) {
|
|
65
|
+
throwClickableError({
|
|
66
|
+
url: message.module.url,
|
|
67
|
+
text: message.module.text ?? null,
|
|
68
|
+
lineNumber: message.lineNum,
|
|
69
|
+
lineColumn: message.linePos,
|
|
70
|
+
length: message.length,
|
|
71
|
+
error: new Error(message.type + ": " + message.message),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
console.error(ev.error.message);
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
console.error(ev.error.message);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
// This event listener should only happen for this event!
|
|
84
|
+
once: true,
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
device.dispatchEvent(
|
|
88
|
+
new GPUUncapturedErrorEvent("uncapturederror", { error: e }),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Keep track of the error scopes so that we can inject our errors into them
|
|
93
|
+
// Based on https://jsgist.org/?src=e3fb4659a668e00c69b03c82ec8f0ad1 from @greggman
|
|
94
|
+
device.pushErrorScope = ((
|
|
95
|
+
baseFn: GPUDevice["pushErrorScope"],
|
|
96
|
+
): GPUDevice["pushErrorScope"] => {
|
|
97
|
+
return function (this: GPUDevice, filter: GPUErrorFilter) {
|
|
98
|
+
errorScopeStack.push({
|
|
99
|
+
filter,
|
|
100
|
+
errors: [],
|
|
101
|
+
});
|
|
102
|
+
return baseFn.call(this, filter);
|
|
103
|
+
};
|
|
104
|
+
})(device.pushErrorScope);
|
|
105
|
+
|
|
106
|
+
device.popErrorScope = ((
|
|
107
|
+
baseFn: GPUDevice["popErrorScope"],
|
|
108
|
+
): GPUDevice["popErrorScope"] => {
|
|
109
|
+
return function (this: GPUDevice) {
|
|
110
|
+
// Get our custom error scope stack
|
|
111
|
+
const errorScope = errorScopeStack.pop();
|
|
112
|
+
if (errorScope === undefined) {
|
|
113
|
+
// This can also happen when makeWeslDevice was called after a `pushErrorScope`
|
|
114
|
+
throw new DOMException(
|
|
115
|
+
"popErrorScope called on empty error scope stack",
|
|
116
|
+
"OperationError",
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
// Add the real error reporter
|
|
120
|
+
errorScope.errors.push(baseFn.call(this));
|
|
121
|
+
// And get the first error (not null)
|
|
122
|
+
// LATER consider reporting *all* errors, and not just the first
|
|
123
|
+
const errorPromise = Promise.all(errorScope.errors).then(
|
|
124
|
+
values => values.find(v => v !== null) ?? null,
|
|
125
|
+
);
|
|
126
|
+
return errorPromise;
|
|
127
|
+
};
|
|
128
|
+
})(device.popErrorScope);
|
|
129
|
+
|
|
130
|
+
return device as WeslDevice;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Based on https://stackoverflow.com/questions/65274147/sourceurl-for-css
|
|
134
|
+
export function throwClickableError({
|
|
135
|
+
url,
|
|
136
|
+
text,
|
|
137
|
+
lineNumber,
|
|
138
|
+
lineColumn,
|
|
139
|
+
length,
|
|
140
|
+
error,
|
|
141
|
+
}: {
|
|
142
|
+
url: string;
|
|
143
|
+
text: string | null;
|
|
144
|
+
lineNumber: number;
|
|
145
|
+
lineColumn: number;
|
|
146
|
+
length: number;
|
|
147
|
+
error: Error;
|
|
148
|
+
}) {
|
|
149
|
+
// We remap an error directly to where we need it to be
|
|
150
|
+
// The fields are
|
|
151
|
+
// 1. Generated column (aka 0)
|
|
152
|
+
// 2. Index into sources list (aka 0)
|
|
153
|
+
// 3. Original line number (zero based)
|
|
154
|
+
// 4. Original column number (zero based)
|
|
155
|
+
|
|
156
|
+
// So we need 2 mappings. One to map to the correct spot,
|
|
157
|
+
// and another one to be the "length" (terminate the first mapping)
|
|
158
|
+
let mappings =
|
|
159
|
+
encodeVlq([
|
|
160
|
+
0,
|
|
161
|
+
0,
|
|
162
|
+
Math.max(0, lineNumber - 1),
|
|
163
|
+
Math.max(0, lineColumn - 1),
|
|
164
|
+
]) +
|
|
165
|
+
"," +
|
|
166
|
+
// Sadly no browser makes use of this info to map the error properly
|
|
167
|
+
encodeVlq([
|
|
168
|
+
18, // Arbitrary number that is high enough
|
|
169
|
+
0,
|
|
170
|
+
Math.max(0, lineNumber - 1),
|
|
171
|
+
Math.max(0, lineColumn - 1) + length,
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
// And this is what our source map looks like
|
|
175
|
+
const sourceMap = {
|
|
176
|
+
version: 3,
|
|
177
|
+
file: null,
|
|
178
|
+
sources: [url],
|
|
179
|
+
sourcesContent: [text ?? null],
|
|
180
|
+
names: [],
|
|
181
|
+
mappings,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
let generatedCode = `throw new Error(${JSON.stringify(error.message + "")})`;
|
|
185
|
+
// And redirect it to WESL
|
|
186
|
+
generatedCode +=
|
|
187
|
+
"\n//# sourceMappingURL=data:application/json;base64," +
|
|
188
|
+
btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))));
|
|
189
|
+
generatedCode += "\n//# sourceURL=" + sourceMap.sources[0];
|
|
190
|
+
|
|
191
|
+
let oldLimit = 0;
|
|
192
|
+
// Supported on Chrome https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stackTraceLimit
|
|
193
|
+
if ("stackTraceLimit" in Error) {
|
|
194
|
+
oldLimit = Error.stackTraceLimit;
|
|
195
|
+
Error.stackTraceLimit = 1;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Run the error-throwing file
|
|
199
|
+
try {
|
|
200
|
+
(0, eval)(generatedCode);
|
|
201
|
+
} catch (e: any) {
|
|
202
|
+
if ("stackTraceLimit" in Error) {
|
|
203
|
+
Error.stackTraceLimit = oldLimit;
|
|
204
|
+
}
|
|
205
|
+
error.message = "";
|
|
206
|
+
e.cause = error;
|
|
207
|
+
throw e;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { assertUnreachable } from "../../../mini-parse/src/Assertions.ts";
|
|
2
|
+
import {
|
|
3
|
+
AbstractElem,
|
|
4
|
+
Attribute,
|
|
5
|
+
AttributeElem,
|
|
6
|
+
DirectiveElem,
|
|
7
|
+
FnElem,
|
|
8
|
+
StuffElem,
|
|
9
|
+
TypedDeclElem,
|
|
10
|
+
TypeRefElem,
|
|
11
|
+
TypeTemplateParameter,
|
|
12
|
+
UnknownExpressionElem,
|
|
13
|
+
} from "../AbstractElems.ts";
|
|
14
|
+
import {
|
|
15
|
+
diagnosticControlToString,
|
|
16
|
+
expressionToString,
|
|
17
|
+
} from "../LowerAndEmit.ts";
|
|
18
|
+
import { importToString } from "./ImportToString.ts";
|
|
19
|
+
import { LineWrapper } from "./LineWrapper.ts";
|
|
20
|
+
|
|
21
|
+
const maxLineLength = 150;
|
|
22
|
+
|
|
23
|
+
export function astToString(elem: AbstractElem, indent = 0): string {
|
|
24
|
+
const { kind } = elem;
|
|
25
|
+
const str = new LineWrapper(indent, maxLineLength);
|
|
26
|
+
str.add(kind);
|
|
27
|
+
addElemFields(elem, str);
|
|
28
|
+
let childStrings: string[] = [];
|
|
29
|
+
if ("contents" in elem) {
|
|
30
|
+
childStrings = elem.contents.map(e => astToString(e, indent + 2));
|
|
31
|
+
}
|
|
32
|
+
if (childStrings.length) {
|
|
33
|
+
str.nl();
|
|
34
|
+
str.addBlock(childStrings.join("\n"), false);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return str.result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// LATER rewrite to be shorter and easier to read
|
|
41
|
+
function addElemFields(elem: AbstractElem, str: LineWrapper): void {
|
|
42
|
+
const { kind } = elem;
|
|
43
|
+
if (kind === "text") {
|
|
44
|
+
const { srcModule, start, end } = elem;
|
|
45
|
+
str.add(` '${srcModule.src.slice(start, end)}'`);
|
|
46
|
+
} else if (
|
|
47
|
+
kind === "var" ||
|
|
48
|
+
kind === "let" ||
|
|
49
|
+
kind === "gvar" ||
|
|
50
|
+
kind === "const" ||
|
|
51
|
+
kind === "override"
|
|
52
|
+
) {
|
|
53
|
+
addTypedDeclIdent(elem.name, str);
|
|
54
|
+
listAttributeElems(elem.attributes, str);
|
|
55
|
+
} else if (kind === "struct") {
|
|
56
|
+
str.add(" " + elem.name.ident.originalName);
|
|
57
|
+
} else if (kind === "member") {
|
|
58
|
+
const { name, typeRef, attributes } = elem;
|
|
59
|
+
listAttributeElems(attributes, str);
|
|
60
|
+
str.add(" " + name.name);
|
|
61
|
+
str.add(": " + typeRefElemToString(typeRef));
|
|
62
|
+
} else if (kind === "name") {
|
|
63
|
+
str.add(" " + elem.name);
|
|
64
|
+
} else if (kind === "memberRef") {
|
|
65
|
+
const { extraComponents } = elem;
|
|
66
|
+
const extraText =
|
|
67
|
+
extraComponents ? debugContentsToString(extraComponents) : "";
|
|
68
|
+
str.add(` ${elem.name.ident.originalName}.${elem.member.name}${extraText}`);
|
|
69
|
+
} else if (kind === "fn") {
|
|
70
|
+
addFnFields(elem, str);
|
|
71
|
+
} else if (kind === "alias") {
|
|
72
|
+
const { name, typeRef } = elem;
|
|
73
|
+
const prefix = name.ident.kind === "decl" ? "%" : "";
|
|
74
|
+
str.add(" " + prefix + name.ident.originalName);
|
|
75
|
+
str.add("=" + typeRefElemToString(typeRef));
|
|
76
|
+
} else if (kind === "attribute") {
|
|
77
|
+
addAttributeFields(elem.attribute, str);
|
|
78
|
+
} else if (kind === "expression") {
|
|
79
|
+
const contents = elem.contents
|
|
80
|
+
.map(e => {
|
|
81
|
+
if (e.kind === "text") {
|
|
82
|
+
return "'" + e.srcModule.src.slice(e.start, e.end) + "'";
|
|
83
|
+
} else {
|
|
84
|
+
return astToString(e);
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
.join(" ");
|
|
88
|
+
str.add(" " + contents);
|
|
89
|
+
} else if (kind === "type") {
|
|
90
|
+
const { name } = elem;
|
|
91
|
+
const nameStr = typeof name === "string" ? name : name.originalName;
|
|
92
|
+
str.add(" " + nameStr);
|
|
93
|
+
|
|
94
|
+
if (elem.templateParams !== undefined) {
|
|
95
|
+
const paramStrs = elem.templateParams
|
|
96
|
+
.map(templateParamToString)
|
|
97
|
+
.join(", ");
|
|
98
|
+
str.add("<" + paramStrs + ">");
|
|
99
|
+
}
|
|
100
|
+
} else if (kind === "synthetic") {
|
|
101
|
+
str.add(` '${elem.text}'`);
|
|
102
|
+
} else if (kind === "import") {
|
|
103
|
+
str.add(" " + importToString(elem.imports));
|
|
104
|
+
} else if (kind === "ref") {
|
|
105
|
+
str.add(" " + elem.ident.originalName);
|
|
106
|
+
} else if (kind === "typeDecl") {
|
|
107
|
+
addTypedDeclIdent(elem, str);
|
|
108
|
+
} else if (kind === "decl") {
|
|
109
|
+
const { ident } = elem;
|
|
110
|
+
str.add(" %" + ident.originalName);
|
|
111
|
+
} else if (kind === "assert") {
|
|
112
|
+
// Nothing to do for now
|
|
113
|
+
} else if (kind === "module") {
|
|
114
|
+
// Ignore this kind of elem
|
|
115
|
+
} else if (kind === "param") {
|
|
116
|
+
// LATER This branch shouldn't exist
|
|
117
|
+
} else if (kind === "stuff") {
|
|
118
|
+
// Ignore
|
|
119
|
+
} else if (kind === "directive") {
|
|
120
|
+
addDirective(elem, str);
|
|
121
|
+
} else if (kind === "statement") {
|
|
122
|
+
listAttributeElems(elem.attributes, str);
|
|
123
|
+
} else if (kind === "switch-clause") {
|
|
124
|
+
// Nothing to do for now
|
|
125
|
+
} else {
|
|
126
|
+
assertUnreachable(kind);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function addAttributeFields(attr: Attribute, str: LineWrapper) {
|
|
131
|
+
const { kind } = attr;
|
|
132
|
+
if (kind === "@attribute") {
|
|
133
|
+
const { name, params } = attr;
|
|
134
|
+
str.add(" @" + name);
|
|
135
|
+
if (params && params.length > 0) {
|
|
136
|
+
str.add("(");
|
|
137
|
+
str.add(params.map(unknownExpressionToString).join(", "));
|
|
138
|
+
str.add(")");
|
|
139
|
+
}
|
|
140
|
+
} else if (kind === "@builtin") {
|
|
141
|
+
str.add(` @builtin(${attr.param.name})`);
|
|
142
|
+
} else if (kind === "@diagnostic") {
|
|
143
|
+
str.add(
|
|
144
|
+
` @diagnostic${diagnosticControlToString(attr.severity, attr.rule)}`,
|
|
145
|
+
);
|
|
146
|
+
} else if (kind === "@if") {
|
|
147
|
+
str.add(" @if");
|
|
148
|
+
str.add("(");
|
|
149
|
+
str.add(expressionToString(attr.param.expression));
|
|
150
|
+
str.add(")");
|
|
151
|
+
} else if (kind === "@interpolate") {
|
|
152
|
+
str.add(` @interpolate(${attr.params.map(v => v.name).join(", ")})`);
|
|
153
|
+
} else {
|
|
154
|
+
assertUnreachable(kind);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** @return string representation of an attribute (for test/debug) */
|
|
159
|
+
export function attributeToString(attr: Attribute): string {
|
|
160
|
+
const str = new LineWrapper(0, maxLineLength);
|
|
161
|
+
addAttributeFields(attr, str);
|
|
162
|
+
return str.result;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function addTypedDeclIdent(elem: TypedDeclElem, str: LineWrapper) {
|
|
166
|
+
const { decl, typeRef } = elem;
|
|
167
|
+
str.add(" %" + decl.ident.originalName);
|
|
168
|
+
if (typeRef) {
|
|
169
|
+
str.add(" : " + typeRefElemToString(typeRef));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function addFnFields(elem: FnElem, str: LineWrapper) {
|
|
174
|
+
const { name, params, returnType, attributes } = elem;
|
|
175
|
+
|
|
176
|
+
str.add(" " + name.ident.originalName);
|
|
177
|
+
|
|
178
|
+
str.add("(");
|
|
179
|
+
const paramStrs = params
|
|
180
|
+
.map(
|
|
181
|
+
(
|
|
182
|
+
p, // LATER DRY
|
|
183
|
+
) => {
|
|
184
|
+
const { name } = p;
|
|
185
|
+
const { originalName } = name.decl.ident;
|
|
186
|
+
const typeRef = typeRefElemToString(name.typeRef!);
|
|
187
|
+
return originalName + ": " + typeRef;
|
|
188
|
+
},
|
|
189
|
+
)
|
|
190
|
+
.join(", ");
|
|
191
|
+
str.add(paramStrs);
|
|
192
|
+
str.add(")");
|
|
193
|
+
|
|
194
|
+
listAttributeElems(attributes, str);
|
|
195
|
+
|
|
196
|
+
if (returnType) {
|
|
197
|
+
str.add(" -> " + typeRefElemToString(returnType));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** show attribute names in short form to verify collection */
|
|
202
|
+
function listAttributeElems(
|
|
203
|
+
attributes: AttributeElem[] | undefined,
|
|
204
|
+
str: LineWrapper,
|
|
205
|
+
) {
|
|
206
|
+
attributes?.forEach(a => str.add(" " + attributeName(a.attribute)));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function attributeName(attr: Attribute): string {
|
|
210
|
+
const { kind } = attr;
|
|
211
|
+
if (kind === "@attribute") {
|
|
212
|
+
return "@" + attr.name;
|
|
213
|
+
} else {
|
|
214
|
+
return kind;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function addDirective(elem: DirectiveElem, str: LineWrapper) {
|
|
219
|
+
const { directive, attributes } = elem;
|
|
220
|
+
const { kind } = directive;
|
|
221
|
+
if (kind === "diagnostic") {
|
|
222
|
+
const { severity, rule } = directive;
|
|
223
|
+
const control = diagnosticControlToString(severity, rule);
|
|
224
|
+
str.add(` diagnostic${control}`);
|
|
225
|
+
} else if (kind === "enable" || kind === "requires") {
|
|
226
|
+
str.add(` ${kind} ${directive.extensions.map(v => v.name).join(", ")}`);
|
|
227
|
+
} else {
|
|
228
|
+
assertUnreachable(kind);
|
|
229
|
+
}
|
|
230
|
+
listAttributeElems(attributes, str);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function unknownExpressionToString(elem: UnknownExpressionElem): string {
|
|
234
|
+
// LATER Temp hack while I clean up the expression parsing
|
|
235
|
+
if ("contents" in elem) {
|
|
236
|
+
// @ts-ignore
|
|
237
|
+
const contents = elem.contents
|
|
238
|
+
// @ts-ignore
|
|
239
|
+
.map(e => {
|
|
240
|
+
if (e.kind === "text") {
|
|
241
|
+
return "'" + e.srcModule.src.slice(e.start, e.end) + "'";
|
|
242
|
+
} else {
|
|
243
|
+
return astToString(e);
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
.join(" ");
|
|
247
|
+
return contents;
|
|
248
|
+
}
|
|
249
|
+
return astToString(elem);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function templateParamToString(p: TypeTemplateParameter): string {
|
|
253
|
+
if (typeof p === "string") {
|
|
254
|
+
return p;
|
|
255
|
+
} else if (p.kind === "type") {
|
|
256
|
+
return typeRefElemToString(p);
|
|
257
|
+
} else if (p.kind === "expression") {
|
|
258
|
+
return unknownExpressionToString(p);
|
|
259
|
+
} else {
|
|
260
|
+
console.log("unknown template parameter type", p);
|
|
261
|
+
return "??";
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function typeRefElemToString(elem: TypeRefElem): string {
|
|
266
|
+
if (!elem) return "?type?";
|
|
267
|
+
const { name } = elem;
|
|
268
|
+
const nameStr = typeof name === "string" ? name : name.originalName;
|
|
269
|
+
|
|
270
|
+
let params = "";
|
|
271
|
+
if (elem.templateParams !== undefined) {
|
|
272
|
+
const paramStrs = elem.templateParams.map(templateParamToString).join(", ");
|
|
273
|
+
params = "<" + paramStrs + ">";
|
|
274
|
+
}
|
|
275
|
+
return nameStr + params;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function debugContentsToString(elem: StuffElem): string {
|
|
279
|
+
const parts = elem.contents.map(c => {
|
|
280
|
+
const { kind } = c;
|
|
281
|
+
if (kind === "text") {
|
|
282
|
+
return c.srcModule.src.slice(c.start, c.end);
|
|
283
|
+
} else if (kind === "ref") {
|
|
284
|
+
return c.ident.originalName; // not using the mapped to decl name, so this can be used for debug..
|
|
285
|
+
} else {
|
|
286
|
+
return `?${c.kind}?`;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
return parts.join(" ");
|
|
290
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { assertUnreachable } from "../../../mini-parse/src/Assertions";
|
|
2
|
+
import {
|
|
3
|
+
ImportCollection,
|
|
4
|
+
ImportItem,
|
|
5
|
+
ImportStatement,
|
|
6
|
+
} from "../AbstractElems";
|
|
7
|
+
|
|
8
|
+
export function importToString(tree: ImportStatement): string {
|
|
9
|
+
return importToStringImpl(tree) + ";";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function importToStringImpl(tree: ImportStatement): string {
|
|
13
|
+
return [
|
|
14
|
+
...tree.segments.map(s => s.name),
|
|
15
|
+
segmentToString(tree.finalSegment),
|
|
16
|
+
].join("::");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function segmentToString(segment: ImportCollection | ImportItem): string {
|
|
20
|
+
if (segment.kind === "import-item") {
|
|
21
|
+
const { name, as } = segment;
|
|
22
|
+
const asMsg = as ? ` as ${as}` : "";
|
|
23
|
+
return `${name}${asMsg}`;
|
|
24
|
+
} else if (segment.kind === "import-collection") {
|
|
25
|
+
return `{${segment.subtrees.map(s => importToStringImpl(s)).join(", ")}}`;
|
|
26
|
+
} else {
|
|
27
|
+
assertUnreachable(segment);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** debug utility for constructing strings that wrap at a fixed column width
|
|
2
|
+
* text beyond the column width is wrapped to start on the next line
|
|
3
|
+
*/
|
|
4
|
+
export class LineWrapper {
|
|
5
|
+
#fragments: string[] = [];
|
|
6
|
+
#column = 0;
|
|
7
|
+
#spc: string;
|
|
8
|
+
#oneLine = true;
|
|
9
|
+
#isHanging = false;
|
|
10
|
+
#hangingSpc: string;
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
readonly indent = 0,
|
|
14
|
+
readonly maxWidth = 60,
|
|
15
|
+
readonly hangingIndent = 2,
|
|
16
|
+
) {
|
|
17
|
+
this.#spc = " ".repeat(indent);
|
|
18
|
+
this.#hangingSpc = " ".repeat(hangingIndent);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** add a new line to the constructed string */
|
|
22
|
+
nl() {
|
|
23
|
+
this.#fragments.push("\n");
|
|
24
|
+
this.#column = 0;
|
|
25
|
+
this.#oneLine = false;
|
|
26
|
+
this.#isHanging = false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** add a string, wrapping to the next line if necessary */
|
|
30
|
+
add(s: string) {
|
|
31
|
+
if (this.#column + firstLineLength(s) > this.maxWidth) {
|
|
32
|
+
this.hangingNl();
|
|
33
|
+
}
|
|
34
|
+
if (this.#column === 0) {
|
|
35
|
+
this.#fragments.push(this.#spc);
|
|
36
|
+
if (this.#isHanging) {
|
|
37
|
+
this.#fragments.push(this.#hangingSpc);
|
|
38
|
+
}
|
|
39
|
+
this.#column = this.indent;
|
|
40
|
+
}
|
|
41
|
+
this.#fragments.push(s);
|
|
42
|
+
this.#column += s.length;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** add a raw block of text with no wrapping */
|
|
46
|
+
addBlock(s: string, andNewLine = true) {
|
|
47
|
+
this.#fragments.push(s);
|
|
48
|
+
if (andNewLine) this.nl();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** @return the constructed string */
|
|
52
|
+
get result(): string {
|
|
53
|
+
return this.#fragments.join("");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** true if the result contains no newlines */
|
|
57
|
+
get oneLine(): boolean {
|
|
58
|
+
return this.#oneLine;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private hangingNl() {
|
|
62
|
+
this.nl();
|
|
63
|
+
this.#isHanging = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function firstLineLength(s: string): number {
|
|
68
|
+
const i = s.indexOf("\n");
|
|
69
|
+
return i === -1 ? s.length : i;
|
|
70
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { childScope, Ident, Scope } from "../Scope.ts";
|
|
2
|
+
import { attributeToString } from "./ASTtoString.ts";
|
|
3
|
+
import { LineWrapper } from "./LineWrapper.ts";
|
|
4
|
+
|
|
5
|
+
/** A debugging print of the scope tree with identifiers in nested brackets */
|
|
6
|
+
export function scopeToString(
|
|
7
|
+
scope: Scope,
|
|
8
|
+
indent = 0,
|
|
9
|
+
shortIdents = true,
|
|
10
|
+
): string {
|
|
11
|
+
const { contents, kind, ifAttribute } = scope;
|
|
12
|
+
|
|
13
|
+
const str = new LineWrapper(indent);
|
|
14
|
+
const attrStrings = ifAttribute && attributeToString(ifAttribute);
|
|
15
|
+
if (attrStrings) str.add(attrStrings + " ");
|
|
16
|
+
if (kind === "partial") str.add("-");
|
|
17
|
+
str.add("{ ");
|
|
18
|
+
|
|
19
|
+
const last = contents.length - 1;
|
|
20
|
+
let lastWasScope = false;
|
|
21
|
+
let hasBlock = false;
|
|
22
|
+
contents.forEach((elem, i) => {
|
|
23
|
+
if (childScope(elem)) {
|
|
24
|
+
const childScope: Scope = elem;
|
|
25
|
+
const childBlock = scopeToString(childScope, indent + 2, shortIdents);
|
|
26
|
+
!lastWasScope && str.nl();
|
|
27
|
+
str.addBlock(childBlock);
|
|
28
|
+
lastWasScope = true;
|
|
29
|
+
hasBlock = true;
|
|
30
|
+
} else {
|
|
31
|
+
lastWasScope && str.add(" ");
|
|
32
|
+
lastWasScope = false;
|
|
33
|
+
const ident: Ident = elem;
|
|
34
|
+
if (shortIdents) {
|
|
35
|
+
str.add(identShortString(ident));
|
|
36
|
+
} else {
|
|
37
|
+
str.add(identToString(ident));
|
|
38
|
+
}
|
|
39
|
+
if (i < last) str.add(" ");
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (!hasBlock && str.oneLine) {
|
|
44
|
+
str.add(" }");
|
|
45
|
+
} else {
|
|
46
|
+
if (hasBlock && !lastWasScope) str.nl();
|
|
47
|
+
str.add("}");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
str.add(` #${scope.id}`);
|
|
51
|
+
|
|
52
|
+
return str.result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** A debug print of the scope tree with identifiers in long form in nested brackets */
|
|
56
|
+
export function scopeToStringLong(scope: Scope): string {
|
|
57
|
+
return scopeToString(scope, 0, false);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** name of an identifier, with decls prefixed with '%' */
|
|
61
|
+
function identShortString(ident: Ident): string {
|
|
62
|
+
const { kind, originalName } = ident;
|
|
63
|
+
const prefix = kind === "decl" ? "%" : "";
|
|
64
|
+
return `${prefix}${originalName}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function identToString(ident?: Ident): string {
|
|
68
|
+
if (!ident) return JSON.stringify(ident);
|
|
69
|
+
const { kind, originalName } = ident;
|
|
70
|
+
const idStr = ident.id ? `#${ident.id}` : "";
|
|
71
|
+
if (kind === "ref") {
|
|
72
|
+
const ref = identToString(ident.refersTo!);
|
|
73
|
+
return `${originalName} ${idStr} -> ${ref}`;
|
|
74
|
+
} else {
|
|
75
|
+
const { mangledName } = ident;
|
|
76
|
+
const mangled = mangledName ? `(${mangledName})` : "";
|
|
77
|
+
return `%${originalName}${mangled} ${idStr} `;
|
|
78
|
+
}
|
|
79
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./debug/ASTtoString.js";
|
|
2
|
+
export * from "./debug/ScopeToString.js";
|
|
3
|
+
export * from "./LinkedWesl.js";
|
|
4
|
+
export * from "./Linker.js";
|
|
5
|
+
export { WeslStream } from "./parse/WeslStream.js";
|
|
6
|
+
export * from "./ParsedRegistry.js";
|
|
7
|
+
export * from "./ParseWESL.js";
|
|
8
|
+
export * from "./PathUtil.js";
|
|
9
|
+
export * from "./TransformBindingStructs.js";
|
|
10
|
+
export * from "./WeslBundle.js";
|
|
11
|
+
export * from "./WeslDevice.js";
|