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,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";