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.
- package/dist/index.d.ts +295 -214
- package/dist/index.js +2947 -1550
- package/package.json +6 -8
- package/src/AbstractElems.ts +81 -81
- package/src/Assertions.ts +5 -5
- package/src/BindIdents.ts +193 -319
- package/src/ClickableError.ts +3 -2
- package/src/Conditions.ts +2 -2
- package/src/LinkedWesl.ts +1 -1
- package/src/Linker.ts +4 -3
- package/src/LinkerUtil.ts +1 -1
- package/src/Logging.ts +165 -0
- package/src/LowerAndEmit.ts +278 -110
- package/src/ModulePathUtil.ts +59 -0
- package/src/ModuleResolver.ts +26 -62
- package/src/ParseError.ts +9 -0
- package/src/ParseWESL.ts +30 -94
- package/src/RawEmit.ts +1 -4
- package/src/Reflection.ts +1 -1
- package/src/Scope.ts +3 -0
- package/src/Span.ts +2 -0
- package/src/SrcMap.ts +208 -0
- package/src/Stream.ts +30 -0
- package/src/TransformBindingStructs.ts +2 -2
- package/src/Util.ts +1 -1
- package/src/debug/ASTtoString.ts +84 -135
- package/src/discovery/FindUnboundIdents.ts +14 -5
- package/src/index.ts +5 -0
- package/src/parse/ContentsHelpers.ts +70 -0
- package/src/parse/ExpressionUtil.ts +121 -0
- package/src/parse/Keywords.ts +12 -12
- package/src/parse/OperatorBinding.ts +146 -0
- package/src/parse/ParseAttribute.ts +272 -0
- package/src/parse/ParseCall.ts +77 -0
- package/src/parse/ParseControlFlow.ts +129 -0
- package/src/parse/ParseDirective.ts +105 -0
- package/src/parse/ParseExpression.ts +288 -0
- package/src/parse/ParseFn.ts +151 -0
- package/src/parse/ParseGlobalVar.ts +131 -0
- package/src/parse/ParseIdent.ts +77 -0
- package/src/parse/ParseImport.ts +160 -0
- package/src/parse/ParseLocalVar.ts +69 -0
- package/src/parse/ParseLoop.ts +112 -0
- package/src/parse/ParseModule.ts +116 -0
- package/src/parse/ParseSimpleStatement.ts +162 -0
- package/src/parse/ParseStatement.ts +215 -0
- package/src/parse/ParseStruct.ts +89 -0
- package/src/parse/ParseType.ts +71 -0
- package/src/parse/ParseUtil.ts +174 -0
- package/src/parse/ParseValueDeclaration.ts +130 -0
- package/src/parse/ParseWesl.ts +51 -0
- package/src/parse/ParsingContext.ts +93 -0
- package/src/parse/WeslStream.ts +63 -20
- package/src/parse/stream/CachingStream.ts +48 -0
- package/src/parse/stream/MatchersStream.ts +85 -0
- package/src/parse/stream/RegexHelpers.ts +38 -0
- package/src/test/BevyLink.test.ts +100 -0
- package/src/test/BindStdTypes.test.ts +110 -0
- package/src/test/{BindWESL.test.ts → BindWESLV2.test.ts} +21 -22
- package/src/test/BulkTests.test.ts +11 -12
- package/src/test/ConditionLinking.test.ts +107 -0
- package/src/test/ConditionalElif.test.ts +1 -13
- package/src/test/ConditionalTranslationCases.test.ts +5 -0
- package/src/test/ErrorLogging.test.ts +2 -2
- package/src/test/ImportCasesV2.test.ts +63 -0
- package/src/test/LinkFails.test.ts +69 -0
- package/src/test/LinkPackage.test.ts +1 -1
- package/src/test/Linker.test.ts +75 -2
- package/src/test/LogCatcher.ts +53 -0
- package/src/test/Mangling.test.ts +1 -1
- package/src/test/ParseComments.test.ts +1 -2
- package/src/test/{ParseConditions.test.ts → ParseConditionsV2.test.ts} +57 -49
- package/src/test/ParseErrorV2.test.ts +73 -0
- package/src/test/{ParseWESL.test.ts → ParseWeslV2.test.ts} +288 -370
- package/src/test/{ScopeWESL.test.ts → ScopeWESLV2.test.ts} +205 -176
- package/src/test/TestLink.ts +51 -51
- package/src/test/TestSetup.ts +9 -3
- package/src/test/TestUtil.ts +47 -77
- package/src/test/TrimmedMatch.ts +40 -0
- package/src/test/VirtualModules.test.ts +33 -2
- package/src/test/WeslDevice.test.ts +9 -2
- package/src/test/__snapshots__/ParseWeslV2.test.ts.snap +67 -0
- package/src/test-util.ts +7 -0
- package/src/WESLCollect.ts +0 -656
- package/src/parse/AttributeGrammar.ts +0 -232
- package/src/parse/ImportGrammar.ts +0 -195
- package/src/parse/WeslBaseGrammar.ts +0 -11
- package/src/parse/WeslExpression.ts +0 -231
- package/src/parse/WeslGrammar.ts +0 -739
- package/src/test/Expression.test.ts +0 -22
- package/src/test/ImportSyntaxCases.test.ts +0 -24
- package/src/test/ParseError.test.ts +0 -45
- package/src/test/Reflection.test.ts +0 -176
- package/src/test/TransformBindingStructs.test.ts +0 -238
- /package/src/test/{ParseElif.test.ts → ParseElifV2.test.ts} +0 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AttributeElem,
|
|
3
|
+
BlockStatement,
|
|
4
|
+
ContinuingElem,
|
|
5
|
+
ElifAttribute,
|
|
6
|
+
ElseAttribute,
|
|
7
|
+
IfAttribute,
|
|
8
|
+
StatementElem,
|
|
9
|
+
} from "../AbstractElems.ts";
|
|
10
|
+
import { findMap } from "../Util.ts";
|
|
11
|
+
import { beginElem, finishElem } from "./ContentsHelpers.ts";
|
|
12
|
+
import { parseAttributeList } from "./ParseAttribute.ts";
|
|
13
|
+
import { parseIfStatement, parseSwitchStatement } from "./ParseControlFlow.ts";
|
|
14
|
+
import { parseConstAssert } from "./ParseGlobalVar.ts";
|
|
15
|
+
import { parseLetDecl, parseLocalVarDecl } from "./ParseLocalVar.ts";
|
|
16
|
+
import {
|
|
17
|
+
parseContinuingStatement,
|
|
18
|
+
parseForStatement,
|
|
19
|
+
parseLoopStatement,
|
|
20
|
+
parseWhileStatement,
|
|
21
|
+
} from "./ParseLoop.ts";
|
|
22
|
+
import { parseSimpleStatement } from "./ParseSimpleStatement.ts";
|
|
23
|
+
import {
|
|
24
|
+
attachAttributes,
|
|
25
|
+
expect,
|
|
26
|
+
hasConditionalAttribute,
|
|
27
|
+
isConditionalAttribute,
|
|
28
|
+
throwParseError,
|
|
29
|
+
} from "./ParseUtil.ts";
|
|
30
|
+
import { parseConstDecl } from "./ParseValueDeclaration.ts";
|
|
31
|
+
import type { ParsingContext } from "./ParsingContext.ts";
|
|
32
|
+
|
|
33
|
+
type CondAttr = IfAttribute | ElifAttribute | ElseAttribute;
|
|
34
|
+
|
|
35
|
+
interface CompoundOptions {
|
|
36
|
+
loopBody?: boolean;
|
|
37
|
+
noScope?: boolean; // for function bodies (scope shared with params)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Experimental: declarations in conditional blocks visible in outer scope.
|
|
41
|
+
// e.g. @if(X) { let y = 1; } makes y visible outside the block.
|
|
42
|
+
// see https://github.com/wgsl-tooling-wg/wesl-spec/issues/158
|
|
43
|
+
const conditionalBlockFeature = true;
|
|
44
|
+
|
|
45
|
+
/** Function bodies share scope with parameters (per WGSL spec). */
|
|
46
|
+
export function parseFunctionBody(ctx: ParsingContext): StatementElem | null {
|
|
47
|
+
return parseCompoundStatement(ctx, undefined, { noScope: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Grammar: '{' statement* '}' (attributes parsed by caller)
|
|
52
|
+
* For loop bodies: '{' statement* continuing_statement? '}'
|
|
53
|
+
*/
|
|
54
|
+
export function parseCompoundStatement(
|
|
55
|
+
ctx: ParsingContext,
|
|
56
|
+
attributes?: AttributeElem[],
|
|
57
|
+
options?: CompoundOptions,
|
|
58
|
+
): StatementElem | null {
|
|
59
|
+
const brace = ctx.stream.matchText("{");
|
|
60
|
+
if (!brace) return null;
|
|
61
|
+
|
|
62
|
+
const startPos = getStartWithAttributes(attributes, brace.span[0]);
|
|
63
|
+
|
|
64
|
+
beginElem(ctx, "statement", attributes);
|
|
65
|
+
|
|
66
|
+
const skipScope =
|
|
67
|
+
options?.noScope ||
|
|
68
|
+
(conditionalBlockFeature && hasConditionalAttr(attributes));
|
|
69
|
+
if (!skipScope) ctx.pushScope();
|
|
70
|
+
parseBlockStatements(ctx, options?.loopBody);
|
|
71
|
+
if (!skipScope) ctx.popScope();
|
|
72
|
+
|
|
73
|
+
return finishBlockStatement(startPos, ctx, attributes);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Grammar: attribute* compound_statement (for control flow bodies) */
|
|
77
|
+
export function expectCompound(
|
|
78
|
+
ctx: ParsingContext,
|
|
79
|
+
errorMsg: string,
|
|
80
|
+
loopBody?: boolean,
|
|
81
|
+
): StatementElem {
|
|
82
|
+
const attrs = parseAttributeList(ctx);
|
|
83
|
+
const attrsOrUndef = attrs.length > 0 ? attrs : undefined;
|
|
84
|
+
const options = loopBody ? { loopBody } : undefined;
|
|
85
|
+
const block = parseCompoundStatement(ctx, attrsOrUndef, options);
|
|
86
|
+
if (!block) throwParseError(ctx.stream, errorMsg);
|
|
87
|
+
return block;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Get start position from first attribute, or keyword position. */
|
|
91
|
+
export function getStartWithAttributes(
|
|
92
|
+
attributes: AttributeElem[] | undefined,
|
|
93
|
+
keywordPos: number,
|
|
94
|
+
): number {
|
|
95
|
+
return attributes?.[0]?.start ?? keywordPos;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Match keyword and begin statement element. Returns start position or null. */
|
|
99
|
+
export function beginStatement(
|
|
100
|
+
ctx: ParsingContext,
|
|
101
|
+
keyword: string,
|
|
102
|
+
attributes?: AttributeElem[],
|
|
103
|
+
kind: "statement" | "continuing" = "statement",
|
|
104
|
+
): number | null {
|
|
105
|
+
const keywordPos = ctx.stream.checkpoint();
|
|
106
|
+
if (!ctx.stream.matchText(keyword)) return null;
|
|
107
|
+
const startPos = getStartWithAttributes(attributes, keywordPos);
|
|
108
|
+
beginElem(ctx, kind, attributes);
|
|
109
|
+
return startPos;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Finish block statement element: close contents, attach attributes. */
|
|
113
|
+
export function finishBlockStatement(
|
|
114
|
+
start: number,
|
|
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(
|
|
125
|
+
start: number,
|
|
126
|
+
ctx: ParsingContext,
|
|
127
|
+
attributes?: AttributeElem[],
|
|
128
|
+
kind: "statement" | "continuing" = "statement",
|
|
129
|
+
): BlockStatement {
|
|
130
|
+
const elem = finishElem(kind, start, ctx, {});
|
|
131
|
+
attachAttributes(elem, attributes);
|
|
132
|
+
return elem;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function hasConditionalAttr(attributes?: AttributeElem[]): boolean {
|
|
136
|
+
return !!attributes && hasConditionalAttribute(attributes);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Grammar: statement* '}' (after '{' consumed). Loop bodies may end with continuing. */
|
|
140
|
+
function parseBlockStatements(ctx: ParsingContext, loopBody?: boolean): void {
|
|
141
|
+
const { stream } = ctx;
|
|
142
|
+
while (true) {
|
|
143
|
+
if (stream.matchText("}")) break;
|
|
144
|
+
const stmt = parseStatement(ctx);
|
|
145
|
+
if (!stmt) throwParseError(stream, "Expected statement or '}'");
|
|
146
|
+
ctx.addElem(stmt);
|
|
147
|
+
if (loopBody && stmt.kind === "continuing") {
|
|
148
|
+
expect(stream, "}", "continuing block");
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Grammar: statement :
|
|
156
|
+
* ';' | return_statement ';' | if_statement | switch_statement | loop_statement
|
|
157
|
+
* | for_statement | while_statement | func_call_statement ';'
|
|
158
|
+
* | variable_or_value_statement ';' | break_statement ';' | continue_statement ';'
|
|
159
|
+
* | 'discard' ';' | variable_updating_statement ';' | compound_statement
|
|
160
|
+
* | const_assert_statement ';'
|
|
161
|
+
*/
|
|
162
|
+
function parseStatement(ctx: ParsingContext): BlockStatement | null {
|
|
163
|
+
const { stream } = ctx;
|
|
164
|
+
const startPos = stream.checkpoint();
|
|
165
|
+
const attributes = parseAttributeList(ctx);
|
|
166
|
+
|
|
167
|
+
const token = stream.peek();
|
|
168
|
+
if (!token || token.text === "}") {
|
|
169
|
+
stream.reset(startPos);
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const hasConditional =
|
|
174
|
+
attributes.length > 0 && hasConditionalAttribute(attributes);
|
|
175
|
+
if (hasConditional) ctx.pushScope("partial");
|
|
176
|
+
|
|
177
|
+
const attrsOrUndef = attributes.length > 0 ? attributes : undefined;
|
|
178
|
+
const parsers = [
|
|
179
|
+
parseLocalVarDecl,
|
|
180
|
+
parseLetDecl,
|
|
181
|
+
parseConstDecl,
|
|
182
|
+
parseConstAssert,
|
|
183
|
+
parseCompoundStatement,
|
|
184
|
+
parseIfStatement,
|
|
185
|
+
parseSwitchStatement,
|
|
186
|
+
parseForStatement,
|
|
187
|
+
parseWhileStatement,
|
|
188
|
+
parseLoopStatement,
|
|
189
|
+
parseContinuingStatement,
|
|
190
|
+
parseSimpleStatement,
|
|
191
|
+
];
|
|
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
|
+
}
|
|
198
|
+
|
|
199
|
+
function finalizeConditional(
|
|
200
|
+
ctx: ParsingContext,
|
|
201
|
+
hasConditional: boolean,
|
|
202
|
+
attributes: AttributeElem[],
|
|
203
|
+
): void {
|
|
204
|
+
if (hasConditional) {
|
|
205
|
+
const partialScope = ctx.popScope();
|
|
206
|
+
partialScope.condAttribute = getConditionalAttribute(attributes);
|
|
207
|
+
}
|
|
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;
|
|
215
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AttributeElem,
|
|
3
|
+
StructElem,
|
|
4
|
+
StructMemberElem,
|
|
5
|
+
} from "../AbstractElems.ts";
|
|
6
|
+
import { beginElem, finishElem } from "./ContentsHelpers.ts";
|
|
7
|
+
import { parseAttributeList } from "./ParseAttribute.ts";
|
|
8
|
+
import { getStartWithAttributes } from "./ParseStatement.ts";
|
|
9
|
+
import { parseSimpleTypeRef } from "./ParseType.ts";
|
|
10
|
+
import {
|
|
11
|
+
attachAttributes,
|
|
12
|
+
createDeclIdentElem,
|
|
13
|
+
expect,
|
|
14
|
+
expectWord,
|
|
15
|
+
linkDeclIdentElem,
|
|
16
|
+
makeNameElem,
|
|
17
|
+
parseCommaList,
|
|
18
|
+
throwParseError,
|
|
19
|
+
} from "./ParseUtil.ts";
|
|
20
|
+
import type { ParsingContext } from "./ParsingContext.ts";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Grammar: struct_decl : 'struct' ident struct_body_decl
|
|
24
|
+
* Grammar: struct_body_decl : '{' struct_member ( ',' struct_member )* ','? '}'
|
|
25
|
+
*/
|
|
26
|
+
export function parseStructDecl(
|
|
27
|
+
ctx: ParsingContext,
|
|
28
|
+
attributes?: AttributeElem[],
|
|
29
|
+
): StructElem | null {
|
|
30
|
+
const { stream } = ctx;
|
|
31
|
+
const structToken = stream.matchText("struct");
|
|
32
|
+
if (!structToken) return null;
|
|
33
|
+
|
|
34
|
+
const start = getStartWithAttributes(attributes, structToken.span[0]);
|
|
35
|
+
const nameToken = expectWord(stream, "Expected identifier after 'struct'");
|
|
36
|
+
|
|
37
|
+
const identElem = createDeclIdentElem(ctx, nameToken, true);
|
|
38
|
+
ctx.saveIdent(identElem.ident);
|
|
39
|
+
|
|
40
|
+
beginElem(ctx, "struct", attributes);
|
|
41
|
+
ctx.addElem(identElem);
|
|
42
|
+
expect(stream, "{", "struct name");
|
|
43
|
+
|
|
44
|
+
ctx.pushScope();
|
|
45
|
+
const members = parseStructMembers(ctx);
|
|
46
|
+
identElem.ident.dependentScope = ctx.currentScope();
|
|
47
|
+
ctx.popScope();
|
|
48
|
+
|
|
49
|
+
expect(stream, "}", "struct member");
|
|
50
|
+
|
|
51
|
+
const elem = finishElem("struct", start, ctx, { name: identElem, members });
|
|
52
|
+
attachAttributes(elem, attributes);
|
|
53
|
+
linkDeclIdentElem(identElem, elem);
|
|
54
|
+
return elem;
|
|
55
|
+
}
|
|
56
|
+
|
|
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
|
+
/** Grammar: struct_member : attribute* member_ident ':' type_specifier */
|
|
65
|
+
function parseStructMember(ctx: ParsingContext): StructMemberElem | null {
|
|
66
|
+
const { stream } = ctx;
|
|
67
|
+
const checkpoint = stream.checkpoint();
|
|
68
|
+
const attributes = parseAttributeList(ctx);
|
|
69
|
+
|
|
70
|
+
const nameToken = stream.matchKind("word");
|
|
71
|
+
if (!nameToken) {
|
|
72
|
+
stream.reset(checkpoint);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const start = getStartWithAttributes(attributes, nameToken.span[0]);
|
|
77
|
+
beginElem(ctx, "member", attributes.length ? attributes : undefined);
|
|
78
|
+
const name = makeNameElem(nameToken);
|
|
79
|
+
ctx.addElem(name);
|
|
80
|
+
expect(stream, ":", "struct member name");
|
|
81
|
+
|
|
82
|
+
const typeRef = parseSimpleTypeRef(ctx);
|
|
83
|
+
if (!typeRef) throwParseError(stream, "Expected type after ':'");
|
|
84
|
+
ctx.addElem(typeRef);
|
|
85
|
+
|
|
86
|
+
const elem = finishElem("member", start, ctx, { name, typeRef });
|
|
87
|
+
attachAttributes(elem, attributes.length ? attributes : undefined);
|
|
88
|
+
return elem;
|
|
89
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { TypeRefElem, TypeTemplateParameter } from "../AbstractElems.ts";
|
|
2
|
+
import { beginElem, finishElem } from "./ContentsHelpers.ts";
|
|
3
|
+
import { parseExpression } from "./ParseExpression.ts";
|
|
4
|
+
import { parseModulePath } from "./ParseIdent.ts";
|
|
5
|
+
import { makeRefIdentElem, throwParseError } from "./ParseUtil.ts";
|
|
6
|
+
import type { ParsingContext } from "./ParsingContext.ts";
|
|
7
|
+
import type { WeslStream } from "./WeslStream.ts";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Grammar: type_specifier : template_elaborated_ident
|
|
11
|
+
* Grammar: template_elaborated_ident : ident template_list?
|
|
12
|
+
* WESL extension: qualified names with :: (e.g., pkg::Type)
|
|
13
|
+
*/
|
|
14
|
+
export function parseSimpleTypeRef(ctx: ParsingContext): TypeRefElem | null {
|
|
15
|
+
const path = parseModulePath(ctx.stream);
|
|
16
|
+
if (!path) return null;
|
|
17
|
+
|
|
18
|
+
const { parts, start, end: nameEnd } = path;
|
|
19
|
+
const refIdent = ctx.createRefIdent(parts.join("::"));
|
|
20
|
+
|
|
21
|
+
beginElem(ctx, "type");
|
|
22
|
+
|
|
23
|
+
const refIdentElem = makeRefIdentElem(ctx, refIdent, start, nameEnd);
|
|
24
|
+
ctx.saveIdent(refIdent);
|
|
25
|
+
ctx.addElem(refIdentElem);
|
|
26
|
+
|
|
27
|
+
const templateParams = ctx.stream.nextTemplateStartToken()
|
|
28
|
+
? parseTemplateParams(ctx)
|
|
29
|
+
: undefined;
|
|
30
|
+
|
|
31
|
+
return finishElem("type", start, ctx, { name: refIdent, templateParams });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Parse comma-separated template parameters until closing '>'. */
|
|
35
|
+
export function parseTemplateParams(
|
|
36
|
+
ctx: ParsingContext,
|
|
37
|
+
): TypeTemplateParameter[] {
|
|
38
|
+
const { stream } = ctx;
|
|
39
|
+
|
|
40
|
+
// Handle empty template <>
|
|
41
|
+
if (consumeTemplateEnd(stream)) return [];
|
|
42
|
+
|
|
43
|
+
// Parse comma-separated params
|
|
44
|
+
const params: TypeTemplateParameter[] = [parseTemplateParam(ctx)];
|
|
45
|
+
while (stream.matchText(",")) {
|
|
46
|
+
params.push(parseTemplateParam(ctx));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Must end with >
|
|
50
|
+
if (!consumeTemplateEnd(stream))
|
|
51
|
+
throwParseError(stream, "Expected '>' or ',' after template parameter");
|
|
52
|
+
|
|
53
|
+
return params;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Consume template end token (>) if present, returning success. */
|
|
57
|
+
function consumeTemplateEnd(stream: WeslStream): boolean {
|
|
58
|
+
if (!stream.peek()?.text.startsWith(">")) return false;
|
|
59
|
+
if (!stream.nextTemplateEndToken())
|
|
60
|
+
throwParseError(stream, "Expected '>' to close template parameters");
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Grammar: template_arg_expression : expression */
|
|
65
|
+
function parseTemplateParam(ctx: ParsingContext): TypeTemplateParameter {
|
|
66
|
+
// parseExpression handles template_elaborated_ident via parsePrimaryExpr
|
|
67
|
+
// inTemplate prevents '>' from being parsed as comparison operator
|
|
68
|
+
const expr = parseExpression(ctx, { inTemplate: true });
|
|
69
|
+
if (expr) return expr;
|
|
70
|
+
throwParseError(ctx.stream, "Expected expression in template parameters");
|
|
71
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Attribute,
|
|
3
|
+
AttributeElem,
|
|
4
|
+
DeclarationElem,
|
|
5
|
+
DeclIdentElem,
|
|
6
|
+
ExpressionElem,
|
|
7
|
+
NameElem,
|
|
8
|
+
RefIdentElem,
|
|
9
|
+
TypedDeclElem,
|
|
10
|
+
} from "../AbstractElems.ts";
|
|
11
|
+
import { ParseError } from "../ParseError.ts";
|
|
12
|
+
import type { RefIdent } from "../Scope.ts";
|
|
13
|
+
import type { Stream, Token } from "../Stream.ts";
|
|
14
|
+
import { parseExpression } from "./ParseExpression.ts";
|
|
15
|
+
import type { ParsingContext } from "./ParsingContext.ts";
|
|
16
|
+
import type { WeslStream, WeslToken } from "./WeslStream.ts";
|
|
17
|
+
|
|
18
|
+
// --- Stream/token expectations ---
|
|
19
|
+
|
|
20
|
+
/** Match text and throw ParseError if not found. */
|
|
21
|
+
export function expect(
|
|
22
|
+
stream: WeslStream,
|
|
23
|
+
text: string,
|
|
24
|
+
context?: string,
|
|
25
|
+
): ReturnType<WeslStream["matchText"]> & {} {
|
|
26
|
+
const token = stream.matchText(text);
|
|
27
|
+
if (!token) {
|
|
28
|
+
const pos = stream.checkpoint();
|
|
29
|
+
const msg = context
|
|
30
|
+
? `Expected '${text}' after ${context}`
|
|
31
|
+
: `Expected '${text}'`;
|
|
32
|
+
throw new ParseError(msg, [pos, pos]);
|
|
33
|
+
}
|
|
34
|
+
return token;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Match word token and throw ParseError if not found. */
|
|
38
|
+
export function expectWord(
|
|
39
|
+
stream: WeslStream,
|
|
40
|
+
errorMsg: string,
|
|
41
|
+
): WeslToken<"word"> {
|
|
42
|
+
const token = stream.peek();
|
|
43
|
+
if (!token || token.kind !== "word") {
|
|
44
|
+
throwParseError(stream, errorMsg);
|
|
45
|
+
}
|
|
46
|
+
stream.nextToken();
|
|
47
|
+
return token as WeslToken<"word">;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Parse expression and throw ParseError if not found. */
|
|
51
|
+
export function expectExpression(
|
|
52
|
+
ctx: ParsingContext,
|
|
53
|
+
errorMsg = "Expected expression",
|
|
54
|
+
): ExpressionElem {
|
|
55
|
+
const expr = parseExpression(ctx);
|
|
56
|
+
if (!expr) throwParseError(ctx.stream, errorMsg);
|
|
57
|
+
return expr;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Throw a ParseError at the current/next token position. */
|
|
61
|
+
export function throwParseError(stream: Stream<Token>, message: string): never {
|
|
62
|
+
const weslStream = stream as WeslStream;
|
|
63
|
+
const token = weslStream.peek();
|
|
64
|
+
const span = token
|
|
65
|
+
? token.span
|
|
66
|
+
: ([weslStream.checkpoint(), weslStream.checkpoint()] as const);
|
|
67
|
+
throw new ParseError(message, span);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// --- List parsing ---
|
|
71
|
+
|
|
72
|
+
/** Parse comma-separated items. Caller handles delimiters. */
|
|
73
|
+
export function parseCommaList<T>(
|
|
74
|
+
ctx: ParsingContext,
|
|
75
|
+
parseItem: (ctx: ParsingContext) => T | null,
|
|
76
|
+
): T[] {
|
|
77
|
+
const items: T[] = [];
|
|
78
|
+
while (true) {
|
|
79
|
+
const item = parseItem(ctx);
|
|
80
|
+
if (item === null) break;
|
|
81
|
+
items.push(item);
|
|
82
|
+
if (!ctx.stream.matchText(",")) break;
|
|
83
|
+
}
|
|
84
|
+
return items;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Yield parsed elements until parser returns null. */
|
|
88
|
+
export function* parseMany<T>(
|
|
89
|
+
ctx: ParsingContext,
|
|
90
|
+
parse: (ctx: ParsingContext) => T | null,
|
|
91
|
+
): Generator<T> {
|
|
92
|
+
for (let elem = parse(ctx); elem; elem = parse(ctx)) yield elem;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// --- Element creation ---
|
|
96
|
+
|
|
97
|
+
/** Create a NameElem from a word token. */
|
|
98
|
+
export function makeNameElem(token: WeslToken<"word">): NameElem {
|
|
99
|
+
const [start, end] = token.span;
|
|
100
|
+
return { kind: "name", name: token.text, start, end };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Create a DeclIdentElem from a name token. */
|
|
104
|
+
export function createDeclIdentElem(
|
|
105
|
+
ctx: ParsingContext,
|
|
106
|
+
nameToken: WeslToken<"word">,
|
|
107
|
+
isGlobal: boolean,
|
|
108
|
+
): DeclIdentElem {
|
|
109
|
+
const declIdent = ctx.createDeclIdent(nameToken.text, isGlobal);
|
|
110
|
+
const [start, end] = nameToken.span;
|
|
111
|
+
return {
|
|
112
|
+
kind: "decl",
|
|
113
|
+
ident: declIdent,
|
|
114
|
+
srcModule: ctx.srcModule,
|
|
115
|
+
start,
|
|
116
|
+
end,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Create a RefIdentElem and link it to its RefIdent. */
|
|
121
|
+
export function makeRefIdentElem(
|
|
122
|
+
ctx: ParsingContext,
|
|
123
|
+
refIdent: RefIdent,
|
|
124
|
+
start: number,
|
|
125
|
+
end: number,
|
|
126
|
+
): RefIdentElem {
|
|
127
|
+
const elem: RefIdentElem = {
|
|
128
|
+
kind: "ref",
|
|
129
|
+
ident: refIdent,
|
|
130
|
+
srcModule: ctx.srcModule,
|
|
131
|
+
start,
|
|
132
|
+
end,
|
|
133
|
+
};
|
|
134
|
+
refIdent.refIdentElem = elem;
|
|
135
|
+
return elem;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// --- Attribute utilities ---
|
|
139
|
+
|
|
140
|
+
export function isConditionalAttribute(attr: Attribute): boolean {
|
|
141
|
+
const { kind } = attr;
|
|
142
|
+
return kind === "@if" || kind === "@elif" || kind === "@else";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** @return true if any attribute is a conditional (@if, @elif, @else) */
|
|
146
|
+
export function hasConditionalAttribute(attributes: AttributeElem[]): boolean {
|
|
147
|
+
return attributes.some(attr => isConditionalAttribute(attr.attribute));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Attach non-empty attributes array to element. */
|
|
151
|
+
export function attachAttributes<T extends { attributes?: AttributeElem[] }>(
|
|
152
|
+
elem: T,
|
|
153
|
+
attributes?: AttributeElem[],
|
|
154
|
+
): void {
|
|
155
|
+
if (attributes?.length) elem.attributes = attributes;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// --- Declaration linking ---
|
|
159
|
+
|
|
160
|
+
/** Link a DeclIdentElem's ident to its parent declaration. */
|
|
161
|
+
export function linkDeclIdentElem(
|
|
162
|
+
declIdentElem: DeclIdentElem,
|
|
163
|
+
declElem: DeclarationElem,
|
|
164
|
+
): void {
|
|
165
|
+
declIdentElem.ident.declElem = declElem;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Link a TypedDeclElem's ident to its parent declaration. */
|
|
169
|
+
export function linkDeclIdent(
|
|
170
|
+
typedDecl: TypedDeclElem,
|
|
171
|
+
declElem: DeclarationElem,
|
|
172
|
+
): void {
|
|
173
|
+
linkDeclIdentElem(typedDecl.decl, declElem);
|
|
174
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AttributeElem,
|
|
3
|
+
ConstElem,
|
|
4
|
+
ElemKindMap,
|
|
5
|
+
OverrideElem,
|
|
6
|
+
TypedDeclElem,
|
|
7
|
+
TypeRefElem,
|
|
8
|
+
} from "../AbstractElems.ts";
|
|
9
|
+
import type { Scope } from "../Scope.ts";
|
|
10
|
+
import { beginElem, finishContents } from "./ContentsHelpers.ts";
|
|
11
|
+
import { getStartWithAttributes } from "./ParseStatement.ts";
|
|
12
|
+
import { parseSimpleTypeRef } from "./ParseType.ts";
|
|
13
|
+
import {
|
|
14
|
+
attachAttributes,
|
|
15
|
+
createDeclIdentElem,
|
|
16
|
+
expect,
|
|
17
|
+
expectExpression,
|
|
18
|
+
linkDeclIdent,
|
|
19
|
+
throwParseError,
|
|
20
|
+
} from "./ParseUtil.ts";
|
|
21
|
+
import type { ParsingContext } from "./ParsingContext.ts";
|
|
22
|
+
|
|
23
|
+
type ValueDeclKind = "const" | "override";
|
|
24
|
+
|
|
25
|
+
/** Grammar: 'const' optionally_typed_ident '=' expression (global or local) */
|
|
26
|
+
export function parseConstDecl(
|
|
27
|
+
ctx: ParsingContext,
|
|
28
|
+
attributes?: AttributeElem[],
|
|
29
|
+
): ConstElem | null {
|
|
30
|
+
return parseValueDecl(ctx, "const", true, isModuleScope(ctx), attributes);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Grammar: 'override' optionally_typed_ident ( '=' expression )? */
|
|
34
|
+
export function parseOverrideDecl(
|
|
35
|
+
ctx: ParsingContext,
|
|
36
|
+
attributes?: AttributeElem[],
|
|
37
|
+
): OverrideElem | null {
|
|
38
|
+
return parseValueDecl(ctx, "override", false, true, attributes);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Grammar: optionally_typed_ident : ident ( ':' type_specifier )? */
|
|
42
|
+
export function parseTypedDecl(
|
|
43
|
+
ctx: ParsingContext,
|
|
44
|
+
isGlobal = true,
|
|
45
|
+
): TypedDeclElem | null {
|
|
46
|
+
const nameToken = ctx.stream.matchKind("word");
|
|
47
|
+
if (!nameToken) return null;
|
|
48
|
+
const start = nameToken.span[0];
|
|
49
|
+
|
|
50
|
+
beginElem(ctx, "typeDecl");
|
|
51
|
+
const decl = createDeclIdentElem(ctx, nameToken, isGlobal);
|
|
52
|
+
ctx.addElem(decl);
|
|
53
|
+
ctx.saveIdent(decl.ident);
|
|
54
|
+
|
|
55
|
+
const { typeRef, typeScope } = parseOptionalType(ctx);
|
|
56
|
+
|
|
57
|
+
const end = ctx.stream.checkpoint();
|
|
58
|
+
const contents = finishContents(ctx, start, end);
|
|
59
|
+
return { kind: "typeDecl", decl, typeRef, typeScope, start, end, contents };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Shared parser for const/override declarations. */
|
|
63
|
+
function parseValueDecl<K extends ValueDeclKind>(
|
|
64
|
+
ctx: ParsingContext,
|
|
65
|
+
keyword: K,
|
|
66
|
+
requiresInit: boolean,
|
|
67
|
+
isGlobal: boolean,
|
|
68
|
+
attributes?: AttributeElem[],
|
|
69
|
+
): ElemKindMap[K] | null {
|
|
70
|
+
const { stream } = ctx;
|
|
71
|
+
const token = stream.matchText(keyword);
|
|
72
|
+
if (!token) return null;
|
|
73
|
+
|
|
74
|
+
const startPos = getStartWithAttributes(attributes, token.span[0]);
|
|
75
|
+
ctx.pushScope("partial");
|
|
76
|
+
beginElem(ctx, keyword, attributes);
|
|
77
|
+
|
|
78
|
+
const typedDecl = parseTypedDecl(ctx, isGlobal);
|
|
79
|
+
if (!typedDecl)
|
|
80
|
+
throwParseError(stream, `Expected identifier after '${keyword}'`);
|
|
81
|
+
ctx.addElem(typedDecl);
|
|
82
|
+
|
|
83
|
+
if (requiresInit) {
|
|
84
|
+
expect(stream, "=", `${keyword} identifier`);
|
|
85
|
+
expectExpression(ctx);
|
|
86
|
+
} else if (stream.matchText("=")) {
|
|
87
|
+
expectExpression(ctx);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
expect(stream, ";", `${keyword} declaration`);
|
|
91
|
+
|
|
92
|
+
const endPos = stream.checkpoint();
|
|
93
|
+
const contents = finishContents(ctx, startPos, endPos);
|
|
94
|
+
typedDecl.decl.ident.dependentScope = ctx.currentScope();
|
|
95
|
+
ctx.popScope();
|
|
96
|
+
|
|
97
|
+
const elem: ConstElem | OverrideElem = {
|
|
98
|
+
kind: keyword,
|
|
99
|
+
name: typedDecl,
|
|
100
|
+
start: startPos,
|
|
101
|
+
end: endPos,
|
|
102
|
+
contents,
|
|
103
|
+
};
|
|
104
|
+
attachAttributes(elem, attributes);
|
|
105
|
+
linkDeclIdent(typedDecl, elem);
|
|
106
|
+
return elem as ElemKindMap[K];
|
|
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;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Parse optional ': type' annotation, managing scope for type references. */
|
|
117
|
+
function parseOptionalType(ctx: ParsingContext): {
|
|
118
|
+
typeRef?: TypeRefElem;
|
|
119
|
+
typeScope?: Scope;
|
|
120
|
+
} {
|
|
121
|
+
if (!ctx.stream.matchText(":")) return {};
|
|
122
|
+
|
|
123
|
+
ctx.pushScope();
|
|
124
|
+
const typeRef = parseSimpleTypeRef(ctx);
|
|
125
|
+
if (!typeRef) throwParseError(ctx.stream, "Expected type after ':'");
|
|
126
|
+
ctx.addElem(typeRef);
|
|
127
|
+
const typeScope = ctx.currentScope();
|
|
128
|
+
ctx.popScope();
|
|
129
|
+
return { typeRef, typeScope };
|
|
130
|
+
}
|