wesl 0.6.49 → 0.7.1

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 (94) hide show
  1. package/dist/index.d.ts +269 -215
  2. package/dist/index.js +2911 -1539
  3. package/package.json +6 -8
  4. package/src/AbstractElems.ts +81 -81
  5. package/src/Assertions.ts +5 -5
  6. package/src/BindIdents.ts +192 -306
  7. package/src/ClickableError.ts +3 -2
  8. package/src/Conditions.ts +2 -2
  9. package/src/LinkedWesl.ts +1 -1
  10. package/src/Linker.ts +4 -3
  11. package/src/LinkerUtil.ts +1 -1
  12. package/src/Logging.ts +165 -0
  13. package/src/LowerAndEmit.ts +278 -110
  14. package/src/ModuleResolver.ts +15 -25
  15. package/src/ParseError.ts +9 -0
  16. package/src/ParseWESL.ts +30 -94
  17. package/src/RawEmit.ts +1 -4
  18. package/src/Reflection.ts +1 -1
  19. package/src/Scope.ts +3 -0
  20. package/src/Span.ts +2 -0
  21. package/src/SrcMap.ts +208 -0
  22. package/src/Stream.ts +30 -0
  23. package/src/TransformBindingStructs.ts +2 -2
  24. package/src/Util.ts +1 -1
  25. package/src/debug/ASTtoString.ts +84 -135
  26. package/src/discovery/FindUnboundIdents.ts +14 -5
  27. package/src/index.ts +4 -0
  28. package/src/parse/ContentsHelpers.ts +70 -0
  29. package/src/parse/ExpressionUtil.ts +121 -0
  30. package/src/parse/Keywords.ts +12 -12
  31. package/src/parse/OperatorBinding.ts +146 -0
  32. package/src/parse/ParseAttribute.ts +272 -0
  33. package/src/parse/ParseCall.ts +77 -0
  34. package/src/parse/ParseControlFlow.ts +129 -0
  35. package/src/parse/ParseDirective.ts +105 -0
  36. package/src/parse/ParseExpression.ts +288 -0
  37. package/src/parse/ParseFn.ts +151 -0
  38. package/src/parse/ParseGlobalVar.ts +131 -0
  39. package/src/parse/ParseIdent.ts +77 -0
  40. package/src/parse/ParseImport.ts +160 -0
  41. package/src/parse/ParseLocalVar.ts +69 -0
  42. package/src/parse/ParseLoop.ts +112 -0
  43. package/src/parse/ParseModule.ts +116 -0
  44. package/src/parse/ParseSimpleStatement.ts +162 -0
  45. package/src/parse/ParseStatement.ts +215 -0
  46. package/src/parse/ParseStruct.ts +89 -0
  47. package/src/parse/ParseType.ts +71 -0
  48. package/src/parse/ParseUtil.ts +174 -0
  49. package/src/parse/ParseValueDeclaration.ts +130 -0
  50. package/src/parse/ParseWesl.ts +51 -0
  51. package/src/parse/ParsingContext.ts +93 -0
  52. package/src/parse/WeslStream.ts +63 -20
  53. package/src/parse/stream/CachingStream.ts +48 -0
  54. package/src/parse/stream/MatchersStream.ts +85 -0
  55. package/src/parse/stream/RegexHelpers.ts +38 -0
  56. package/src/test/BevyLink.test.ts +100 -0
  57. package/src/test/BindStdTypes.test.ts +110 -0
  58. package/src/test/{BindWESL.test.ts → BindWESLV2.test.ts} +21 -22
  59. package/src/test/BulkTests.test.ts +11 -12
  60. package/src/test/ConditionLinking.test.ts +107 -0
  61. package/src/test/ConditionalElif.test.ts +1 -13
  62. package/src/test/ConditionalTranslationCases.test.ts +5 -0
  63. package/src/test/ErrorLogging.test.ts +2 -2
  64. package/src/test/ImportCasesV2.test.ts +63 -0
  65. package/src/test/LinkFails.test.ts +69 -0
  66. package/src/test/LinkPackage.test.ts +1 -1
  67. package/src/test/Linker.test.ts +75 -2
  68. package/src/test/LogCatcher.ts +53 -0
  69. package/src/test/Mangling.test.ts +1 -1
  70. package/src/test/ParseComments.test.ts +1 -2
  71. package/src/test/{ParseConditions.test.ts → ParseConditionsV2.test.ts} +57 -49
  72. package/src/test/ParseErrorV2.test.ts +73 -0
  73. package/src/test/{ParseWESL.test.ts → ParseWeslV2.test.ts} +288 -370
  74. package/src/test/{ScopeWESL.test.ts → ScopeWESLV2.test.ts} +205 -176
  75. package/src/test/TestLink.ts +51 -51
  76. package/src/test/TestSetup.ts +9 -3
  77. package/src/test/TestUtil.ts +47 -77
  78. package/src/test/TrimmedMatch.ts +40 -0
  79. package/src/test/VirtualModules.test.ts +33 -2
  80. package/src/test/WeslDevice.test.ts +9 -2
  81. package/src/test/__snapshots__/ParseWeslV2.test.ts.snap +67 -0
  82. package/src/test-util.ts +7 -0
  83. package/src/WESLCollect.ts +0 -656
  84. package/src/parse/AttributeGrammar.ts +0 -232
  85. package/src/parse/ImportGrammar.ts +0 -195
  86. package/src/parse/WeslBaseGrammar.ts +0 -11
  87. package/src/parse/WeslExpression.ts +0 -231
  88. package/src/parse/WeslGrammar.ts +0 -739
  89. package/src/test/Expression.test.ts +0 -22
  90. package/src/test/ImportSyntaxCases.test.ts +0 -24
  91. package/src/test/ParseError.test.ts +0 -45
  92. package/src/test/Reflection.test.ts +0 -176
  93. package/src/test/TransformBindingStructs.test.ts +0 -238
  94. /package/src/test/{ParseElif.test.ts → ParseElifV2.test.ts} +0 -0
