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,14 +1,11 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AttributeElem,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
IfAttribute,
|
|
8
|
-
StatementElem,
|
|
3
|
+
BlockElem,
|
|
4
|
+
ElemKindMap,
|
|
5
|
+
HasAttributes,
|
|
6
|
+
Statement,
|
|
9
7
|
} from "../AbstractElems.ts";
|
|
10
8
|
import { findMap } from "../Util.ts";
|
|
11
|
-
import { beginElem, finishElem } from "./ContentsHelpers.ts";
|
|
12
9
|
import { parseAttributeList } from "./ParseAttribute.ts";
|
|
13
10
|
import { parseIfStatement, parseSwitchStatement } from "./ParseControlFlow.ts";
|
|
14
11
|
import { parseConstAssert } from "./ParseGlobalVar.ts";
|
|
@@ -22,16 +19,15 @@ import {
|
|
|
22
19
|
import { parseSimpleStatement } from "./ParseSimpleStatement.ts";
|
|
23
20
|
import {
|
|
24
21
|
attachAttributes,
|
|
22
|
+
attrsOrUndef,
|
|
23
|
+
conditionalAttribute,
|
|
25
24
|
expect,
|
|
26
25
|
hasConditionalAttribute,
|
|
27
|
-
isConditionalAttribute,
|
|
28
26
|
throwParseError,
|
|
29
27
|
} from "./ParseUtil.ts";
|
|
30
28
|
import { parseConstDecl } from "./ParseValueDeclaration.ts";
|
|
31
29
|
import type { ParsingContext } from "./ParsingContext.ts";
|
|
32
30
|
|
|
33
|
-
type CondAttr = IfAttribute | ElifAttribute | ElseAttribute;
|
|
34
|
-
|
|
35
31
|
interface CompoundOptions {
|
|
36
32
|
loopBody?: boolean;
|
|
37
33
|
noScope?: boolean; // for function bodies (scope shared with params)
|
|
@@ -39,11 +35,11 @@ interface CompoundOptions {
|
|
|
39
35
|
|
|
40
36
|
// Experimental: declarations in conditional blocks visible in outer scope.
|
|
41
37
|
// e.g. @if(X) { let y = 1; } makes y visible outside the block.
|
|
42
|
-
// see https://github.com/
|
|
38
|
+
// see https://github.com/webgpu-tools/wesl-spec/issues/158
|
|
43
39
|
const conditionalBlockFeature = true;
|
|
44
40
|
|
|
45
41
|
/** Function bodies share scope with parameters (per WGSL spec). */
|
|
46
|
-
export function parseFunctionBody(ctx: ParsingContext):
|
|
42
|
+
export function parseFunctionBody(ctx: ParsingContext): BlockElem | null {
|
|
47
43
|
return parseCompoundStatement(ctx, undefined, { noScope: true });
|
|
48
44
|
}
|
|
49
45
|
|
|
@@ -55,22 +51,20 @@ export function parseCompoundStatement(
|
|
|
55
51
|
ctx: ParsingContext,
|
|
56
52
|
attributes?: AttributeElem[],
|
|
57
53
|
options?: CompoundOptions,
|
|
58
|
-
):
|
|
54
|
+
): BlockElem | null {
|
|
59
55
|
const brace = ctx.stream.matchText("{");
|
|
60
56
|
if (!brace) return null;
|
|
61
57
|
|
|
62
58
|
const startPos = getStartWithAttributes(attributes, brace.span[0]);
|
|
63
59
|
|
|
64
|
-
beginElem(ctx, "statement", attributes);
|
|
65
|
-
|
|
66
60
|
const skipScope =
|
|
67
61
|
options?.noScope ||
|
|
68
62
|
(conditionalBlockFeature && hasConditionalAttr(attributes));
|
|
69
63
|
if (!skipScope) ctx.pushScope();
|
|
70
|
-
parseBlockStatements(ctx, options?.loopBody);
|
|
64
|
+
const body = parseBlockStatements(ctx, options?.loopBody);
|
|
71
65
|
if (!skipScope) ctx.popScope();
|
|
72
66
|
|
|
73
|
-
return
|
|
67
|
+
return finishStatement("block", startPos, ctx, { body }, attributes);
|
|
74
68
|
}
|
|
75
69
|
|
|
76
70
|
/** Grammar: attribute* compound_statement (for control flow bodies) */
|
|
@@ -78,11 +72,10 @@ export function expectCompound(
|
|
|
78
72
|
ctx: ParsingContext,
|
|
79
73
|
errorMsg: string,
|
|
80
74
|
loopBody?: boolean,
|
|
81
|
-
):
|
|
75
|
+
): BlockElem {
|
|
82
76
|
const attrs = parseAttributeList(ctx);
|
|
83
|
-
const attrsOrUndef = attrs.length > 0 ? attrs : undefined;
|
|
84
77
|
const options = loopBody ? { loopBody } : undefined;
|
|
85
|
-
const block = parseCompoundStatement(ctx, attrsOrUndef, options);
|
|
78
|
+
const block = parseCompoundStatement(ctx, attrsOrUndef(attrs), options);
|
|
86
79
|
if (!block) throwParseError(ctx.stream, errorMsg);
|
|
87
80
|
return block;
|
|
88
81
|
}
|
|
@@ -95,40 +88,30 @@ export function getStartWithAttributes(
|
|
|
95
88
|
return attributes?.[0]?.start ?? keywordPos;
|
|
96
89
|
}
|
|
97
90
|
|
|
98
|
-
/** Match keyword and
|
|
91
|
+
/** Match keyword and return the statement's start position (or null if no match). */
|
|
99
92
|
export function beginStatement(
|
|
100
93
|
ctx: ParsingContext,
|
|
101
94
|
keyword: string,
|
|
102
95
|
attributes?: AttributeElem[],
|
|
103
|
-
kind: "statement" | "continuing" = "statement",
|
|
104
96
|
): number | null {
|
|
105
|
-
const
|
|
106
|
-
if (!
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return
|
|
97
|
+
const token = ctx.stream.matchText(keyword);
|
|
98
|
+
if (!token) return null;
|
|
99
|
+
// Start at the keyword token, not any leading comment, so a preceding comment
|
|
100
|
+
// falls in the gap before the statement and attaches as leading.
|
|
101
|
+
return getStartWithAttributes(attributes, token.span[0]);
|
|
110
102
|
}
|
|
111
103
|
|
|
112
|
-
/**
|
|
113
|
-
export function
|
|
114
|
-
|
|
115
|
-
ctx: ParsingContext,
|
|
116
|
-
attributes?: AttributeElem[],
|
|
117
|
-
): StatementElem;
|
|
118
|
-
export function finishBlockStatement(
|
|
119
|
-
start: number,
|
|
120
|
-
ctx: ParsingContext,
|
|
121
|
-
attributes: AttributeElem[] | undefined,
|
|
122
|
-
kind: "continuing",
|
|
123
|
-
): ContinuingElem;
|
|
124
|
-
export function finishBlockStatement(
|
|
104
|
+
/** Build a statement element from its typed fields and attach its attributes. */
|
|
105
|
+
export function finishStatement<K extends keyof ElemKindMap>(
|
|
106
|
+
kind: K,
|
|
125
107
|
start: number,
|
|
126
108
|
ctx: ParsingContext,
|
|
109
|
+
params: Omit<ElemKindMap[K], "kind" | "start" | "end">,
|
|
127
110
|
attributes?: AttributeElem[],
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const elem =
|
|
131
|
-
attachAttributes(elem, attributes);
|
|
111
|
+
): ElemKindMap[K] {
|
|
112
|
+
const end = ctx.stream.checkpoint();
|
|
113
|
+
const elem = { kind, start, end, ...params } as ElemKindMap[K];
|
|
114
|
+
attachAttributes(elem as HasAttributes, attributes);
|
|
132
115
|
return elem;
|
|
133
116
|
}
|
|
134
117
|
|
|
@@ -137,18 +120,23 @@ function hasConditionalAttr(attributes?: AttributeElem[]): boolean {
|
|
|
137
120
|
}
|
|
138
121
|
|
|
139
122
|
/** Grammar: statement* '}' (after '{' consumed). Loop bodies may end with continuing. */
|
|
140
|
-
function parseBlockStatements(
|
|
123
|
+
function parseBlockStatements(
|
|
124
|
+
ctx: ParsingContext,
|
|
125
|
+
loopBody?: boolean,
|
|
126
|
+
): Statement[] {
|
|
141
127
|
const { stream } = ctx;
|
|
128
|
+
const body: Statement[] = [];
|
|
142
129
|
while (true) {
|
|
143
130
|
if (stream.matchText("}")) break;
|
|
144
131
|
const stmt = parseStatement(ctx);
|
|
145
132
|
if (!stmt) throwParseError(stream, "Expected statement or '}'");
|
|
146
|
-
|
|
133
|
+
body.push(stmt);
|
|
147
134
|
if (loopBody && stmt.kind === "continuing") {
|
|
148
135
|
expect(stream, "}", "continuing block");
|
|
149
136
|
break;
|
|
150
137
|
}
|
|
151
138
|
}
|
|
139
|
+
return body;
|
|
152
140
|
}
|
|
153
141
|
|
|
154
142
|
/**
|
|
@@ -159,7 +147,7 @@ function parseBlockStatements(ctx: ParsingContext, loopBody?: boolean): void {
|
|
|
159
147
|
* | 'discard' ';' | variable_updating_statement ';' | compound_statement
|
|
160
148
|
* | const_assert_statement ';'
|
|
161
149
|
*/
|
|
162
|
-
function parseStatement(ctx: ParsingContext):
|
|
150
|
+
function parseStatement(ctx: ParsingContext): Statement | null {
|
|
163
151
|
const { stream } = ctx;
|
|
164
152
|
const startPos = stream.checkpoint();
|
|
165
153
|
const attributes = parseAttributeList(ctx);
|
|
@@ -174,7 +162,6 @@ function parseStatement(ctx: ParsingContext): BlockStatement | null {
|
|
|
174
162
|
attributes.length > 0 && hasConditionalAttribute(attributes);
|
|
175
163
|
if (hasConditional) ctx.pushScope("partial");
|
|
176
164
|
|
|
177
|
-
const attrsOrUndef = attributes.length > 0 ? attributes : undefined;
|
|
178
165
|
const parsers = [
|
|
179
166
|
parseLocalVarDecl,
|
|
180
167
|
parseLetDecl,
|
|
@@ -189,27 +176,13 @@ function parseStatement(ctx: ParsingContext): BlockStatement | null {
|
|
|
189
176
|
parseContinuingStatement,
|
|
190
177
|
parseSimpleStatement,
|
|
191
178
|
];
|
|
192
|
-
const stmt = findMap(parsers, p => p(ctx, attrsOrUndef));
|
|
193
|
-
if (!stmt) return null;
|
|
194
|
-
|
|
195
|
-
finalizeConditional(ctx, hasConditional, attributes);
|
|
196
|
-
return stmt as BlockStatement;
|
|
197
|
-
}
|
|
179
|
+
const stmt = findMap(parsers, p => p(ctx, attrsOrUndef(attributes)));
|
|
198
180
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
hasConditional: boolean,
|
|
202
|
-
attributes: AttributeElem[],
|
|
203
|
-
): void {
|
|
181
|
+
// Always pop the partial scope we pushed, even on the no-match path, so the
|
|
182
|
+
// scope stack stays balanced; only a matched statement gets the condition.
|
|
204
183
|
if (hasConditional) {
|
|
205
184
|
const partialScope = ctx.popScope();
|
|
206
|
-
partialScope.condAttribute =
|
|
185
|
+
if (stmt) partialScope.condAttribute = conditionalAttribute(attributes);
|
|
207
186
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
function getConditionalAttribute(
|
|
211
|
-
attributes: AttributeElem[],
|
|
212
|
-
): CondAttr | undefined {
|
|
213
|
-
const found = attributes.find(a => isConditionalAttribute(a.attribute));
|
|
214
|
-
return found?.attribute as CondAttr | undefined;
|
|
187
|
+
return stmt ? (stmt as Statement) : null;
|
|
215
188
|
}
|
package/src/parse/ParseStruct.ts
CHANGED
|
@@ -3,12 +3,11 @@ import type {
|
|
|
3
3
|
StructElem,
|
|
4
4
|
StructMemberElem,
|
|
5
5
|
} from "../AbstractElems.ts";
|
|
6
|
-
import { beginElem, finishElem } from "./ContentsHelpers.ts";
|
|
7
6
|
import { parseAttributeList } from "./ParseAttribute.ts";
|
|
8
|
-
import { getStartWithAttributes } from "./ParseStatement.ts";
|
|
7
|
+
import { finishStatement, getStartWithAttributes } from "./ParseStatement.ts";
|
|
9
8
|
import { parseSimpleTypeRef } from "./ParseType.ts";
|
|
10
9
|
import {
|
|
11
|
-
|
|
10
|
+
attrsOrUndef,
|
|
12
11
|
createDeclIdentElem,
|
|
13
12
|
expect,
|
|
14
13
|
expectWord,
|
|
@@ -37,53 +36,40 @@ export function parseStructDecl(
|
|
|
37
36
|
const identElem = createDeclIdentElem(ctx, nameToken, true);
|
|
38
37
|
ctx.saveIdent(identElem.ident);
|
|
39
38
|
|
|
40
|
-
beginElem(ctx, "struct", attributes);
|
|
41
|
-
ctx.addElem(identElem);
|
|
42
39
|
expect(stream, "{", "struct name");
|
|
43
40
|
|
|
44
41
|
ctx.pushScope();
|
|
45
|
-
const members =
|
|
42
|
+
const members = parseCommaList(ctx, parseStructMember);
|
|
46
43
|
identElem.ident.dependentScope = ctx.currentScope();
|
|
47
44
|
ctx.popScope();
|
|
48
45
|
|
|
49
46
|
expect(stream, "}", "struct member");
|
|
50
47
|
|
|
51
|
-
const
|
|
52
|
-
|
|
48
|
+
const params = { name: identElem, members };
|
|
49
|
+
const elem = finishStatement("struct", start, ctx, params, attributes);
|
|
53
50
|
linkDeclIdentElem(identElem, elem);
|
|
54
51
|
return elem;
|
|
55
52
|
}
|
|
56
53
|
|
|
57
|
-
/** Grammar: struct_body_decl : '{' struct_member (',' struct_member)* ','? '}' */
|
|
58
|
-
function parseStructMembers(ctx: ParsingContext): StructMemberElem[] {
|
|
59
|
-
const members = parseCommaList(ctx, parseStructMember);
|
|
60
|
-
for (const member of members) ctx.addElem(member);
|
|
61
|
-
return members;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
54
|
/** Grammar: struct_member : attribute* member_ident ':' type_specifier */
|
|
65
55
|
function parseStructMember(ctx: ParsingContext): StructMemberElem | null {
|
|
66
56
|
const { stream } = ctx;
|
|
67
57
|
const checkpoint = stream.checkpoint();
|
|
68
|
-
const
|
|
58
|
+
const attrs = parseAttributeList(ctx);
|
|
69
59
|
|
|
70
60
|
const nameToken = stream.matchKind("word");
|
|
71
61
|
if (!nameToken) {
|
|
72
62
|
stream.reset(checkpoint);
|
|
73
63
|
return null;
|
|
74
64
|
}
|
|
65
|
+
const attributes = attrsOrUndef(attrs);
|
|
75
66
|
|
|
76
67
|
const start = getStartWithAttributes(attributes, nameToken.span[0]);
|
|
77
|
-
beginElem(ctx, "member", attributes.length ? attributes : undefined);
|
|
78
68
|
const name = makeNameElem(nameToken);
|
|
79
|
-
ctx.addElem(name);
|
|
80
69
|
expect(stream, ":", "struct member name");
|
|
81
70
|
|
|
82
71
|
const typeRef = parseSimpleTypeRef(ctx);
|
|
83
72
|
if (!typeRef) throwParseError(stream, "Expected type after ':'");
|
|
84
|
-
ctx.addElem(typeRef);
|
|
85
73
|
|
|
86
|
-
|
|
87
|
-
attachAttributes(elem, attributes.length ? attributes : undefined);
|
|
88
|
-
return elem;
|
|
74
|
+
return finishStatement("member", start, ctx, { name, typeRef }, attributes);
|
|
89
75
|
}
|
package/src/parse/ParseType.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { TypeRefElem, TypeTemplateParameter } from "../AbstractElems.ts";
|
|
2
|
-
import { beginElem, finishElem } from "./ContentsHelpers.ts";
|
|
3
2
|
import { parseExpression } from "./ParseExpression.ts";
|
|
4
3
|
import { parseModulePath } from "./ParseIdent.ts";
|
|
5
4
|
import { makeRefIdentElem, throwParseError } from "./ParseUtil.ts";
|
|
@@ -18,17 +17,15 @@ export function parseSimpleTypeRef(ctx: ParsingContext): TypeRefElem | null {
|
|
|
18
17
|
const { parts, start, end: nameEnd } = path;
|
|
19
18
|
const refIdent = ctx.createRefIdent(parts.join("::"));
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const refIdentElem = makeRefIdentElem(ctx, refIdent, start, nameEnd);
|
|
20
|
+
makeRefIdentElem(ctx, refIdent, start, nameEnd);
|
|
24
21
|
ctx.saveIdent(refIdent);
|
|
25
|
-
ctx.addElem(refIdentElem);
|
|
26
22
|
|
|
27
23
|
const templateParams = ctx.stream.nextTemplateStartToken()
|
|
28
24
|
? parseTemplateParams(ctx)
|
|
29
25
|
: undefined;
|
|
30
26
|
|
|
31
|
-
|
|
27
|
+
const end = ctx.stream.checkpoint();
|
|
28
|
+
return { kind: "type", name: refIdent, templateParams, start, end };
|
|
32
29
|
}
|
|
33
30
|
|
|
34
31
|
/** Parse comma-separated template parameters until closing '>'. */
|
|
@@ -37,16 +34,15 @@ export function parseTemplateParams(
|
|
|
37
34
|
): TypeTemplateParameter[] {
|
|
38
35
|
const { stream } = ctx;
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
if (consumeTemplateEnd(stream)) {
|
|
38
|
+
throwParseError(stream, "Empty template parameter list '<>'");
|
|
39
|
+
}
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
const params: TypeTemplateParameter[] = [parseTemplateParam(ctx)];
|
|
41
|
+
const params = [parseTemplateParam(ctx)];
|
|
45
42
|
while (stream.matchText(",")) {
|
|
46
43
|
params.push(parseTemplateParam(ctx));
|
|
47
44
|
}
|
|
48
45
|
|
|
49
|
-
// Must end with >
|
|
50
46
|
if (!consumeTemplateEnd(stream))
|
|
51
47
|
throwParseError(stream, "Expected '>' or ',' after template parameter");
|
|
52
48
|
|
|
@@ -66,6 +62,7 @@ function parseTemplateParam(ctx: ParsingContext): TypeTemplateParameter {
|
|
|
66
62
|
// parseExpression handles template_elaborated_ident via parsePrimaryExpr
|
|
67
63
|
// inTemplate prevents '>' from being parsed as comparison operator
|
|
68
64
|
const expr = parseExpression(ctx, { inTemplate: true });
|
|
69
|
-
if (expr)
|
|
70
|
-
|
|
65
|
+
if (!expr)
|
|
66
|
+
throwParseError(ctx.stream, "Expected expression in template parameters");
|
|
67
|
+
return expr;
|
|
71
68
|
}
|
package/src/parse/ParseUtil.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
Attribute,
|
|
3
3
|
AttributeElem,
|
|
4
|
+
ConditionalAttribute,
|
|
4
5
|
DeclarationElem,
|
|
5
6
|
DeclIdentElem,
|
|
6
7
|
ExpressionElem,
|
|
@@ -11,12 +12,10 @@ import type {
|
|
|
11
12
|
import { ParseError } from "../ParseError.ts";
|
|
12
13
|
import type { RefIdent } from "../Scope.ts";
|
|
13
14
|
import type { Stream, Token } from "../Stream.ts";
|
|
14
|
-
import { parseExpression } from "./ParseExpression.ts";
|
|
15
|
+
import { type ExpressionOpts, parseExpression } from "./ParseExpression.ts";
|
|
15
16
|
import type { ParsingContext } from "./ParsingContext.ts";
|
|
16
17
|
import type { WeslStream, WeslToken } from "./WeslStream.ts";
|
|
17
18
|
|
|
18
|
-
// --- Stream/token expectations ---
|
|
19
|
-
|
|
20
19
|
/** Match text and throw ParseError if not found. */
|
|
21
20
|
export function expect(
|
|
22
21
|
stream: WeslStream,
|
|
@@ -47,17 +46,24 @@ export function expectWord(
|
|
|
47
46
|
return token as WeslToken<"word">;
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
/** Parse
|
|
49
|
+
/** Parse a required expression, throwing if absent. */
|
|
51
50
|
export function expectExpression(
|
|
52
51
|
ctx: ParsingContext,
|
|
53
52
|
errorMsg = "Expected expression",
|
|
54
53
|
): ExpressionElem {
|
|
55
54
|
const expr = parseExpression(ctx);
|
|
56
55
|
if (!expr) throwParseError(ctx.stream, errorMsg);
|
|
57
|
-
if (ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
58
56
|
return expr;
|
|
59
57
|
}
|
|
60
58
|
|
|
59
|
+
/** Parse an optional expression. */
|
|
60
|
+
export function parseContentExpression(
|
|
61
|
+
ctx: ParsingContext,
|
|
62
|
+
opts?: ExpressionOpts,
|
|
63
|
+
): ExpressionElem | null {
|
|
64
|
+
return parseExpression(ctx, opts);
|
|
65
|
+
}
|
|
66
|
+
|
|
61
67
|
/** Throw a ParseError at the current/next token position. */
|
|
62
68
|
export function throwParseError(stream: Stream<Token>, message: string): never {
|
|
63
69
|
const weslStream = stream as WeslStream;
|
|
@@ -68,8 +74,6 @@ export function throwParseError(stream: Stream<Token>, message: string): never {
|
|
|
68
74
|
throw new ParseError(message, span);
|
|
69
75
|
}
|
|
70
76
|
|
|
71
|
-
// --- List parsing ---
|
|
72
|
-
|
|
73
77
|
/** Parse comma-separated items. Caller handles delimiters. */
|
|
74
78
|
export function parseCommaList<T>(
|
|
75
79
|
ctx: ParsingContext,
|
|
@@ -93,8 +97,6 @@ export function* parseMany<T>(
|
|
|
93
97
|
for (let elem = parse(ctx); elem; elem = parse(ctx)) yield elem;
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
// --- Element creation ---
|
|
97
|
-
|
|
98
100
|
/** Create a NameElem from a word token. */
|
|
99
101
|
export function makeNameElem(token: WeslToken<"word">): NameElem {
|
|
100
102
|
const [start, end] = token.span;
|
|
@@ -136,8 +138,7 @@ export function makeRefIdentElem(
|
|
|
136
138
|
return elem;
|
|
137
139
|
}
|
|
138
140
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
+
/** @return true if the attribute is a conditional (@if, @elif, @else) */
|
|
141
142
|
export function isConditionalAttribute(attr: Attribute): boolean {
|
|
142
143
|
const { kind } = attr;
|
|
143
144
|
return kind === "@if" || kind === "@elif" || kind === "@else";
|
|
@@ -148,6 +149,14 @@ export function hasConditionalAttribute(attributes: AttributeElem[]): boolean {
|
|
|
148
149
|
return attributes.some(attr => isConditionalAttribute(attr.attribute));
|
|
149
150
|
}
|
|
150
151
|
|
|
152
|
+
/** The first conditional (@if, @elif, @else) attribute in the list, if any. */
|
|
153
|
+
export function conditionalAttribute(
|
|
154
|
+
attributes: AttributeElem[],
|
|
155
|
+
): ConditionalAttribute | undefined {
|
|
156
|
+
const found = attributes.find(a => isConditionalAttribute(a.attribute));
|
|
157
|
+
return found?.attribute as ConditionalAttribute | undefined;
|
|
158
|
+
}
|
|
159
|
+
|
|
151
160
|
/** Attach non-empty attributes array to element. */
|
|
152
161
|
export function attachAttributes<T extends { attributes?: AttributeElem[] }>(
|
|
153
162
|
elem: T,
|
|
@@ -156,7 +165,12 @@ export function attachAttributes<T extends { attributes?: AttributeElem[] }>(
|
|
|
156
165
|
if (attributes?.length) elem.attributes = attributes;
|
|
157
166
|
}
|
|
158
167
|
|
|
159
|
-
|
|
168
|
+
/** Normalize an empty attribute list to undefined (the elem's "no attributes"). */
|
|
169
|
+
export function attrsOrUndef(
|
|
170
|
+
attrs: AttributeElem[],
|
|
171
|
+
): AttributeElem[] | undefined {
|
|
172
|
+
return attrs.length ? attrs : undefined;
|
|
173
|
+
}
|
|
160
174
|
|
|
161
175
|
/** Link a DeclIdentElem's ident to its parent declaration. */
|
|
162
176
|
export function linkDeclIdentElem(
|
|
@@ -2,16 +2,15 @@ import type {
|
|
|
2
2
|
AttributeElem,
|
|
3
3
|
ConstElem,
|
|
4
4
|
ElemKindMap,
|
|
5
|
+
ExpressionElem,
|
|
5
6
|
OverrideElem,
|
|
6
7
|
TypedDeclElem,
|
|
7
8
|
TypeRefElem,
|
|
8
9
|
} from "../AbstractElems.ts";
|
|
9
10
|
import type { Scope } from "../Scope.ts";
|
|
10
|
-
import {
|
|
11
|
-
import { getStartWithAttributes } from "./ParseStatement.ts";
|
|
11
|
+
import { finishStatement, getStartWithAttributes } from "./ParseStatement.ts";
|
|
12
12
|
import { parseSimpleTypeRef } from "./ParseType.ts";
|
|
13
13
|
import {
|
|
14
|
-
attachAttributes,
|
|
15
14
|
createDeclIdentElem,
|
|
16
15
|
expect,
|
|
17
16
|
expectExpression,
|
|
@@ -27,7 +26,7 @@ export function parseConstDecl(
|
|
|
27
26
|
ctx: ParsingContext,
|
|
28
27
|
attributes?: AttributeElem[],
|
|
29
28
|
): ConstElem | null {
|
|
30
|
-
return parseValueDecl(ctx, "const", true, isModuleScope(
|
|
29
|
+
return parseValueDecl(ctx, "const", true, ctx.isModuleScope(), attributes);
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
/** Grammar: 'override' optionally_typed_ident ( '=' expression )? */
|
|
@@ -47,16 +46,13 @@ export function parseTypedDecl(
|
|
|
47
46
|
if (!nameToken) return null;
|
|
48
47
|
const start = nameToken.span[0];
|
|
49
48
|
|
|
50
|
-
beginElem(ctx, "typeDecl");
|
|
51
49
|
const decl = createDeclIdentElem(ctx, nameToken, isGlobal);
|
|
52
|
-
ctx.addElem(decl);
|
|
53
50
|
ctx.saveIdent(decl.ident);
|
|
54
51
|
|
|
55
52
|
const { typeRef, typeScope } = parseOptionalType(ctx);
|
|
56
53
|
|
|
57
54
|
const end = ctx.stream.checkpoint();
|
|
58
|
-
|
|
59
|
-
return { kind: "typeDecl", decl, typeRef, typeScope, start, end, contents };
|
|
55
|
+
return { kind: "typeDecl", decl, typeRef, typeScope, start, end };
|
|
60
56
|
}
|
|
61
57
|
|
|
62
58
|
/** Shared parser for const/override declarations. */
|
|
@@ -73,44 +69,36 @@ function parseValueDecl<K extends ValueDeclKind>(
|
|
|
73
69
|
|
|
74
70
|
const startPos = getStartWithAttributes(attributes, token.span[0]);
|
|
75
71
|
ctx.pushScope("partial");
|
|
76
|
-
beginElem(ctx, keyword, attributes);
|
|
77
72
|
|
|
78
73
|
const typedDecl = parseTypedDecl(ctx, isGlobal);
|
|
79
74
|
if (!typedDecl)
|
|
80
75
|
throwParseError(stream, `Expected identifier after '${keyword}'`);
|
|
81
|
-
ctx.addElem(typedDecl);
|
|
82
76
|
|
|
77
|
+
let init: ExpressionElem | undefined;
|
|
83
78
|
if (requiresInit) {
|
|
84
79
|
expect(stream, "=", `${keyword} identifier`);
|
|
85
|
-
expectExpression(ctx);
|
|
80
|
+
init = expectExpression(ctx);
|
|
86
81
|
} else if (stream.matchText("=")) {
|
|
87
|
-
expectExpression(ctx);
|
|
82
|
+
init = expectExpression(ctx);
|
|
88
83
|
}
|
|
89
84
|
|
|
90
85
|
expect(stream, ";", `${keyword} declaration`);
|
|
91
86
|
|
|
92
|
-
const endPos = stream.checkpoint();
|
|
93
|
-
const contents = finishContents(ctx, startPos, endPos);
|
|
94
87
|
typedDecl.decl.ident.dependentScope = ctx.currentScope();
|
|
95
88
|
ctx.popScope();
|
|
96
89
|
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
90
|
+
// const/override share these fields; cast keyword to the union so the params
|
|
91
|
+
// type-check against the concrete elems rather than the opaque generic K.
|
|
92
|
+
const fields = { name: typedDecl, init };
|
|
93
|
+
const elem = finishStatement(
|
|
94
|
+
keyword as ValueDeclKind,
|
|
95
|
+
startPos,
|
|
96
|
+
ctx,
|
|
97
|
+
fields,
|
|
98
|
+
attributes,
|
|
99
|
+
) as ElemKindMap[K];
|
|
105
100
|
linkDeclIdent(typedDecl, elem);
|
|
106
|
-
return elem
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** @return true if ctx is at module level (not inside fn/block) */
|
|
110
|
-
function isModuleScope(ctx: ParsingContext): boolean {
|
|
111
|
-
let scope = ctx.currentScope();
|
|
112
|
-
while (scope.kind === "partial" && scope.parent) scope = scope.parent;
|
|
113
|
-
return scope.parent === null;
|
|
101
|
+
return elem;
|
|
114
102
|
}
|
|
115
103
|
|
|
116
104
|
/** Parse optional ': type' annotation, managing scope for type references. */
|
|
@@ -123,7 +111,6 @@ function parseOptionalType(ctx: ParsingContext): {
|
|
|
123
111
|
ctx.pushScope();
|
|
124
112
|
const typeRef = parseSimpleTypeRef(ctx);
|
|
125
113
|
if (!typeRef) throwParseError(ctx.stream, "Expected type after ':'");
|
|
126
|
-
ctx.addElem(typeRef);
|
|
127
114
|
const typeScope = ctx.currentScope();
|
|
128
115
|
ctx.popScope();
|
|
129
116
|
return { typeRef, typeScope };
|
package/src/parse/ParseWesl.ts
CHANGED
|
@@ -4,8 +4,8 @@ import type { WeslAST, WeslParseState } from "../ParseWESL.ts";
|
|
|
4
4
|
import { WeslParseError } from "../ParseWESL.ts";
|
|
5
5
|
import type { SrcModule } from "../Scope.ts";
|
|
6
6
|
import { emptyScope } from "../Scope.ts";
|
|
7
|
-
import {
|
|
8
|
-
import { parseModule } from "./ParseModule.ts";
|
|
7
|
+
import { attachComments } from "./AttachComments.ts";
|
|
8
|
+
import { checkDoBlockNames, parseModule } from "./ParseModule.ts";
|
|
9
9
|
import { type ParseOptions, ParsingContext } from "./ParsingContext.ts";
|
|
10
10
|
import { WeslStream } from "./WeslStream.ts";
|
|
11
11
|
|
|
@@ -16,10 +16,10 @@ export function parseWesl(
|
|
|
16
16
|
): WeslAST {
|
|
17
17
|
const { ctx, state } = createParseState(srcModule, options);
|
|
18
18
|
try {
|
|
19
|
-
beginElem(ctx, "module");
|
|
20
19
|
parseModule(ctx);
|
|
21
|
-
const moduleElem = state.stable
|
|
22
|
-
|
|
20
|
+
const { moduleElem } = state.stable;
|
|
21
|
+
attachComments(ctx, moduleElem);
|
|
22
|
+
checkDoBlockNames(moduleElem);
|
|
23
23
|
return state.stable;
|
|
24
24
|
} catch (e) {
|
|
25
25
|
if (e instanceof ParseError) {
|
|
@@ -35,23 +35,20 @@ export function parseWesl(
|
|
|
35
35
|
/** Initialize parse state: token stream, root scope, and module element. */
|
|
36
36
|
function createParseState(
|
|
37
37
|
srcModule: SrcModule,
|
|
38
|
-
|
|
39
|
-
): {
|
|
40
|
-
ctx: ParsingContext;
|
|
41
|
-
state: WeslParseState;
|
|
42
|
-
} {
|
|
38
|
+
parseOptions?: ParseOptions,
|
|
39
|
+
): { ctx: ParsingContext; state: WeslParseState } {
|
|
43
40
|
const stream = new WeslStream(srcModule.src);
|
|
44
41
|
const rootScope = emptyScope(null);
|
|
45
42
|
const moduleElem: ModuleElem = {
|
|
46
43
|
kind: "module",
|
|
47
|
-
|
|
44
|
+
decls: [],
|
|
48
45
|
start: 0,
|
|
49
46
|
end: srcModule.src.length,
|
|
50
47
|
};
|
|
51
48
|
const state: WeslParseState = {
|
|
52
|
-
context: { scope: rootScope
|
|
53
|
-
stable: { srcModule, moduleElem, rootScope, imports: [] },
|
|
49
|
+
context: { scope: rootScope },
|
|
50
|
+
stable: { srcModule, moduleElem, rootScope, imports: [], parseOptions },
|
|
54
51
|
};
|
|
55
|
-
const ctx = new ParsingContext(stream, state,
|
|
52
|
+
const ctx = new ParsingContext(stream, state, parseOptions);
|
|
56
53
|
return { ctx, state };
|
|
57
54
|
}
|
|
@@ -11,10 +11,15 @@ import {
|
|
|
11
11
|
} from "../Scope.ts";
|
|
12
12
|
import type { WeslStream } from "./WeslStream.ts";
|
|
13
13
|
|
|
14
|
+
/** Opt-in toggles for not-yet-spec'd WESL/WGSL features (for prototyping). */
|
|
15
|
+
export interface WeslExtensions {
|
|
16
|
+
/** Parse `do name(...) { ... }` blocks. */
|
|
17
|
+
doBlocks?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
export interface ParseOptions {
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
preserveExpressions?: boolean;
|
|
21
|
+
/** Enable parsing of experimental, not-yet-spec'd syntax extensions. */
|
|
22
|
+
weslExtensions?: WeslExtensions;
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
/** Context for parsers to build AST and manage scopes. */
|
|
@@ -45,12 +50,9 @@ export class ParsingContext {
|
|
|
45
50
|
return this.state.context.scope;
|
|
46
51
|
}
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const open = openElems[openElems.length - 1];
|
|
52
|
-
open.contents.push(elem);
|
|
53
|
-
}
|
|
53
|
+
/** Append a top-level declaration to the module, in source order. */
|
|
54
|
+
addModuleDecl(elem: AbstractElem): void {
|
|
55
|
+
this.state.stable.moduleElem.decls.push(elem);
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
pushScope(kind: Scope["kind"] = "scope"): void {
|