wesl 0.7.26 → 0.7.28
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 +1 -1
- package/dist/index.d.ts +283 -147
- package/dist/index.js +1765 -1143
- package/package.json +2 -2
- package/src/AbstractElems.ts +264 -82
- package/src/ClickableError.ts +8 -1
- package/src/Linker.ts +63 -67
- package/src/LinkerUtil.ts +141 -7
- package/src/LowerAndEmit.ts +660 -304
- package/src/Mangler.ts +1 -1
- package/src/ModuleResolver.ts +15 -4
- package/src/ParseWESL.ts +21 -33
- package/src/SrcMap.ts +7 -19
- package/src/StandardTypes.ts +1 -1
- package/src/WeslDevice.ts +1 -0
- package/src/debug/ASTtoString.ts +92 -76
- package/src/index.ts +1 -1
- package/src/parse/AttachComments.ts +289 -0
- package/src/parse/ExpressionUtil.ts +3 -3
- package/src/parse/Keywords.ts +1 -1
- package/src/parse/ParseAttribute.ts +49 -71
- package/src/parse/ParseCall.ts +3 -4
- package/src/parse/ParseControlFlow.ts +100 -56
- package/src/parse/ParseDirective.ts +9 -8
- package/src/parse/ParseDoBlock.ts +63 -0
- package/src/parse/ParseExpression.ts +11 -10
- package/src/parse/ParseFn.ts +11 -33
- package/src/parse/ParseGlobalVar.ts +36 -27
- package/src/parse/ParseIdent.ts +4 -5
- package/src/parse/ParseLocalVar.ts +16 -12
- package/src/parse/ParseLoop.ts +76 -39
- package/src/parse/ParseModule.ts +65 -19
- package/src/parse/ParseSimpleStatement.ts +112 -66
- package/src/parse/ParseStatement.ts +40 -67
- package/src/parse/ParseStruct.ts +8 -22
- package/src/parse/ParseType.ts +10 -13
- package/src/parse/ParseUtil.ts +26 -12
- package/src/parse/ParseValueDeclaration.ts +18 -31
- package/src/parse/ParseWesl.ts +11 -14
- package/src/parse/ParsingContext.ts +11 -9
- package/src/parse/WeslStream.ts +138 -121
- package/src/parse/stream/RegexMatchers.ts +45 -0
- package/src/parse/stream/WeslLexer.ts +249 -0
- package/src/test/BevyLink.test.ts +18 -11
- package/src/test/ConditionalElif.test.ts +4 -2
- package/src/test/DeclCommentEmit.test.ts +122 -0
- package/src/test/DoBlock.test.ts +164 -0
- package/src/test/FilterValidElements.test.ts +4 -6
- package/src/test/Linker.test.ts +23 -7
- package/src/test/Mangling.test.ts +8 -4
- package/src/test/ParseComments.test.ts +234 -6
- package/src/test/ParseConditionsV2.test.ts +47 -175
- package/src/test/ParseElifV2.test.ts +12 -33
- package/src/test/ParseErrorV2.test.ts +0 -0
- package/src/test/ParseWeslV2.test.ts +247 -626
- package/src/test/StatementEmit.test.ts +143 -0
- package/src/test/StripWesl.ts +24 -3
- package/src/test/TestLink.ts +19 -3
- package/src/test/TestUtil.ts +18 -8
- package/src/test/Tokenizer.test.ts +96 -0
- package/src/test/__snapshots__/ParseWeslV2.test.ts.snap +10 -42
- package/src/RawEmit.ts +0 -103
- package/src/Reflection.ts +0 -336
- package/src/TransformBindingStructs.ts +0 -320
- package/src/parse/ContentsHelpers.ts +0 -70
- package/src/parse/stream/CachingStream.ts +0 -48
- package/src/parse/stream/MatchersStream.ts +0 -85
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type {
|
|
2
|
+
AttributeElem,
|
|
3
|
+
ExpressionElem,
|
|
4
|
+
LetElem,
|
|
5
|
+
VarElem,
|
|
6
|
+
} from "../AbstractElems.ts";
|
|
7
|
+
import { parseTemplateList } from "./ParseGlobalVar.ts";
|
|
8
|
+
import { finishStatement, getStartWithAttributes } from "./ParseStatement.ts";
|
|
5
9
|
import {
|
|
6
|
-
attachAttributes,
|
|
7
10
|
expect,
|
|
8
11
|
expectExpression,
|
|
9
12
|
linkDeclIdent,
|
|
@@ -44,26 +47,27 @@ function parseVarOrLet(
|
|
|
44
47
|
if (!token) return null;
|
|
45
48
|
|
|
46
49
|
const startPos = getStartWithAttributes(attributes, token.span[0]);
|
|
47
|
-
|
|
48
|
-
if (hasTemplate) skipTemplateList(ctx);
|
|
50
|
+
const template = hasTemplate ? parseTemplateList(ctx) : undefined;
|
|
49
51
|
|
|
50
52
|
const typedDecl = parseTypedDecl(ctx, false);
|
|
51
53
|
if (!typedDecl)
|
|
52
54
|
throwParseError(stream, `Expected identifier after '${keyword}'`);
|
|
53
|
-
ctx.addElem(typedDecl);
|
|
54
55
|
|
|
56
|
+
let init: ExpressionElem | undefined;
|
|
55
57
|
if (requiresInit) {
|
|
56
58
|
const msg = `${keyword} identifier (${keyword} requires initialization)`;
|
|
57
59
|
expect(stream, "=", msg);
|
|
58
|
-
expectExpression(ctx);
|
|
60
|
+
init = expectExpression(ctx);
|
|
59
61
|
} else if (stream.matchText("=")) {
|
|
60
|
-
expectExpression(ctx);
|
|
62
|
+
init = expectExpression(ctx);
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
expect(stream, ";", `${keyword} declaration`);
|
|
64
66
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
+
const params = { name: typedDecl, init };
|
|
68
|
+
const elem = finishStatement(keyword, startPos, ctx, params, attributes);
|
|
69
|
+
// template lives outside the shared fields: only VarElem (not LetElem) has it.
|
|
70
|
+
if (template && elem.kind === "var") elem.template = template;
|
|
67
71
|
linkDeclIdent(typedDecl, elem);
|
|
68
72
|
return elem;
|
|
69
73
|
}
|
package/src/parse/ParseLoop.ts
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AttributeElem,
|
|
3
3
|
ContinuingElem,
|
|
4
|
-
|
|
4
|
+
ExpressionElem,
|
|
5
|
+
ForElem,
|
|
6
|
+
ForInit,
|
|
7
|
+
ForUpdate,
|
|
8
|
+
LoopElem,
|
|
9
|
+
WhileElem,
|
|
5
10
|
} from "../AbstractElems.ts";
|
|
6
|
-
import { parseExpression } from "./ParseExpression.ts";
|
|
7
11
|
import { parseLocalVarDecl } from "./ParseLocalVar.ts";
|
|
8
|
-
import {
|
|
9
|
-
parseAssignmentRhs,
|
|
10
|
-
parseIncDecOperator,
|
|
11
|
-
} from "./ParseSimpleStatement.ts";
|
|
12
|
+
import { parseAssignmentRhs, parseIncDecOp } from "./ParseSimpleStatement.ts";
|
|
12
13
|
import {
|
|
13
14
|
beginStatement,
|
|
14
15
|
expectCompound,
|
|
15
|
-
|
|
16
|
+
finishStatement,
|
|
16
17
|
} from "./ParseStatement.ts";
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
expect,
|
|
20
|
+
expectExpression,
|
|
21
|
+
parseContentExpression,
|
|
22
|
+
throwParseError,
|
|
23
|
+
} from "./ParseUtil.ts";
|
|
18
24
|
import type { ParsingContext } from "./ParsingContext.ts";
|
|
19
25
|
|
|
20
26
|
/**
|
|
@@ -24,7 +30,7 @@ import type { ParsingContext } from "./ParsingContext.ts";
|
|
|
24
30
|
export function parseForStatement(
|
|
25
31
|
ctx: ParsingContext,
|
|
26
32
|
attributes?: AttributeElem[],
|
|
27
|
-
):
|
|
33
|
+
): ForElem | null {
|
|
28
34
|
const { stream } = ctx;
|
|
29
35
|
const startPos = beginStatement(ctx, "for", attributes);
|
|
30
36
|
if (startPos === null) return null;
|
|
@@ -32,48 +38,49 @@ export function parseForStatement(
|
|
|
32
38
|
ctx.pushScope();
|
|
33
39
|
expect(stream, "(", "'for'");
|
|
34
40
|
|
|
35
|
-
parseForInit(ctx);
|
|
36
|
-
const
|
|
37
|
-
if (cond && ctx.options.preserveExpressions) ctx.addElem(cond);
|
|
41
|
+
const init = parseForInit(ctx);
|
|
42
|
+
const condition = parseContentExpression(ctx) ?? undefined;
|
|
38
43
|
expect(stream, ";", "for loop condition");
|
|
39
|
-
parseForUpdate(ctx);
|
|
44
|
+
const update = parseForUpdate(ctx);
|
|
40
45
|
expect(stream, ")", "for loop header");
|
|
41
46
|
|
|
42
47
|
const body = expectCompound(ctx, "Expected '{' after for loop header");
|
|
43
|
-
ctx.addElem(body);
|
|
44
48
|
ctx.popScope();
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
const params = { init, condition, update, body };
|
|
51
|
+
return finishStatement("for", startPos, ctx, params, attributes);
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
/** Grammar: while_statement : attribute* 'while' expression compound_statement */
|
|
50
55
|
export function parseWhileStatement(
|
|
51
56
|
ctx: ParsingContext,
|
|
52
57
|
attributes?: AttributeElem[],
|
|
53
|
-
):
|
|
58
|
+
): WhileElem | null {
|
|
54
59
|
const startPos = beginStatement(ctx, "while", attributes);
|
|
55
60
|
if (startPos === null) return null;
|
|
56
61
|
|
|
57
|
-
expectExpression(ctx, "Expected condition
|
|
58
|
-
|
|
62
|
+
const condition = expectExpression(ctx, "Expected condition after 'while'");
|
|
59
63
|
const body = expectCompound(ctx, "Expected '{' after while condition");
|
|
60
|
-
ctx.addElem(body);
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
const params = { condition, body };
|
|
66
|
+
return finishStatement("while", startPos, ctx, params, attributes);
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
/** Grammar: loop_statement : attribute* 'loop' attribute* '{' statement* continuing_statement? '}' */
|
|
66
70
|
export function parseLoopStatement(
|
|
67
71
|
ctx: ParsingContext,
|
|
68
72
|
attributes?: AttributeElem[],
|
|
69
|
-
):
|
|
73
|
+
): LoopElem | null {
|
|
70
74
|
const startPos = beginStatement(ctx, "loop", attributes);
|
|
71
75
|
if (startPos === null) return null;
|
|
72
76
|
|
|
73
77
|
const body = expectCompound(ctx, "Expected '{' after 'loop'", true);
|
|
74
|
-
|
|
78
|
+
const continuing = body.body.find(
|
|
79
|
+
(s): s is ContinuingElem => s.kind === "continuing",
|
|
80
|
+
);
|
|
75
81
|
|
|
76
|
-
|
|
82
|
+
const params = { body, continuing };
|
|
83
|
+
return finishStatement("loop", startPos, ctx, params, attributes);
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
/** Grammar: continuing_statement : 'continuing' continuing_compound_statement */
|
|
@@ -81,35 +88,65 @@ export function parseContinuingStatement(
|
|
|
81
88
|
ctx: ParsingContext,
|
|
82
89
|
attributes?: AttributeElem[],
|
|
83
90
|
): ContinuingElem | null {
|
|
84
|
-
const startPos = beginStatement(ctx, "continuing", attributes
|
|
91
|
+
const startPos = beginStatement(ctx, "continuing", attributes);
|
|
85
92
|
if (startPos === null) return null;
|
|
86
93
|
|
|
87
94
|
const body = expectCompound(ctx, "Expected '{' after 'continuing'");
|
|
88
|
-
|
|
95
|
+
const breakIf = body.body.find(s => s.kind === "break")?.condition;
|
|
89
96
|
|
|
90
|
-
|
|
97
|
+
const params = { body, breakIf };
|
|
98
|
+
return finishStatement("continuing", startPos, ctx, params, attributes);
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
/** Grammar: for_init? ';'
|
|
94
102
|
* for_init : variable_or_value_statement | variable_updating_statement | func_call_statement
|
|
95
103
|
*/
|
|
96
|
-
function parseForInit(ctx: ParsingContext):
|
|
104
|
+
function parseForInit(ctx: ParsingContext): ForInit | undefined {
|
|
97
105
|
const { stream } = ctx;
|
|
98
106
|
const varDecl = parseLocalVarDecl(ctx);
|
|
99
107
|
if (varDecl) {
|
|
100
|
-
|
|
101
|
-
// parseLocalVarDecl already consumed the ';'
|
|
102
|
-
} else {
|
|
103
|
-
const expr = parseExpression(ctx); // returns null for empty case
|
|
104
|
-
if (expr && ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
105
|
-
expect(stream, ";", "for loop init");
|
|
108
|
+
return varDecl; // parseLocalVarDecl already consumed the ';'
|
|
106
109
|
}
|
|
110
|
+
const expr = parseContentExpression(ctx); // null for empty case
|
|
111
|
+
const update = expr ? finishForUpdate(ctx, expr) : undefined;
|
|
112
|
+
expect(stream, ";", "for loop init");
|
|
113
|
+
return update;
|
|
107
114
|
}
|
|
108
115
|
|
|
109
|
-
/** Grammar: for_update : variable_updating_statement | func_call_statement
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
/** Grammar: for_update : variable_updating_statement | func_call_statement */
|
|
117
|
+
function parseForUpdate(ctx: ParsingContext): ForUpdate | undefined {
|
|
118
|
+
const expr = parseContentExpression(ctx);
|
|
119
|
+
if (!expr) return undefined;
|
|
120
|
+
return finishForUpdate(ctx, expr);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Build a typed for-init/for-update node from an already-parsed lhs expression,
|
|
125
|
+
* consuming its trailing operator (++/-- or an assignment + rhs).
|
|
126
|
+
*/
|
|
127
|
+
function finishForUpdate(ctx: ParsingContext, expr: ExpressionElem): ForUpdate {
|
|
128
|
+
const { stream } = ctx;
|
|
129
|
+
const start = expr.start;
|
|
130
|
+
const incDec = parseIncDecOp(stream);
|
|
131
|
+
if (incDec) {
|
|
132
|
+
const end = stream.checkpoint();
|
|
133
|
+
if (incDec.op === "++")
|
|
134
|
+
return { kind: "increment", target: expr, start, end };
|
|
135
|
+
return { kind: "decrement", target: expr, start, end };
|
|
136
|
+
}
|
|
137
|
+
const assign = parseAssignmentRhs(ctx);
|
|
138
|
+
if (assign) {
|
|
139
|
+
const { op, rhs } = assign;
|
|
140
|
+
const end = stream.checkpoint();
|
|
141
|
+
return { kind: "assign", lhs: expr, op, rhs, start, end };
|
|
142
|
+
}
|
|
143
|
+
if (expr.kind === "call-expression") {
|
|
144
|
+
return { kind: "call", call: expr, start, end: stream.checkpoint() };
|
|
145
|
+
}
|
|
146
|
+
// A bare expression (no ++/--/assignment, not a call) is not a valid for
|
|
147
|
+
// init/update clause; reject it rather than silently dropping it.
|
|
148
|
+
throwParseError(
|
|
149
|
+
stream,
|
|
150
|
+
"Expected an assignment, increment, decrement, or call in for-clause",
|
|
151
|
+
);
|
|
115
152
|
}
|
package/src/parse/ParseModule.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
2
|
+
AbstractElem,
|
|
3
3
|
AttributeElem,
|
|
4
|
-
ConditionalAttribute,
|
|
5
4
|
ConstAssertElem,
|
|
6
5
|
GlobalDeclarationElem,
|
|
6
|
+
ModuleElem,
|
|
7
7
|
} from "../AbstractElems.ts";
|
|
8
|
+
import { declsOfKind } from "../LinkerUtil.ts";
|
|
9
|
+
import { ParseError } from "../ParseError.ts";
|
|
8
10
|
import { findMap } from "../Util.ts";
|
|
9
11
|
import { parseAttributeList } from "./ParseAttribute.ts";
|
|
10
12
|
import { parseDirective } from "./ParseDirective.ts";
|
|
13
|
+
import { parseDoBlock } from "./ParseDoBlock.ts";
|
|
11
14
|
import { parseFnDecl } from "./ParseFn.ts";
|
|
12
15
|
import {
|
|
13
16
|
parseAliasDecl,
|
|
@@ -17,6 +20,8 @@ import {
|
|
|
17
20
|
import { parseWeslImports } from "./ParseImport.ts";
|
|
18
21
|
import { parseStructDecl } from "./ParseStruct.ts";
|
|
19
22
|
import {
|
|
23
|
+
attrsOrUndef,
|
|
24
|
+
conditionalAttribute,
|
|
20
25
|
hasConditionalAttribute,
|
|
21
26
|
parseMany,
|
|
22
27
|
throwParseError,
|
|
@@ -31,6 +36,7 @@ const declParsers = [
|
|
|
31
36
|
parseAliasDecl,
|
|
32
37
|
parseStructDecl,
|
|
33
38
|
parseFnDecl,
|
|
39
|
+
parseDoBlock,
|
|
34
40
|
parseConstAssert,
|
|
35
41
|
];
|
|
36
42
|
|
|
@@ -39,13 +45,46 @@ export function parseModule(ctx: ParsingContext): void {
|
|
|
39
45
|
parseImports(ctx);
|
|
40
46
|
parseDirectives(ctx);
|
|
41
47
|
while (parseNextDeclaration(ctx)) {}
|
|
48
|
+
// reject input the declaration loop couldn't consume (e.g. a directive after a
|
|
49
|
+
// declaration, or stray tokens); otherwise it would be silently dropped
|
|
50
|
+
if (ctx.stream.peek() !== null)
|
|
51
|
+
throwParseError(ctx.stream, "Expected a declaration or directive");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Reject a module that gives a `do` block a name that clashes with a fn/global
|
|
56
|
+
* or with another `do` block (`do` blocks share the module's declaration
|
|
57
|
+
* namespace, and runners key blocks by name so a duplicate would silently
|
|
58
|
+
* shadow the earlier one). This is a small module-local pass, deliberately not
|
|
59
|
+
* part of bindIdents.
|
|
60
|
+
*/
|
|
61
|
+
export function checkDoBlockNames(moduleElem: ModuleElem): void {
|
|
62
|
+
const doBlocks = declsOfKind(moduleElem, "do");
|
|
63
|
+
if (doBlocks.length === 0) return;
|
|
64
|
+
|
|
65
|
+
const declNames = new Set(moduleElem.decls.map(globalDeclName));
|
|
66
|
+
const seen = new Set<string>();
|
|
67
|
+
for (const block of doBlocks) {
|
|
68
|
+
const { name, start, end } = block.name;
|
|
69
|
+
if (declNames.has(name))
|
|
70
|
+
throw new ParseError(`'${name}' declared as both fn and do`, [
|
|
71
|
+
start,
|
|
72
|
+
end,
|
|
73
|
+
]);
|
|
74
|
+
if (seen.has(name))
|
|
75
|
+
throw new ParseError(`'${name}' declared as do more than once`, [
|
|
76
|
+
start,
|
|
77
|
+
end,
|
|
78
|
+
]);
|
|
79
|
+
seen.add(name);
|
|
80
|
+
}
|
|
42
81
|
}
|
|
43
82
|
|
|
44
83
|
/** Parse WESL import statements at the start of the module. */
|
|
45
84
|
function parseImports(ctx: ParsingContext): void {
|
|
46
85
|
const importElems = parseWeslImports(ctx);
|
|
47
86
|
for (const importElem of importElems) {
|
|
48
|
-
ctx.
|
|
87
|
+
ctx.addModuleDecl(importElem);
|
|
49
88
|
ctx.state.stable.imports.push(importElem.imports);
|
|
50
89
|
}
|
|
51
90
|
}
|
|
@@ -53,7 +92,7 @@ function parseImports(ctx: ParsingContext): void {
|
|
|
53
92
|
/** Grammar: global_directive : diagnostic_directive | enable_directive | requires_directive */
|
|
54
93
|
function parseDirectives(ctx: ParsingContext): void {
|
|
55
94
|
const directives = parseMany(ctx, parseDirective);
|
|
56
|
-
for (const elem of directives) ctx.
|
|
95
|
+
for (const elem of directives) ctx.addModuleDecl(elem);
|
|
57
96
|
}
|
|
58
97
|
|
|
59
98
|
/** Parse one declaration, return true if more may exist. */
|
|
@@ -74,15 +113,28 @@ function parseNextDeclaration(ctx: ParsingContext): boolean {
|
|
|
74
113
|
return false;
|
|
75
114
|
}
|
|
76
115
|
|
|
116
|
+
/** @return the declared name of a module-level declaration, if it has one. */
|
|
117
|
+
function globalDeclName(elem: AbstractElem): string | undefined {
|
|
118
|
+
switch (elem.kind) {
|
|
119
|
+
case "fn":
|
|
120
|
+
case "struct":
|
|
121
|
+
case "alias":
|
|
122
|
+
return elem.name.ident.originalName;
|
|
123
|
+
case "gvar":
|
|
124
|
+
case "const":
|
|
125
|
+
case "override":
|
|
126
|
+
return elem.name.decl.ident.originalName;
|
|
127
|
+
default:
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
77
132
|
/** Try each declaration parser until one succeeds. */
|
|
78
133
|
function parseDecl(ctx: ParsingContext, attrs: AttributeElem[]): boolean {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
|
-
return false;
|
|
134
|
+
const elem = findMap(declParsers, p => p(ctx, attrsOrUndef(attrs)));
|
|
135
|
+
if (!elem) return false;
|
|
136
|
+
recordDecl(ctx, elem, attrs);
|
|
137
|
+
return true;
|
|
86
138
|
}
|
|
87
139
|
|
|
88
140
|
/** Pop conditional scope and attach the conditional attribute. */
|
|
@@ -91,9 +143,7 @@ function finalizeConditional(
|
|
|
91
143
|
attrs: AttributeElem[],
|
|
92
144
|
): void {
|
|
93
145
|
const partialScope = ctx.popScope();
|
|
94
|
-
partialScope.condAttribute =
|
|
95
|
-
isConditionalAttribute(attribute) ? attribute : undefined,
|
|
96
|
-
);
|
|
146
|
+
partialScope.condAttribute = conditionalAttribute(attrs);
|
|
97
147
|
}
|
|
98
148
|
|
|
99
149
|
/** Record a parsed declaration, extending start to include attributes. */
|
|
@@ -103,14 +153,10 @@ function recordDecl(
|
|
|
103
153
|
attrs: AttributeElem[],
|
|
104
154
|
): void {
|
|
105
155
|
if (attrs.length && elem.start > attrs[0].start) elem.start = attrs[0].start;
|
|
106
|
-
ctx.
|
|
156
|
+
ctx.addModuleDecl(elem);
|
|
107
157
|
if (elem.kind === "assert") {
|
|
108
158
|
const { stable } = ctx.state;
|
|
109
159
|
stable.moduleAsserts ??= [];
|
|
110
160
|
stable.moduleAsserts.push(elem);
|
|
111
161
|
}
|
|
112
162
|
}
|
|
113
|
-
|
|
114
|
-
function isConditionalAttribute(a: Attribute): a is ConditionalAttribute {
|
|
115
|
-
return a.kind === "@if" || a.kind === "@elif" || a.kind === "@else";
|
|
116
|
-
}
|
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
AssignElem,
|
|
3
|
+
AssignOp,
|
|
4
|
+
AttributeElem,
|
|
5
|
+
BreakElem,
|
|
6
|
+
CallElem,
|
|
7
|
+
ContinueElem,
|
|
8
|
+
DecrementElem,
|
|
9
|
+
DiscardElem,
|
|
10
|
+
EmptyElem,
|
|
11
|
+
ExpressionElem,
|
|
12
|
+
IncrementElem,
|
|
13
|
+
PhonyTarget,
|
|
14
|
+
ReturnElem,
|
|
15
|
+
Statement,
|
|
16
|
+
} from "../AbstractElems.ts";
|
|
17
|
+
import type { Span } from "../Span.ts";
|
|
3
18
|
import { parseExpression } from "./ParseExpression.ts";
|
|
19
|
+
import { finishStatement, getStartWithAttributes } from "./ParseStatement.ts";
|
|
4
20
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
21
|
+
expect,
|
|
22
|
+
expectExpression,
|
|
23
|
+
parseContentExpression,
|
|
24
|
+
throwParseError,
|
|
25
|
+
} from "./ParseUtil.ts";
|
|
9
26
|
import type { ParsingContext } from "./ParsingContext.ts";
|
|
10
27
|
import type { WeslStream } from "./WeslStream.ts";
|
|
11
28
|
|
|
@@ -33,34 +50,61 @@ const assignmentOps = new Set([
|
|
|
33
50
|
export function parseSimpleStatement(
|
|
34
51
|
ctx: ParsingContext,
|
|
35
52
|
attributes?: AttributeElem[],
|
|
36
|
-
):
|
|
53
|
+
): Statement | null {
|
|
37
54
|
const { stream } = ctx;
|
|
38
55
|
const startPos = getStartWithAttributes(attributes, stream.checkpoint());
|
|
39
56
|
|
|
40
57
|
return (
|
|
41
58
|
parseReturnStmt(ctx, startPos, attributes) ||
|
|
42
59
|
parseBreakStmt(ctx, startPos, attributes) ||
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
parseContinueStmt(ctx, startPos, attributes) ||
|
|
61
|
+
parseDiscardStmt(ctx, startPos, attributes) ||
|
|
45
62
|
parseEmptyStmt(stream, startPos) ||
|
|
46
63
|
parsePhonyAssignment(ctx, startPos, attributes) ||
|
|
47
64
|
parseExpressionStmt(ctx, startPos, attributes)
|
|
48
65
|
);
|
|
49
66
|
}
|
|
50
67
|
|
|
68
|
+
/** Match an assignment operator, capturing its value and span. */
|
|
69
|
+
export function parseAssignmentOp(stream: WeslStream): AssignOp | null {
|
|
70
|
+
const token = stream.nextIf(({ text }) => assignmentOps.has(text));
|
|
71
|
+
if (!token) return null;
|
|
72
|
+
return { value: token.text as AssignOp["value"], span: token.span };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Match '++' or '--', returning its text and span (or null if absent). */
|
|
76
|
+
export function parseIncDecOp(
|
|
77
|
+
stream: WeslStream,
|
|
78
|
+
): { op: "++" | "--"; span: Span } | null {
|
|
79
|
+
const token = stream.nextIf(({ text }) => text === "++" || text === "--");
|
|
80
|
+
if (!token) return null;
|
|
81
|
+
return { op: token.text as "++" | "--", span: token.span };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Grammar: ( '=' | compound_assignment_operator ) expression. Returns op + rhs. */
|
|
85
|
+
export function parseAssignmentRhs(
|
|
86
|
+
ctx: ParsingContext,
|
|
87
|
+
): { op: AssignOp; rhs: ExpressionElem } | null {
|
|
88
|
+
const op = parseAssignmentOp(ctx.stream);
|
|
89
|
+
if (!op) return null;
|
|
90
|
+
const rhs = expectExpression(
|
|
91
|
+
ctx,
|
|
92
|
+
"Expected expression after assignment operator",
|
|
93
|
+
);
|
|
94
|
+
return { op, rhs };
|
|
95
|
+
}
|
|
96
|
+
|
|
51
97
|
/** Grammar: return_statement : 'return' expression? ';' */
|
|
52
98
|
function parseReturnStmt(
|
|
53
99
|
ctx: ParsingContext,
|
|
54
100
|
startPos: number,
|
|
55
101
|
attributes?: AttributeElem[],
|
|
56
|
-
):
|
|
102
|
+
): ReturnElem | null {
|
|
57
103
|
const { stream } = ctx;
|
|
58
104
|
if (!stream.matchText("return")) return null;
|
|
59
|
-
|
|
60
|
-
const expr = parseExpression(ctx);
|
|
61
|
-
if (expr && ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
105
|
+
const value = parseContentExpression(ctx) ?? undefined;
|
|
62
106
|
expect(stream, ";", "return statement");
|
|
63
|
-
return
|
|
107
|
+
return finishStatement("return", startPos, ctx, { value }, attributes);
|
|
64
108
|
}
|
|
65
109
|
|
|
66
110
|
/**
|
|
@@ -71,39 +115,46 @@ function parseBreakStmt(
|
|
|
71
115
|
ctx: ParsingContext,
|
|
72
116
|
startPos: number,
|
|
73
117
|
attributes?: AttributeElem[],
|
|
74
|
-
):
|
|
118
|
+
): BreakElem | null {
|
|
75
119
|
const { stream } = ctx;
|
|
76
120
|
if (!stream.matchText("break")) return null;
|
|
77
|
-
|
|
121
|
+
let condition: ExpressionElem | undefined;
|
|
78
122
|
if (stream.matchText("if")) {
|
|
79
|
-
expectExpression(ctx, "Expected condition after 'break if'");
|
|
123
|
+
condition = expectExpression(ctx, "Expected condition after 'break if'");
|
|
80
124
|
}
|
|
81
125
|
expect(stream, ";", "break statement");
|
|
82
|
-
return
|
|
126
|
+
return finishStatement("break", startPos, ctx, { condition }, attributes);
|
|
83
127
|
}
|
|
84
128
|
|
|
85
|
-
/** Grammar: continue_statement : 'continue' ';'
|
|
86
|
-
function
|
|
129
|
+
/** Grammar: continue_statement : 'continue' ';' */
|
|
130
|
+
function parseContinueStmt(
|
|
87
131
|
ctx: ParsingContext,
|
|
88
132
|
startPos: number,
|
|
89
|
-
attributes
|
|
90
|
-
|
|
91
|
-
): StatementElem | null {
|
|
133
|
+
attributes?: AttributeElem[],
|
|
134
|
+
): ContinueElem | null {
|
|
92
135
|
const { stream } = ctx;
|
|
93
|
-
if (!stream.matchText(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return finishBlockStatement(startPos, ctx, attributes);
|
|
136
|
+
if (!stream.matchText("continue")) return null;
|
|
137
|
+
expect(stream, ";", "continue statement");
|
|
138
|
+
return finishStatement("continue", startPos, ctx, {}, attributes);
|
|
97
139
|
}
|
|
98
140
|
|
|
99
|
-
/**
|
|
100
|
-
function
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
141
|
+
/** Grammar: 'discard' ';' */
|
|
142
|
+
function parseDiscardStmt(
|
|
143
|
+
ctx: ParsingContext,
|
|
144
|
+
startPos: number,
|
|
145
|
+
attributes?: AttributeElem[],
|
|
146
|
+
): DiscardElem | null {
|
|
147
|
+
const { stream } = ctx;
|
|
148
|
+
if (!stream.matchText("discard")) return null;
|
|
149
|
+
expect(stream, ";", "discard statement");
|
|
150
|
+
return finishStatement("discard", startPos, ctx, {}, attributes);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Parse empty statement (just ';'). Spans the ';' so it emits no extra text. */
|
|
154
|
+
function parseEmptyStmt(stream: WeslStream, start: number): EmptyElem | null {
|
|
104
155
|
if (!stream.matchText(";")) return null;
|
|
105
156
|
const end = stream.checkpoint();
|
|
106
|
-
return { kind: "
|
|
157
|
+
return { kind: "empty", start, end };
|
|
107
158
|
}
|
|
108
159
|
|
|
109
160
|
/** Grammar: assignment_statement : '_' '=' expression ';' (phony assignment) */
|
|
@@ -111,54 +162,49 @@ function parsePhonyAssignment(
|
|
|
111
162
|
ctx: ParsingContext,
|
|
112
163
|
startPos: number,
|
|
113
164
|
attributes?: AttributeElem[],
|
|
114
|
-
):
|
|
165
|
+
): AssignElem | null {
|
|
115
166
|
const { stream } = ctx;
|
|
116
|
-
|
|
117
|
-
if (!
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
167
|
+
const underscore = stream.matchText("_");
|
|
168
|
+
if (!underscore) return null;
|
|
169
|
+
const lhs: PhonyTarget = { kind: "phony", span: underscore.span };
|
|
170
|
+
// WGSL phony assignment uses only `=`, never a compound operator.
|
|
171
|
+
const eq = stream.matchText("=");
|
|
172
|
+
if (!eq) throwParseError(stream, "Expected '=' after '_'");
|
|
173
|
+
const op: AssignOp = { value: "=", span: eq.span };
|
|
174
|
+
const rhs = expectExpression(ctx, "Expected expression after '_ ='");
|
|
121
175
|
expect(stream, ";", "assignment");
|
|
122
|
-
return
|
|
176
|
+
return finishStatement("assign", startPos, ctx, { lhs, op, rhs }, attributes);
|
|
123
177
|
}
|
|
124
178
|
|
|
125
|
-
/**
|
|
126
|
-
* Parses expression statements: assignments, increments/decrements, or function calls.
|
|
127
|
-
* Grammar: ( assignment_statement | increment_statement | decrement_statement | call_phrase ) ';'
|
|
128
|
-
*/
|
|
179
|
+
/** Grammar: ( assignment_statement | increment_statement | decrement_statement | call_phrase ) ';' */
|
|
129
180
|
function parseExpressionStmt(
|
|
130
181
|
ctx: ParsingContext,
|
|
131
182
|
startPos: number,
|
|
132
183
|
attributes?: AttributeElem[],
|
|
133
|
-
):
|
|
184
|
+
): AssignElem | IncrementElem | DecrementElem | CallElem | null {
|
|
134
185
|
const { stream } = ctx;
|
|
135
|
-
beginElem(ctx, "statement", attributes);
|
|
136
186
|
const expr = parseExpression(ctx);
|
|
137
187
|
if (!expr) {
|
|
138
|
-
finishContents(ctx, startPos, startPos);
|
|
139
188
|
stream.reset(startPos);
|
|
140
189
|
return null;
|
|
141
190
|
}
|
|
142
|
-
if (ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
143
|
-
|
|
144
|
-
if (!parseIncDecOperator(stream)) parseAssignmentRhs(ctx);
|
|
145
|
-
expect(stream, ";", "expression");
|
|
146
|
-
return finishBlockStatement(startPos, ctx, attributes);
|
|
147
|
-
}
|
|
148
191
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
192
|
+
const incDec = parseIncDecOp(stream);
|
|
193
|
+
if (incDec) {
|
|
194
|
+
expect(stream, ";", "expression");
|
|
195
|
+
const kind = incDec.op === "++" ? "increment" : "decrement";
|
|
196
|
+
return finishStatement(kind, startPos, ctx, { target: expr }, attributes);
|
|
197
|
+
}
|
|
153
198
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
199
|
+
const assign = parseAssignmentRhs(ctx);
|
|
200
|
+
expect(stream, ";", "expression");
|
|
201
|
+
if (assign) {
|
|
202
|
+
const params = { lhs: expr, op: assign.op, rhs: assign.rhs };
|
|
203
|
+
return finishStatement("assign", startPos, ctx, params, attributes);
|
|
204
|
+
}
|
|
160
205
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
206
|
+
if (expr.kind !== "call-expression") {
|
|
207
|
+
throwParseError(stream, "Expected call, assignment, or increment");
|
|
208
|
+
}
|
|
209
|
+
return finishStatement("call", startPos, ctx, { call: expr }, attributes);
|
|
164
210
|
}
|