@@ -1,4 +1,10 @@
1
- import { enableTracing } from "mini-parse";
1
+ import { beforeEach } from "vitest";
2
+ import { resetScopeIds } from "wesl";
3
+ import { resetScopeIds as resetSourceScopeIds } from "../Scope.ts";
2
4
 
3
- // enable parser tracing features
4
- enableTracing();
5
+ // Auto-reset scope IDs before each test for consistent output
6
+ // Reset both source and built counters (nodebug tests alias wesl to dist-nodebug)
7
+ beforeEach(() => {
8
+ resetScopeIds();
9
+ resetSourceScopeIds();
10
+ });
@@ -1,134 +1,104 @@
1
- import { type Parser, type Stream, withLoggerAsync } from "mini-parse";
2
- import {
3
- expectNoLog,
4
- logCatch,
5
- type TestParseResult,
6
- testParseWithStream,
7
- } from "mini-parse/test-util";
1
+ import { expect } from "vitest";
8
2
  import { type BoundAndTransformed, RecordResolver, type SrcModule } from "wesl";
9
3
  import { bindAndTransform, type LinkParams, link } from "../Linker.ts";
10
- import {
11
- parseSrcModule,
12
- syntheticWeslParseState,
13
- type WeslAST,
14
- } from "../ParseWESL.ts";
15
- import { WeslStream, type WeslToken } from "../parse/WeslStream.ts";
16
- import { resetScopeIds } from "../Scope.ts";
4
+ import { withLoggerAsync } from "../Logging.ts";
5
+ import { parseSrcModule, type WeslAST } from "../ParseWESL.ts";
6
+ import { expectNoLog, logCatch } from "./LogCatcher.ts";
7
+ import { stripWesl } from "./StripWesl.ts";
17
8
 
18
- /** Parse a single wesl file */
9
+ export type LinkTestOpts = Pick<
10
+ LinkParams,
11
+ "conditions" | "libs" | "config" | "virtualLibs" | "constants" | "mangler"
12
+ >;
13
+
14
+ interface BindTestResult {
15
+ bound: BoundAndTransformed;
16
+ resolver: RecordResolver;
17
+ }
18
+
19
+ /** Compare WGSL/WESL by token sequence, ignoring whitespace. */
20
+ export function expectTokenMatch(actual: string, expected: string): void {
21
+ expect(stripWesl(actual)).toBe(stripWesl(expected));
22
+ }
23
+
24
+ /** Parse a single wesl file. */
19
25
  export function parseWESL(src: string): WeslAST {
20
26
  const srcModule: SrcModule = {
21
- modulePath: "package::test", // TODO this ought not be used outside of tests
27
+ modulePath: "package::test",
22
28
  debugFilePath: "./test.wesl",
23
29
  src,
24
30
  };
25
-
26
31
  return parseSrcModule(srcModule);
27
32
  }
