wesl 0.6.48 → 0.7.0

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 (95) hide show
  1. package/dist/index.d.ts +295 -214
  2. package/dist/index.js +2947 -1550
  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 +193 -319
  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/ModulePathUtil.ts +59 -0
  15. package/src/ModuleResolver.ts +26 -62
  16. package/src/ParseError.ts +9 -0
  17. package/src/ParseWESL.ts +30 -94
  18. package/src/RawEmit.ts +1 -4
  19. package/src/Reflection.ts +1 -1
  20. package/src/Scope.ts +3 -0
  21. package/src/Span.ts +2 -0
  22. package/src/SrcMap.ts +208 -0
  23. package/src/Stream.ts +30 -0
  24. package/src/TransformBindingStructs.ts +2 -2
  25. package/src/Util.ts +1 -1
  26. package/src/debug/ASTtoString.ts +84 -135
  27. package/src/discovery/FindUnboundIdents.ts +14 -5
  28. package/src/index.ts +5 -0
  29. package/src/parse/ContentsHelpers.ts +70 -0
  30. package/src/parse/ExpressionUtil.ts +121 -0
  31. package/src/parse/Keywords.ts +12 -12
  32. package/src/parse/OperatorBinding.ts +146 -0
  33. package/src/parse/ParseAttribute.ts +272 -0
  34. package/src/parse/ParseCall.ts +77 -0
  35. package/src/parse/ParseControlFlow.ts +129 -0
  36. package/src/parse/ParseDirective.ts +105 -0
  37. package/src/parse/ParseExpression.ts +288 -0
  38. package/src/parse/ParseFn.ts +151 -0
  39. package/src/parse/ParseGlobalVar.ts +131 -0
  40. package/src/parse/ParseIdent.ts +77 -0
  41. package/src/parse/ParseImport.ts +160 -0
  42. package/src/parse/ParseLocalVar.ts +69 -0
  43. package/src/parse/ParseLoop.ts +112 -0
  44. package/src/parse/ParseModule.ts +116 -0
  45. package/src/parse/ParseSimpleStatement.ts +162 -0
  46. package/src/parse/ParseStatement.ts +215 -0
  47. package/src/parse/ParseStruct.ts +89 -0
  48. package/src/parse/ParseType.ts +71 -0
  49. package/src/parse/ParseUtil.ts +174 -0
  50. package/src/parse/ParseValueDeclaration.ts +130 -0
  51. package/src/parse/ParseWesl.ts +51 -0
  52. package/src/parse/ParsingContext.ts +93 -0
  53. package/src/parse/WeslStream.ts +63 -20
  54. package/src/parse/stream/CachingStream.ts +48 -0
  55. package/src/parse/stream/MatchersStream.ts +85 -0
  56. package/src/parse/stream/RegexHelpers.ts +38 -0
  57. package/src/test/BevyLink.test.ts +100 -0
  58. package/src/test/BindStdTypes.test.ts +110 -0
  59. package/src/test/{BindWESL.test.ts → BindWESLV2.test.ts} +21 -22
  60. package/src/test/BulkTests.test.ts +11 -12
  61. package/src/test/ConditionLinking.test.ts +107 -0
  62. package/src/test/ConditionalElif.test.ts +1 -13
  63. package/src/test/ConditionalTranslationCases.test.ts +5 -0
  64. package/src/test/ErrorLogging.test.ts +2 -2
  65. package/src/test/ImportCasesV2.test.ts +63 -0
  66. package/src/test/LinkFails.test.ts +69 -0
  67. package/src/test/LinkPackage.test.ts +1 -1
  68. package/src/test/Linker.test.ts +75 -2
  69. package/src/test/LogCatcher.ts +53 -0
  70. package/src/test/Mangling.test.ts +1 -1
  71. package/src/test/ParseComments.test.ts +1 -2
  72. package/src/test/{ParseConditions.test.ts → ParseConditionsV2.test.ts} +57 -49
  73. package/src/test/ParseErrorV2.test.ts +73 -0
  74. package/src/test/{ParseWESL.test.ts → ParseWeslV2.test.ts} +288 -370
  75. package/src/test/{ScopeWESL.test.ts → ScopeWESLV2.test.ts} +205 -176
  76. package/src/test/TestLink.ts +51 -51
  77. package/src/test/TestSetup.ts +9 -3
  78. package/src/test/TestUtil.ts +47 -77
  79. package/src/test/TrimmedMatch.ts +40 -0
  80. package/src/test/VirtualModules.test.ts +33 -2
  81. package/src/test/WeslDevice.test.ts +9 -2
  82. package/src/test/__snapshots__/ParseWeslV2.test.ts.snap +67 -0
  83. package/src/test-util.ts +7 -0
  84. package/src/WESLCollect.ts +0 -656
  85. package/src/parse/AttributeGrammar.ts +0 -232
  86. package/src/parse/ImportGrammar.ts +0 -195
  87. package/src/parse/WeslBaseGrammar.ts +0 -11
  88. package/src/parse/WeslExpression.ts +0 -231
  89. package/src/parse/WeslGrammar.ts +0 -739
  90. package/src/test/Expression.test.ts +0 -22
  91. package/src/test/ImportSyntaxCases.test.ts +0 -24
  92. package/src/test/ParseError.test.ts +0 -45
  93. package/src/test/Reflection.test.ts +0 -176
  94. package/src/test/TransformBindingStructs.test.ts +0 -238
  95. /package/src/test/{ParseElif.test.ts → ParseElifV2.test.ts} +0 -0