28
33
 
29
- /** Parse a single wesl file, returning the parse state as well as the WeslAST */ // LATER get rid of this
30
- export function testAppParse<T>(
31
- parser: Parser<Stream<WeslToken>, T>,
32
- src: string,
33
- ): TestParseResult<T, WeslAST> {
34
- const appState = syntheticWeslParseState();
35
- const stream = new WeslStream(src);
36
- return testParseWithStream(parser, stream, appState);
37
- }
38
-
39
- /** Convenience wrapper to link wgsl for tests.
40
- * The first module is named "./test.wesl",
41
- * subsequent modules are named "./file1.wesl", "./file2.wesl", etc.
42
- */
34
+ /** Link wesl for tests. First module is ./test.wesl, rest are ./file1.wesl, etc. */
43
35
  export async function linkTest(...rawWgsl: string[]): Promise<string> {
44
36
  return linkTestOpts({}, ...rawWgsl);
45
37
  }
46
38
 
47
- export type LinkTestOpts = Pick<
48
- LinkParams,
49
- "conditions" | "libs" | "config" | "virtualLibs" | "constants" | "mangler"
50
- >;
51
-
52
- export async function linkTestOpts(
53
- opts: LinkTestOpts,
54
- ...rawWgsl: string[]
55
- ): Promise<string> {
39
+ export async function linkTestOpts(opts: LinkTestOpts, ...rawWgsl: string[]) {
56
40
  const weslSrc = makeTestBundle(rawWgsl);
57
-
58
- const rootModuleName = "test";
59
- const srcMap = await link({ weslSrc, rootModuleName, ...opts });
41
+ const srcMap = await link({ weslSrc, rootModuleName: "test", ...opts });
60
42
  return srcMap.dest;
61
43
  }
62
44
 
63
- /** Link wesl for tests, capturing console output */
64
- export async function linkWithLog(...rawWgsl: string[]): Promise<{
45
+ interface LogResult {
65
46
  log: string;
66
47
  result: string;
67
- }> {
68
- return linkWithLogInternal(rawWgsl);
69
48
  }
70
49
 
71
- /** Link wesl for tests, capturing console output and swallowing exceptions */
72
- export async function linkWithLogQuietly(...rawWgsl: string[]): Promise<{
73
- log: string;
74
- result: string;
75
- }> {
50
+ /** Link wesl for tests, capturing console output. */
51
+ export async function linkWithLog(...rawWgsl: string[]): Promise<LogResult> {
52
+ return linkWithLogInternal(rawWgsl, false);
53
+ }
54
+
55
+ /** Link wesl for tests, capturing console output and swallowing exceptions. */
56
+ export async function linkWithLogQuietly(
57
+ ...rawWgsl: string[]
58
+ ): Promise<LogResult> {
76
59
  return linkWithLogInternal(rawWgsl, true);
77
60
  }
78
61
 
79
62
  async function linkWithLogInternal(
80
63
  rawWgsl: string[],
81
- quiet = false,
82
- ): Promise<{
83
- log: string;
84
- result: string;
85
- }> {
64
+ quiet: boolean,
65
+ ): Promise<LogResult> {
86
66
  const { log, logged } = logCatch();
87
67
  let result = "??";
88
68
  try {
89
- result = await withLoggerAsync(log, async () => linkTest(...rawWgsl));
69
+ result = await withLoggerAsync(log, () => linkTest(...rawWgsl));
90
70
  } catch (e) {
91
71
  if (!quiet) console.error(e);
92
72
  }
93
73
  return { result, log: logged() };
94
74
  }
95
75
 
96
- /** Parse wesl for testing, ensuring no logged warnings */
76
+ /** Parse wesl for testing, ensuring no logged warnings. */
97
77
  export function parseTest(src: string): WeslAST {
98
- return expectNoLog(() => parseTestRaw(src));
78
+ return expectNoLog(() => parseWESL(src));
99
79
  }
100
80
 
101
- /** Parse wesl without log collection (for debugging) */
102
- export function parseTestRaw(src: string) {
81
+ /** Parse wesl without log collection (for debugging). */
82
+ export function parseTestRaw(src: string): WeslAST {
103
83
  return parseWESL(src);
104
84
  }
105
85
 
106
- interface BindTestResult {
107
- bound: BoundAndTransformed;
108
- resolver: RecordResolver;
109
- }
110
-
111
- /** Parse and bind wesl source for testing. Returns bound result and resolver for inspection. */
86
+ /** Parse and bind wesl source for testing. Returns bound result and resolver. */
112
87
  export function bindTest(...rawWesl: string[]): BindTestResult {
113
- resetScopeIds();
114
88
  const weslSrc = makeTestBundle(rawWesl);
115
-
116
89
  const resolver = new RecordResolver(weslSrc, {
117
90
  packageName: "package",
118
91
  debugWeslRoot: "test",
119
92
  });
120
- const bound = bindAndTransform({
121
- rootModuleName: "test",
122
- resolver,
123
- });
93
+ const bound = bindAndTransform({ rootModuleName: "test", resolver });
124
94
  return { bound, resolver };
125
95
  }
126
96
 
127
- /** Synthesize test file bundle. Root module is ./test.wesl, others are ./file1.wesl, ./file2.wesl, etc. */
97
+ /** Synthesize test file bundle: ./test.wesl, ./file1.wesl, ./file2.wesl, etc. */
128
98
  function makeTestBundle(rawWgsl: string[]): Record<string, string> {
129
99
  const [root, ...rest] = rawWgsl;
130
- const restWgsl = Object.fromEntries(
100
+ const restFiles = Object.fromEntries(
131
101
  rest.map((src, i) => [`./file${i + 1}.wesl`, src]),
132
102
  );
133
- return { "./test.wesl": root, ...restWgsl };
103
+ return { "./test.wesl": root, ...restFiles };
134
104
  }
@@ -0,0 +1,40 @@
1
+ import { expect } from "vitest";
2
+
3
+ /**
4
+ * Trim source for test comparisons:
5
+ * - Remove blank lines
6
+ * - Remove leading and trailing white space
7
+ */
8
+ export function trimSrc(src: string): string {
9
+ const rawLines = src.split("\n");
10
+ const trimmed = rawLines.map(l => l.trim());
11
+ const nonBlank = trimmed.filter(l => l !== "");
12
+ return nonBlank.join("\n");
13
+ }
14
+
15
+ /**
16
+ * Expect a match between two strings with blank lines and
17
+ * any consistent leading indent removed.
18
+ */
19
+ export function expectTrimmedMatch(result: string, expected: string): void {
20
+ const resultTrimmed = trimSrc(result);
21
+ const expectTrimmed = trimSrc(expected);
22
+ if (resultTrimmed !== expectTrimmed) {
23
+ const expectLines = expectTrimmed.split("\n");
24
+ const resultLines = resultTrimmed.split("\n");
25
+ const len = Math.max(expectLines.length, resultLines.length);
26
+ for (let i = 0; i < len; i++) {
27
+ const diff = expectLines[i] !== resultLines[i];
28
+ if (diff) {
29
+ console.log(`...failed. Line ${i + 1} differs:
30
+ expected: ${expectLines[i]}
31
+ actual: ${resultLines[i] ?? ""}`);
32
+ break;
33
+ }
34
+ }
35
+ console.log(
36
+ `\ntrimmed result:\n${resultTrimmed}\n\ntrimmed expected:\n${expectTrimmed}\n`,
37
+ );
38
+ }
39
+ expect(resultTrimmed).toBe(expectTrimmed);
40
+ }
@@ -1,6 +1,7 @@
1
- import { expectTrimmedMatch } from "mini-parse/vitest-util";
2
- import { test } from "vitest";
1
+ import { expect, test } from "vitest";
2
+ import { link } from "../Linker.ts";
3
3
  import { linkTestOpts } from "./TestUtil.ts";
4
+ import { expectTrimmedMatch } from "./TrimmedMatch.ts";
4
5
 
5
6
  test("simple virtual module", async () => {
6
7
  const src = `
@@ -35,3 +36,33 @@ test("virtual constants", async () => {
35
36
  `;
36
37
  expectTrimmedMatch(result, expected);
37
38
  });
39
+
40
+ // WGSL reserved words (like 'common') are allowed in module paths per WESL spec.
41
+ test("inline reference to virtual module with reserved word name", async () => {
42
+ const result = await link({
43
+ weslSrc: { "./main.wesl": "fn main() { let x = common::value; }" },
44
+ rootModuleName: "main",
45
+ virtualLibs: { common: () => "const value = 42;" },
46
+ });
47
+ expect(result.dest).toContain("const value = 42");
48
+ });
49
+
50
+ test("inline constant in array template param", async () => {
51
+ const src = `
52
+ fn main() {
53
+ var data: array<f32, constants::size>;
54
+ }
55
+ `;
56
+ const result = await linkTestOpts({ constants: { size: 4 } }, src);
57
+ expect(result).toContain("const size = 4");
58
+ });
59
+
60
+ test("function call with inline ref in template param", async () => {
61
+ const src = `
62
+ fn main() {
63
+ var data: array<f32, u32(constants::size)>;
64
+ }
65
+ `;
66
+ const result = await linkTestOpts({ constants: { size: 4 } }, src);
67
+ expect(result).toContain("const size = 4");
68
+ });
@@ -1,7 +1,7 @@
1
1
  import { setTimeout } from "node:timers";
2
- import { SrcMap } from "mini-parse";
3
2
  import { expect, test, vi } from "vitest";
4
3
  import { LinkedWesl } from "../LinkedWesl";
4
+ import { SrcMap } from "../SrcMap.ts";
5
5
  import { makeWeslDevice } from "../WeslDevice";
6
6
 
7
7
  test("WeslDevice doesn't conflict with uncapturederror", async () => {
@@ -199,7 +199,14 @@ test("Invokes error throwing", async () => {
199
199
  this.message = message;
200
200
  });
201
201
  vi.stubGlobal("GPUValidationError", GPUValidationErrorMock);
202
- const GPUUncapturedErrorEventMock = vi.fn(() => {});
202
+ const GPUUncapturedErrorEventMock = vi.fn(function (
203
+ this: any,
204
+ type: string,
205
+ options: { error: GPUError },
206
+ ) {
207
+ this.type = type;
208
+ this.error = options.error;
209
+ });
203
210
  vi.stubGlobal("GPUUncapturedErrorEvent", GPUUncapturedErrorEventMock);
204
211
 
205
212
  const dispatchEventPromise = Promise.withResolvers();
@@ -0,0 +1,67 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`parse unicode ident 1`] = `
4
+ "module
5
+ text '
6
+ '
7
+ fn Δέλτα()
8
+ decl %Δέλτα
9
+ statement
10
+ text '{}'
11
+ text '
12
+ '
13
+ fn réflexion()
14
+ decl %réflexion
15
+ statement
16
+ text '{}'
17
+ text '
18
+ '
19
+ fn Кызыл()
20
+ decl %Кызыл
21
+ statement
22
+ text '{}'
23
+ text '
24
+ '
25
+ fn 𐰓𐰏𐰇()
26
+ decl %𐰓𐰏𐰇
27
+ statement
28
+ text '{}'
29
+ text '
30
+ '
31
+ fn 朝焼け()
32
+ decl %朝焼け
33
+ statement
34
+ text '{}'
35
+ text '
36
+ '
37
+ fn سلام()
38
+ decl %سلام
39
+ statement
40
+ text '{}'
41
+ text '
42
+ '
43
+ fn 검정()
44
+ decl %검정
45
+ statement
46
+ text '{}'
47
+ text '
48
+ '
49
+ fn שָׁלוֹם()
50
+ decl %שָׁלוֹם
51
+ statement
52
+ text '{}'
53
+ text '
54
+ '
55
+ fn गुलाबी()
56
+ decl %गुलाबी
57
+ statement
58
+ text '{}'
59
+ text '
60
+ '
61
+ fn փիրուզ()
62
+ decl %փիրուզ
63
+ statement
64
+ text '{}'
65
+ text '
66
+ '"
67
+ `;
@@ -0,0 +1,7 @@
1
+ export {
2
+ expectNoLog,
3
+ expectNoLogAsync,
4
+ logCatch,
5
+ withLogSpyAsync,
6
+ } from "./test/LogCatcher.ts";
7
+ export { expectTrimmedMatch, trimSrc } from "./test/TrimmedMatch.ts";