@@ -0,0 +1,77 @@
1
+ import type { RefIdentElem } from "../AbstractElems.ts";
2
+ import { makeRefIdentElem, throwParseError } from "./ParseUtil.ts";
3
+ import type { ParsingContext } from "./ParsingContext.ts";
4
+ import type { WeslStream, WeslToken } from "./WeslStream.ts";
5
+
6
+ export interface ParsedModulePath {
7
+ parts: string[];
8
+ start: number;
9
+ end: number;
10
+ }
11
+
12
+ /** WESL keywords that cannot be used as path segment identifiers. */
13
+ export const weslKeywords = new Set([
14
+ "as",
15
+ "import",
16
+ "package",
17
+ "super",
18
+ "self",
19
+ ]);
20
+ const pathPrefixKeywords = new Set(["package", "super"]);
21
+
22
+ /** Parse qualified module path like a::b::c, returning parts and span. */
23
+ export function parseModulePath(stream: WeslStream): ParsedModulePath | null {
24
+ const first = stream.peek();
25
+ if (!first || !isPathSegment(first, true)) return null;
26
+
27
+ const start = first.span[0];
28
+ stream.nextToken();
29
+ const parts = [first.text];
30
+
31
+ while (stream.matchText("::")) {
32
+ const next = stream.peek();
33
+ if (!next || !isPathSegment(next, false))
34
+ throwParseError(stream, "Expected identifier after '::'");
35
+ stream.nextToken();
36
+ parts.push(next.text);
37
+ }
38
+
39
+ return { parts, start, end: stream.checkpoint() };
40
+ }
41
+
42
+ /**
43
+ * Parse identifier, possibly qualified with :: (e.g., package::bar, super::baz).
44
+ * @param conditionRef - if true, ident gets conditionRef flag and isn't added to scope
45
+ */
46
+ export function parseIdent(
47
+ ctx: ParsingContext,
48
+ conditionRef?: true,
49
+ ): RefIdentElem | null {
50
+ const path = parseModulePath(ctx.stream);
51
+ if (!path) return null;
52
+
53
+ const { parts, start, end } = path;
54
+ const ident = ctx.createRefIdent(parts.join("::"));
55
+ if (conditionRef) ident.conditionRef = true;
56
+
57
+ const refIdentElem = makeRefIdentElem(ctx, ident, start, end);
58
+
59
+ // Don't add conditionRef idents to scope - they're only in the expression tree
60
+ if (!conditionRef) {
61
+ ctx.saveIdent(ident);
62
+ ctx.addElem(refIdentElem);
63
+ }
64
+
65
+ return refIdentElem;
66
+ }
67
+
68
+ /** Check if token is valid as a path segment (word or allowed keyword). */
69
+ function isPathSegment(token: WeslToken, isFirst: boolean): boolean {
70
+ if (token.kind === "word") return true;
71
+ if (token.kind !== "keyword") return false;
72
+ if (weslKeywords.has(token.text)) {
73
+ // package/super allowed only as first segment
74
+ return isFirst && pathPrefixKeywords.has(token.text);
75
+ }
76
+ return true; // other keywords (reserved words) allowed
77
+ }
@@ -0,0 +1,160 @@
1
+ /** Token-based parsers for WESL import statements working directly with WeslStream. */
2
+
3
+ import type {
4
+ ImportCollection,
5
+ ImportElem,
6
+ ImportItem,
7
+ ImportSegment,
8
+ ImportStatement,
9
+ } from "../AbstractElems.ts";
10
+ import { parseWeslConditional } from "./ParseAttribute.ts";
11
+ import { weslKeywords } from "./ParseIdent.ts";
12
+ import { parseMany, throwParseError } from "./ParseUtil.ts";
13
+ import type { ParsingContext } from "./ParsingContext.ts";
14
+ import type { WeslStream } from "./WeslStream.ts";
15
+
16
+ /** WESL Grammar: translation_unit : import_statement* global_directive* global_decl* */
17
+ export function parseWeslImports(ctx: ParsingContext): ImportElem[] {
18
+ return [...parseMany(ctx, parseImportStatement)];
19
+ }
20
+
21
+ /** Grammar: import_statement : conditional_attribute? 'import' import_relative? (import_collection | import_path_or_item) ';' */
22
+ function parseImportStatement(ctx: ParsingContext): ImportElem | null {
23
+ const { stream } = ctx;
24
+ const condAttr = parseWeslConditional(ctx);
25
+
26
+ const parseResult = parseImportStatementBase(stream);
27
+ if (!parseResult) {
28
+ if (condAttr) stream.reset(condAttr.start);
29
+ return null;
30
+ }
31
+
32
+ const { statement: imports, importPos } = parseResult;
33
+ const start = condAttr?.start ?? importPos;
34
+ const end = stream.checkpoint();
35
+ const attributes = condAttr ? [condAttr] : undefined;
36
+ return { kind: "import", imports, start, end, attributes };
37
+ }
38
+
39
+ /** Parse: import <relative>? <collection_or_path> ';' */
40
+ function parseImportStatementBase(
41
+ stream: WeslStream,
42
+ ): { statement: ImportStatement; importPos: number } | null {
43
+ const importToken = stream.matchText("import");
44
+ if (!importToken) return null;
45
+
46
+ const relative = parseImportRelative(stream) ?? [];
47
+ const parsed = parseImportCollection(stream) || parseImportPathOrItem(stream);
48
+ if (!parsed) throwParseError(stream, "invalid import, expected { or name");
49
+ if (!stream.matchText(";"))
50
+ throwParseError(stream, "invalid import, expected ';'");
51
+
52
+ const isStatement = parsed.kind === "import-statement";
53
+ const statement = isStatement
54
+ ? prependSegments(relative, parsed)
55
+ : makeStatement(relative, parsed);
56
+ return { statement, importPos: importToken.span[0] };
57
+ }
58
+
59
+ /** WESL Grammar: import_relative : 'package' '::' | 'super' '::' ('super' '::')* */
60
+ function parseImportRelative(stream: WeslStream): ImportSegment[] | null {
61
+ if (stream.matchSequence("package", "::")) return [makeSegment("package")];
62
+
63
+ const segments: ImportSegment[] = [];
64
+ while (stream.matchSequence("super", "::")) {
65
+ segments.push(makeSegment("super"));
66
+ }
67
+ return segments.length > 0 ? segments : null;
68
+ }
69
+
70
+ /** WESL Grammar: import_collection : '{' import_path_or_item (',' import_path_or_item)* ','? '}' */
71
+ function parseImportCollection(stream: WeslStream): ImportCollection | null {
72
+ if (!stream.matchText("{")) return null;
73
+
74
+ const msg = "invalid import collection, expected name";
75
+ const first = parseImportPathOrItem(stream);
76
+ if (!first) throwParseError(stream, msg);
77
+ const statements: ImportStatement[] = [first];
78
+
79
+ while (stream.matchText(",")) {
80
+ if (stream.peek()?.text === "}") break;
81
+ const item = parseImportPathOrItem(stream);
82
+ if (!item) throwParseError(stream, msg + " after ','");
83
+ statements.push(item);
84
+ }
85
+
86
+ if (!stream.matchText("}"))
87
+ throwParseError(stream, "invalid import collection, expected }");
88
+ return makeCollection(statements);
89
+ }
90
+
91
+ /**
92
+ * WESL Grammar: import_path_or_item :
93
+ * ident '::' (import_collection | import_path_or_item) | ident ('as' ident)?
94
+ */
95
+ function parseImportPathOrItem(stream: WeslStream): ImportStatement | null {
96
+ const name = parsePackageWord(stream);
97
+ if (!name) return null;
98
+
99
+ if (stream.matchText("::")) {
100
+ const segment = makeSegment(name);
101
+
102
+ const collection = parseImportCollection(stream);
103
+ if (collection) return makeStatement([segment], collection);
104
+
105
+ const pathOrItem = parseImportPathOrItem(stream);
106
+ if (pathOrItem) return prependSegments([segment], pathOrItem);
107
+
108
+ throwParseError(stream, "invalid import, expected '{' or name");
109
+ }
110
+
111
+ if (stream.matchText("as")) {
112
+ const alias = stream.matchKind("word");
113
+ if (!alias) throwParseError(stream, "invalid alias, expected name");
114
+ return makeStatement([], makeImportItem(name, alias.text));
115
+ }
116
+
117
+ return makeStatement([], makeImportItem(name));
118
+ }
119
+
120
+ /** Prepend path segments to an existing statement */
121
+ function prependSegments(
122
+ segments: ImportSegment[],
123
+ statement: ImportStatement,
124
+ ): ImportStatement {
125
+ return { ...statement, segments: segments.concat(statement.segments) };
126
+ }
127
+
128
+ // -- AST node constructors --
129
+
130
+ function makeStatement(
131
+ segments: ImportSegment[],
132
+ finalSegment: ImportCollection | ImportItem,
133
+ ): ImportStatement {
134
+ return { kind: "import-statement", segments, finalSegment };
135
+ }
136
+
137
+ function makeSegment(name: string): ImportSegment {
138
+ return { kind: "import-segment", name };
139
+ }
140
+
141
+ function makeCollection(subtrees: ImportStatement[]): ImportCollection {
142
+ return { kind: "import-collection", subtrees };
143
+ }
144
+
145
+ /** @return word/keyword token text usable in import path, or null if it's a WESL keyword */
146
+ function parsePackageWord(stream: WeslStream): string | null {
147
+ const token = stream.peek();
148
+ if (!token) return null;
149
+
150
+ const { text, kind } = token;
151
+ if (kind !== "word" && kind !== "keyword") return null;
152
+ if (weslKeywords.has(text)) return null;
153
+
154
+ stream.nextToken();
155
+ return text;
156
+ }
157
+
158
+ function makeImportItem(name: string, as?: string): ImportItem {
159
+ return { kind: "import-item", name, as };
160
+ }
@@ -0,0 +1,69 @@
1
+ import type { AttributeElem, LetElem, VarElem } from "../AbstractElems.ts";
2
+ import { beginElem, finishElem } from "./ContentsHelpers.ts";
3
+ import { skipTemplateList } from "./ParseGlobalVar.ts";
4
+ import { getStartWithAttributes } from "./ParseStatement.ts";
5
+ import {
6
+ attachAttributes,
7
+ expect,
8
+ expectExpression,
9
+ linkDeclIdent,
10
+ throwParseError,
11
+ } from "./ParseUtil.ts";
12
+ import { parseTypedDecl } from "./ParseValueDeclaration.ts";
13
+ import type { ParsingContext } from "./ParsingContext.ts";
14
+
15
+ /**
16
+ * Grammar: variable_or_value_statement : variable_decl | variable_decl '=' expression
17
+ * Grammar: variable_decl : 'var' template_list? optionally_typed_ident
18
+ */
19
+ export function parseLocalVarDecl(
20
+ ctx: ParsingContext,
21
+ attributes?: AttributeElem[],
22
+ ): VarElem | null {
23
+ return parseVarOrLet(ctx, "var", true, false, attributes) as VarElem | null;
24
+ }
25
+
26
+ /** Grammar: 'let' optionally_typed_ident '=' expression */
27
+ export function parseLetDecl(
28
+ ctx: ParsingContext,
29
+ attributes?: AttributeElem[],
30
+ ): LetElem | null {
31
+ return parseVarOrLet(ctx, "let", false, true, attributes) as LetElem | null;
32
+ }
33
+
34
+ /** Shared logic for var/let declarations. */
35
+ function parseVarOrLet(
36
+ ctx: ParsingContext,
37
+ keyword: "var" | "let",
38
+ hasTemplate: boolean,
39
+ requiresInit: boolean,
40
+ attributes?: AttributeElem[],
41
+ ): VarElem | LetElem | null {
42
+ const { stream } = ctx;
43
+ const token = stream.matchText(keyword);
44
+ if (!token) return null;
45
+
46
+ const startPos = getStartWithAttributes(attributes, token.span[0]);
47
+ beginElem(ctx, keyword, attributes);
48
+ if (hasTemplate) skipTemplateList(ctx);
49
+
50
+ const typedDecl = parseTypedDecl(ctx, false);
51
+ if (!typedDecl)
52
+ throwParseError(stream, `Expected identifier after '${keyword}'`);
53
+ ctx.addElem(typedDecl);
54
+
55
+ if (requiresInit) {
56
+ const msg = `${keyword} identifier (${keyword} requires initialization)`;
57
+ expect(stream, "=", msg);
58
+ expectExpression(ctx);
59
+ } else if (stream.matchText("=")) {
60
+ expectExpression(ctx);
61
+ }
62
+
63
+ expect(stream, ";", `${keyword} declaration`);
64
+
65
+ const elem = finishElem(keyword, startPos, ctx, { name: typedDecl });
66
+ attachAttributes(elem, attributes);
67
+ linkDeclIdent(typedDecl, elem);
68
+ return elem;
69
+ }
@@ -0,0 +1,112 @@
1
+ import type {
2
+ AttributeElem,
3
+ ContinuingElem,
4
+ StatementElem,
5
+ } from "../AbstractElems.ts";
6
+ import { parseExpression } from "./ParseExpression.ts";
7
+ import { parseLocalVarDecl } from "./ParseLocalVar.ts";
8
+ import {
9
+ parseAssignmentRhs,
10
+ parseIncDecOperator,
11
+ } from "./ParseSimpleStatement.ts";
12
+ import {
13
+ beginStatement,
14
+ expectCompound,
15
+ finishBlockStatement,
16
+ } from "./ParseStatement.ts";
17
+ import { expect, expectExpression } from "./ParseUtil.ts";
18
+ import type { ParsingContext } from "./ParsingContext.ts";
19
+
20
+ /**
21
+ * Grammar: for_statement : attribute* 'for' '(' for_header ')' compound_statement
22
+ * Grammar: for_header : for_init? ';' expression? ';' for_update?
23
+ */
24
+ export function parseForStatement(
25
+ ctx: ParsingContext,
26
+ attributes?: AttributeElem[],
27
+ ): StatementElem | null {
28
+ const { stream } = ctx;
29
+ const startPos = beginStatement(ctx, "for", attributes);
30
+ if (startPos === null) return null;
31
+
32
+ ctx.pushScope();
33
+ expect(stream, "(", "'for'");
34
+
35
+ parseForInit(ctx);
36
+ parseExpression(ctx); // returns null if empty condition
37
+ expect(stream, ";", "for loop condition");
38
+ parseForUpdate(ctx);
39
+ expect(stream, ")", "for loop header");
40
+
41
+ const body = expectCompound(ctx, "Expected '{' after for loop header");
42
+ ctx.addElem(body);
43
+ ctx.popScope();
44
+
45
+ return finishBlockStatement(startPos, ctx, attributes);
46
+ }
47
+
48
+ /** Grammar: while_statement : attribute* 'while' expression compound_statement */
49
+ export function parseWhileStatement(
50
+ ctx: ParsingContext,
51
+ attributes?: AttributeElem[],
52
+ ): StatementElem | null {
53
+ const startPos = beginStatement(ctx, "while", attributes);
54
+ if (startPos === null) return null;
55
+
56
+ expectExpression(ctx, "Expected condition expression after 'while'");
57
+
58
+ const body = expectCompound(ctx, "Expected '{' after while condition");
59
+ ctx.addElem(body);
60
+
61
+ return finishBlockStatement(startPos, ctx, attributes);
62
+ }
63
+
64
+ /** Grammar: loop_statement : attribute* 'loop' attribute* '{' statement* continuing_statement? '}' */
65
+ export function parseLoopStatement(
66
+ ctx: ParsingContext,
67
+ attributes?: AttributeElem[],
68
+ ): StatementElem | null {
69
+ const startPos = beginStatement(ctx, "loop", attributes);
70
+ if (startPos === null) return null;
71
+
72
+ const body = expectCompound(ctx, "Expected '{' after 'loop'", true);
73
+ ctx.addElem(body);
74
+
75
+ return finishBlockStatement(startPos, ctx, attributes);
76
+ }
77
+
78
+ /** Grammar: continuing_statement : 'continuing' continuing_compound_statement */
79
+ export function parseContinuingStatement(
80
+ ctx: ParsingContext,
81
+ attributes?: AttributeElem[],
82
+ ): ContinuingElem | null {
83
+ const startPos = beginStatement(ctx, "continuing", attributes, "continuing");
84
+ if (startPos === null) return null;
85
+
86
+ const body = expectCompound(ctx, "Expected '{' after 'continuing'");
87
+ ctx.addElem(body);
88
+
89
+ return finishBlockStatement(startPos, ctx, attributes, "continuing");
90
+ }
91
+
92
+ /** Grammar: for_init? ';'
93
+ * for_init : variable_or_value_statement | variable_updating_statement | func_call_statement
94
+ */
95
+ function parseForInit(ctx: ParsingContext): void {
96
+ const { stream } = ctx;
97
+ const varDecl = parseLocalVarDecl(ctx);
98
+ if (varDecl) {
99
+ ctx.addElem(varDecl);
100
+ // parseLocalVarDecl already consumed the ';'
101
+ } else {
102
+ parseExpression(ctx); // returns null for empty case
103
+ expect(stream, ";", "for loop init");
104
+ }
105
+ }
106
+
107
+ /** Grammar: for_update : variable_updating_statement | func_call_statement
108
+ * variable_updating_statement : assignment_statement | increment_statement | decrement_statement */
109
+ function parseForUpdate(ctx: ParsingContext): void {
110
+ parseExpression(ctx);
111
+ parseIncDecOperator(ctx.stream) || parseAssignmentRhs(ctx);
112
+ }
@@ -0,0 +1,116 @@
1
+ import type {
2
+ Attribute,
3
+ AttributeElem,
4
+ ConditionalAttribute,
5
+ ConstAssertElem,
6
+ GlobalDeclarationElem,
7
+ } from "../AbstractElems.ts";
8
+ import { findMap } from "../Util.ts";
9
+ import { parseAttributeList } from "./ParseAttribute.ts";
10
+ import { parseDirective } from "./ParseDirective.ts";
11
+ import { parseFnDecl } from "./ParseFn.ts";
12
+ import {
13
+ parseAliasDecl,
14
+ parseConstAssert,
15
+ parseGlobalVarDecl,
16
+ } from "./ParseGlobalVar.ts";
17
+ import { parseWeslImports } from "./ParseImport.ts";
18
+ import { parseStructDecl } from "./ParseStruct.ts";
19
+ import {
20
+ hasConditionalAttribute,
21
+ parseMany,
22
+ throwParseError,
23
+ } from "./ParseUtil.ts";
24
+ import { parseConstDecl, parseOverrideDecl } from "./ParseValueDeclaration.ts";
25
+ import type { ParsingContext } from "./ParsingContext.ts";
26
+
27
+ const declParsers = [
28
+ parseConstDecl,
29
+ parseOverrideDecl,
30
+ parseGlobalVarDecl,
31
+ parseAliasDecl,
32
+ parseStructDecl,
33
+ parseFnDecl,
34
+ parseConstAssert,
35
+ ];
36
+
37
+ /** Grammar: translation_unit : global_directive* ( global_decl | global_assert | ';' )* */
38
+ export function parseModule(ctx: ParsingContext): void {
39
+ parseImports(ctx);
40
+ parseDirectives(ctx);
41
+ while (parseNextDeclaration(ctx)) {}
42
+ }
43
+
44
+ /** Parse WESL import statements at the start of the module. */
45
+ function parseImports(ctx: ParsingContext): void {
46
+ const importElems = parseWeslImports(ctx);
47
+ for (const importElem of importElems) {
48
+ ctx.addElem(importElem);
49
+ ctx.state.stable.imports.push(importElem.imports);
50
+ }
51
+ }
52
+
53
+ /** Grammar: global_directive : diagnostic_directive | enable_directive | requires_directive */
54
+ function parseDirectives(ctx: ParsingContext): void {
55
+ const directives = parseMany(ctx, parseDirective);
56
+ for (const elem of directives) ctx.addElem(elem);
57
+ }
58
+
59
+ /** Parse one declaration, return true if more may exist. */
60
+ function parseNextDeclaration(ctx: ParsingContext): boolean {
61
+ const { stream } = ctx;
62
+ if (stream.matchText(";")) return true;
63
+
64
+ const attrs = parseAttributeList(ctx);
65
+ const hasConditional = hasConditionalAttribute(attrs);
66
+ if (hasConditional) ctx.pushScope("partial");
67
+
68
+ const parsed = parseDecl(ctx, attrs);
69
+ if (hasConditional && parsed) finalizeConditional(ctx, attrs);
70
+
71
+ if (parsed) return true;
72
+ if (attrs.length)
73
+ throwParseError(stream, "Expected declaration after attributes");
74
+ return false;
75
+ }
76
+
77
+ /** Try each declaration parser until one succeeds. */
78
+ function parseDecl(ctx: ParsingContext, attrs: AttributeElem[]): boolean {
79
+ const attrsOrUndef = attrs.length ? attrs : undefined;
80
+ const elem = findMap(declParsers, p => p(ctx, attrsOrUndef));
81
+ if (elem) {
82
+ recordDecl(ctx, elem, attrs);
83
+ return true;
84
+ }
85
+ return false;
86
+ }
87
+
88
+ /** Pop conditional scope and attach the conditional attribute. */
89
+ function finalizeConditional(
90
+ ctx: ParsingContext,
91
+ attrs: AttributeElem[],
92
+ ): void {
93
+ const partialScope = ctx.popScope();
94
+ partialScope.condAttribute = findMap(attrs, ({ attribute }) =>
95
+ isConditionalAttribute(attribute) ? attribute : undefined,
96
+ );
97
+ }
98
+
99
+ /** Record a parsed declaration, extending start to include attributes. */
100
+ function recordDecl(
101
+ ctx: ParsingContext,
102
+ elem: GlobalDeclarationElem | ConstAssertElem,
103
+ attrs: AttributeElem[],
104
+ ): void {
105
+ if (attrs.length && elem.start > attrs[0].start) elem.start = attrs[0].start;
106
+ ctx.addElem(elem);
107
+ if (elem.kind === "assert") {
108
+ const { stable } = ctx.state;
109
+ stable.moduleAsserts ??= [];
110
+ stable.moduleAsserts.push(elem);
111
+ }
112
+ }
113
+
114
+ function isConditionalAttribute(a: Attribute): a is ConditionalAttribute {
115
+ return a.kind === "@if" || a.kind === "@elif" || a.kind === "@else";
116
+ }
@@ -0,0 +1,162 @@
1
+ import type { AttributeElem, StatementElem } from "../AbstractElems.ts";
2
+ import { beginElem, finishContents } from "./ContentsHelpers.ts";
3
+ import { parseExpression } from "./ParseExpression.ts";
4
+ import {
5
+ finishBlockStatement,
6
+ getStartWithAttributes,
7
+ } from "./ParseStatement.ts";
8
+ import { expect, expectExpression, throwParseError } from "./ParseUtil.ts";
9
+ import type { ParsingContext } from "./ParsingContext.ts";
10
+ import type { WeslStream } from "./WeslStream.ts";
11
+
12
+ const assignmentOps = new Set([
13
+ "=",
14
+ "+=",
15
+ "-=",
16
+ "*=",
17
+ "/=",
18
+ "%=",
19
+ "&=",
20
+ "|=",
21
+ "^=",
22
+ "<<=",
23
+ ">>=",
24
+ ]);
25
+
26
+ /**
27
+ * Grammar: return_statement : 'return' expression?
28
+ * Grammar: break_statement : 'break' | 'break' 'if' expression
29
+ * Grammar: continue_statement : 'continue'
30
+ * Grammar: variable_updating_statement : assignment_statement | increment_statement | decrement_statement
31
+ * Grammar: func_call_statement : call_phrase
32
+ */
33
+ export function parseSimpleStatement(
34
+ ctx: ParsingContext,
35
+ attributes?: AttributeElem[],
36
+ ): StatementElem | null {
37
+ const { stream } = ctx;
38
+ const startPos = getStartWithAttributes(attributes, stream.checkpoint());
39
+
40
+ return (
41
+ parseReturnStmt(ctx, startPos, attributes) ||
42
+ parseBreakStmt(ctx, startPos, attributes) ||
43
+ parseKeywordStmt(ctx, startPos, attributes, "continue") ||
44
+ parseKeywordStmt(ctx, startPos, attributes, "discard") ||
45
+ parseEmptyStmt(stream, startPos) ||
46
+ parsePhonyAssignment(ctx, startPos, attributes) ||
47
+ parseExpressionStmt(ctx, startPos, attributes)
48
+ );
49
+ }
50
+
51
+ /** Grammar: return_statement : 'return' expression? ';' */
52
+ function parseReturnStmt(
53
+ ctx: ParsingContext,
54
+ startPos: number,
55
+ attributes?: AttributeElem[],
56
+ ): StatementElem | null {
57
+ const { stream } = ctx;
58
+ if (!stream.matchText("return")) return null;
59
+ beginElem(ctx, "statement", attributes);
60
+ parseExpression(ctx);
61
+ expect(stream, ";", "return statement");
62
+ return finishBlockStatement(startPos, ctx, attributes);
63
+ }
64
+
65
+ /**
66
+ * Grammar: break_statement : 'break' ';'
67
+ * Grammar: break_if_statement : 'break' 'if' expression ';'
68
+ */
69
+ function parseBreakStmt(
70
+ ctx: ParsingContext,
71
+ startPos: number,
72
+ attributes?: AttributeElem[],
73
+ ): StatementElem | null {
74
+ const { stream } = ctx;
75
+ if (!stream.matchText("break")) return null;
76
+ beginElem(ctx, "statement", attributes);
77
+ if (stream.matchText("if")) {
78
+ expectExpression(ctx, "Expected condition after 'break if'");
79
+ }
80
+ expect(stream, ";", "break statement");
81
+ return finishBlockStatement(startPos, ctx, attributes);
82
+ }
83
+
84
+ /** Grammar: continue_statement : 'continue' ';' also handles 'discard' */
85
+ function parseKeywordStmt(
86
+ ctx: ParsingContext,
87
+ startPos: number,
88
+ attributes: AttributeElem[] | undefined,
89
+ keyword: string,
90
+ ): StatementElem | null {
91
+ const { stream } = ctx;
92
+ if (!stream.matchText(keyword)) return null;
93
+ beginElem(ctx, "statement", attributes);
94
+ expect(stream, ";", `${keyword} statement`);
95
+ return finishBlockStatement(startPos, ctx, attributes);
96
+ }
97
+
98
+ /** Parse empty statement (just ';'). */
99
+ function parseEmptyStmt(
100
+ stream: WeslStream,
101
+ start: number,
102
+ ): StatementElem | null {
103
+ if (!stream.matchText(";")) return null;
104
+ const end = stream.checkpoint();
105
+ return { kind: "statement", start, end, contents: [] };
106
+ }
107
+
108
+ /** Grammar: assignment_statement : '_' '=' expression ';' (phony assignment) */
109
+ function parsePhonyAssignment(
110
+ ctx: ParsingContext,
111
+ startPos: number,
112
+ attributes?: AttributeElem[],
113
+ ): StatementElem | null {
114
+ const { stream } = ctx;
115
+ if (!stream.matchText("_")) return null;
116
+ if (!parseAssignmentOperator(stream))
117
+ throwParseError(stream, "Expected assignment operator after '_'");
118
+ beginElem(ctx, "statement", attributes);
119
+ expectExpression(ctx, "Expected expression after assignment operator");
120
+ expect(stream, ";", "assignment");
121
+ return finishBlockStatement(startPos, ctx, attributes);
122
+ }
123
+
124
+ /**
125
+ * Parses expression statements: assignments, increments/decrements, or function calls.
126
+ * Grammar: ( assignment_statement | increment_statement | decrement_statement | call_phrase ) ';'
127
+ */
128
+ function parseExpressionStmt(
129
+ ctx: ParsingContext,
130
+ startPos: number,
131
+ attributes?: AttributeElem[],
132
+ ): StatementElem | null {
133
+ const { stream } = ctx;
134
+ beginElem(ctx, "statement", attributes);
135
+ const expr = parseExpression(ctx);
136
+ if (!expr) {
137
+ finishContents(ctx, startPos, startPos);
138
+ stream.reset(startPos);
139
+ return null;
140
+ }
141
+
142
+ if (!parseIncDecOperator(stream)) parseAssignmentRhs(ctx);
143
+ expect(stream, ";", "expression");
144
+ return finishBlockStatement(startPos, ctx, attributes);
145
+ }
146
+
147
+ /** Grammar: assignment_statement : lhs_expression ( '=' | compound_assignment_operator ) expression */
148
+ export function parseAssignmentOperator(stream: WeslStream): boolean {
149
+ return !!stream.nextIf(({ text }) => assignmentOps.has(text));
150
+ }
151
+
152
+ /** Grammar: ( '=' | compound_assignment_operator ) expression (rhs of assignment_statement) */
153
+ export function parseAssignmentRhs(ctx: ParsingContext): boolean {
154
+ if (!parseAssignmentOperator(ctx.stream)) return false;
155
+ expectExpression(ctx, "Expected expression after assignment operator");
156
+ return true;
157
+ }
158
+
159
+ /** Grammar: increment_statement : lhs_expression '++' ; decrement_statement : lhs_expression '--' */
160
+ export function parseIncDecOperator(stream: WeslStream): boolean {
161
+ return !!stream.nextIf(({ text }) => text === "++" || text === "--");
162
+ }