wesl 0.7.27 → 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/dist/index.d.ts +272 -126
- package/dist/index.js +1742 -1102
- package/package.json +1 -1
- package/src/AbstractElems.ts +263 -81
- package/src/Linker.ts +14 -2
- package/src/LinkerUtil.ts +141 -7
- package/src/LowerAndEmit.ts +660 -304
- package/src/ModuleResolver.ts +15 -4
- package/src/ParseWESL.ts +21 -33
- package/src/SrcMap.ts +7 -19
- 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/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 +39 -66
- 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 +17 -10
- 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,21 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
AttributeElem,
|
|
3
|
+
BlockElem,
|
|
4
|
+
ExpressionElem,
|
|
5
|
+
IfElem,
|
|
6
|
+
SwitchClauseElem,
|
|
7
|
+
SwitchElem,
|
|
8
|
+
} from "../AbstractElems.ts";
|
|
3
9
|
import { parseAttributeList } from "./ParseAttribute.ts";
|
|
4
10
|
import {
|
|
5
11
|
beginStatement,
|
|
6
12
|
expectCompound,
|
|
7
|
-
|
|
13
|
+
finishStatement,
|
|
14
|
+
getStartWithAttributes,
|
|
8
15
|
parseCompoundStatement,
|
|
9
16
|
} from "./ParseStatement.ts";
|
|
10
17
|
import {
|
|
11
|
-
|
|
18
|
+
attrsOrUndef,
|
|
12
19
|
expect,
|
|
13
20
|
expectExpression,
|
|
14
21
|
throwParseError,
|
|
@@ -22,52 +29,67 @@ import type { ParsingContext } from "./ParsingContext.ts";
|
|
|
22
29
|
export function parseIfStatement(
|
|
23
30
|
ctx: ParsingContext,
|
|
24
31
|
attributes?: AttributeElem[],
|
|
25
|
-
):
|
|
32
|
+
): IfElem | null {
|
|
26
33
|
const startPos = beginStatement(ctx, "if", attributes);
|
|
27
34
|
if (startPos === null) return null;
|
|
28
35
|
|
|
29
|
-
expectExpression(ctx, "Expected condition
|
|
30
|
-
|
|
36
|
+
const condition = expectExpression(ctx, "Expected condition after 'if'");
|
|
31
37
|
const body = expectCompound(ctx, "Expected '{' after if condition");
|
|
32
|
-
ctx
|
|
33
|
-
parseElseChain(ctx);
|
|
38
|
+
const elseBranch = parseElseChain(ctx);
|
|
34
39
|
|
|
35
|
-
|
|
40
|
+
const params = { condition, body, else: elseBranch };
|
|
41
|
+
return finishStatement("if", startPos, ctx, params, attributes);
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
/** Grammar: switch_statement : attribute* 'switch' expression switch_body */
|
|
39
45
|
export function parseSwitchStatement(
|
|
40
46
|
ctx: ParsingContext,
|
|
41
47
|
attributes?: AttributeElem[],
|
|
42
|
-
):
|
|
48
|
+
): SwitchElem | null {
|
|
43
49
|
const startPos = beginStatement(ctx, "switch", attributes);
|
|
44
50
|
if (startPos === null) return null;
|
|
45
51
|
|
|
46
|
-
expectExpression(ctx, "Expected expression after 'switch'");
|
|
47
|
-
expectSwitchClauses(ctx);
|
|
52
|
+
const selector = expectExpression(ctx, "Expected expression after 'switch'");
|
|
53
|
+
const { bodyAttributes, clauses } = expectSwitchClauses(ctx);
|
|
48
54
|
|
|
49
|
-
|
|
55
|
+
const params = { selector, clauses, bodyAttributes };
|
|
56
|
+
return finishStatement("switch", startPos, ctx, params, attributes);
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
/**
|
|
53
60
|
* Grammar: else_if_clause : 'else' 'if' expression compound_statement
|
|
54
61
|
* Grammar: else_clause : 'else' compound_statement
|
|
62
|
+
*
|
|
63
|
+
* An else-if nests as an IfElem in the outer if's `else` field; a plain else is
|
|
64
|
+
* a BlockElem. Emit and the AST dump read these typed fields.
|
|
55
65
|
*/
|
|
56
|
-
function parseElseChain(ctx: ParsingContext):
|
|
66
|
+
function parseElseChain(ctx: ParsingContext): IfElem | BlockElem | undefined {
|
|
57
67
|
const { stream } = ctx;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
ctx
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
const elseToken = stream.matchText("else");
|
|
69
|
+
if (!elseToken) return undefined;
|
|
70
|
+
|
|
71
|
+
if (stream.matchText("if")) {
|
|
72
|
+
const condition = expectExpression(
|
|
73
|
+
ctx,
|
|
74
|
+
"Expected expression after 'else if'",
|
|
75
|
+
);
|
|
76
|
+
const body = expectCompound(ctx, "Expected '{' after else if");
|
|
77
|
+
const elseBranch = parseElseChain(ctx);
|
|
78
|
+
const end = stream.checkpoint();
|
|
79
|
+
// Start at the 'else' keyword, not the pre-keyword position, so a comment
|
|
80
|
+
// before 'else if' falls in the gap and leads the branch (matches
|
|
81
|
+
// beginStatement); otherwise the nested if swallows it.
|
|
82
|
+
return {
|
|
83
|
+
kind: "if",
|
|
84
|
+
condition,
|
|
85
|
+
body,
|
|
86
|
+
else: elseBranch,
|
|
87
|
+
start: elseToken.span[0],
|
|
88
|
+
end,
|
|
89
|
+
};
|
|
70
90
|
}
|
|
91
|
+
|
|
92
|
+
return expectCompound(ctx, "Expected '{' after else");
|
|
71
93
|
}
|
|
72
94
|
|
|
73
95
|
/**
|
|
@@ -76,54 +98,76 @@ function parseElseChain(ctx: ParsingContext): void {
|
|
|
76
98
|
* Grammar: case_clause : 'case' case_selectors ':'? compound_statement
|
|
77
99
|
* Grammar: default_alone_clause : 'default' ':'? compound_statement
|
|
78
100
|
*/
|
|
79
|
-
function expectSwitchClauses(ctx: ParsingContext):
|
|
101
|
+
function expectSwitchClauses(ctx: ParsingContext): {
|
|
102
|
+
bodyAttributes?: AttributeElem[];
|
|
103
|
+
clauses: SwitchClauseElem[];
|
|
104
|
+
} {
|
|
80
105
|
const { stream } = ctx;
|
|
81
|
-
parseAttributeList(ctx);
|
|
106
|
+
const bodyAttrs = parseAttributeList(ctx);
|
|
82
107
|
expect(stream, "{", "switch expression");
|
|
108
|
+
const clauses: SwitchClauseElem[] = [];
|
|
83
109
|
while (!stream.matchText("}")) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
110
|
+
clauses.push(parseSwitchClause(ctx));
|
|
111
|
+
}
|
|
112
|
+
return { bodyAttributes: attrsOrUndef(bodyAttrs), clauses };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Parse one 'case'/'default' clause (the keyword has not yet been consumed). */
|
|
116
|
+
function parseSwitchClause(ctx: ParsingContext): SwitchClauseElem {
|
|
117
|
+
const { stream } = ctx;
|
|
118
|
+
const attrs = attrsOrUndef(parseAttributeList(ctx));
|
|
91
119
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
parseCaseBody(ctx, "Expected '{' after 'default'");
|
|
97
|
-
} else {
|
|
98
|
-
throwParseError(stream, "Expected 'case', 'default', or '}' in switch");
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const clauseElem = finishElem("switch-clause", clauseStart, ctx, {});
|
|
102
|
-
attachAttributes(clauseElem, clauseAttrs.length ? clauseAttrs : undefined);
|
|
103
|
-
ctx.addElem(clauseElem);
|
|
120
|
+
const caseTok = stream.matchText("case");
|
|
121
|
+
const keyword = caseTok ?? stream.matchText("default");
|
|
122
|
+
if (!keyword) {
|
|
123
|
+
throwParseError(stream, "Expected 'case', 'default', or '}' in switch");
|
|
104
124
|
}
|
|
125
|
+
// The clause start is the keyword token, not any leading comment, so a
|
|
126
|
+
// preceding comment falls in the gap before it and attaches as leading.
|
|
127
|
+
const clauseStart = getStartWithAttributes(attrs, keyword.span[0]);
|
|
128
|
+
|
|
129
|
+
let selectors: (ExpressionElem | "default")[];
|
|
130
|
+
let body: BlockElem;
|
|
131
|
+
if (caseTok) {
|
|
132
|
+
selectors = parseCaseSelectors(ctx);
|
|
133
|
+
body = parseCaseBody(ctx, "Expected '{' after case value");
|
|
134
|
+
} else {
|
|
135
|
+
selectors = ["default"];
|
|
136
|
+
body = parseCaseBody(ctx, "Expected '{' after 'default'");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return finishStatement(
|
|
140
|
+
"switch-clause",
|
|
141
|
+
clauseStart,
|
|
142
|
+
ctx,
|
|
143
|
+
{ selectors, body },
|
|
144
|
+
attrs,
|
|
145
|
+
);
|
|
105
146
|
}
|
|
106
147
|
|
|
107
148
|
/** Grammar: case_selectors : case_selector (',' case_selector)* ','? */
|
|
108
|
-
function parseCaseSelectors(
|
|
149
|
+
function parseCaseSelectors(
|
|
150
|
+
ctx: ParsingContext,
|
|
151
|
+
): (ExpressionElem | "default")[] {
|
|
109
152
|
const { stream } = ctx;
|
|
110
|
-
expectExpression(ctx, "Expected expression after 'case'");
|
|
153
|
+
const selectors = [expectExpression(ctx, "Expected expression after 'case'")];
|
|
111
154
|
while (stream.matchText(",")) {
|
|
112
|
-
|
|
155
|
+
selectors.push(
|
|
156
|
+
expectExpression(ctx, "Expected expression after ',' in case values"),
|
|
157
|
+
);
|
|
113
158
|
}
|
|
159
|
+
return selectors;
|
|
114
160
|
}
|
|
115
161
|
|
|
116
162
|
/**
|
|
117
163
|
* Grammar: case_clause : 'case' case_selectors ':'? compound_statement
|
|
118
164
|
* Grammar: default_alone_clause : 'default' ':'? compound_statement
|
|
119
165
|
*/
|
|
120
|
-
function parseCaseBody(ctx: ParsingContext, errorMsg: string):
|
|
166
|
+
function parseCaseBody(ctx: ParsingContext, errorMsg: string): BlockElem {
|
|
121
167
|
ctx.stream.matchText(":");
|
|
122
168
|
|
|
123
|
-
const
|
|
124
|
-
const attrs = bodyAttrs.length > 0 ? bodyAttrs : undefined;
|
|
125
|
-
|
|
169
|
+
const attrs = attrsOrUndef(parseAttributeList(ctx));
|
|
126
170
|
const body = parseCompoundStatement(ctx, attrs);
|
|
127
171
|
if (!body) throwParseError(ctx.stream, errorMsg);
|
|
128
|
-
|
|
172
|
+
return body;
|
|
129
173
|
}
|
|
@@ -7,8 +7,10 @@ import type {
|
|
|
7
7
|
RequiresDirective,
|
|
8
8
|
} from "../AbstractElems.ts";
|
|
9
9
|
import { parseAttributeList } from "./ParseAttribute.ts";
|
|
10
|
+
import { getStartWithAttributes } from "./ParseStatement.ts";
|
|
10
11
|
import {
|
|
11
12
|
attachAttributes,
|
|
13
|
+
attrsOrUndef,
|
|
12
14
|
expect,
|
|
13
15
|
expectWord,
|
|
14
16
|
makeNameElem,
|
|
@@ -21,8 +23,7 @@ import type { WeslStream, WeslToken } from "./WeslStream.ts";
|
|
|
21
23
|
export function parseDirective(ctx: ParsingContext): DirectiveElem | null {
|
|
22
24
|
const { stream } = ctx;
|
|
23
25
|
const startPos = stream.checkpoint();
|
|
24
|
-
const
|
|
25
|
-
const attrs = attributes.length > 0 ? attributes : undefined;
|
|
26
|
+
const attrs = attrsOrUndef(parseAttributeList(ctx));
|
|
26
27
|
|
|
27
28
|
const result =
|
|
28
29
|
parseExtensionDirective(ctx, "enable", attrs) ||
|
|
@@ -49,11 +50,6 @@ function parseExtensionDirective(
|
|
|
49
50
|
return makeDirectiveElem(directive, token, stream, attributes);
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
function parseDirectiveName(ctx: ParsingContext): NameElem {
|
|
53
|
-
const nameToken = expectWord(ctx.stream, "Expected identifier in name list");
|
|
54
|
-
return makeNameElem(nameToken);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
53
|
/**
|
|
58
54
|
* Grammar: diagnostic_directive : 'diagnostic' diagnostic_control ';'
|
|
59
55
|
* Grammar: diagnostic_control : '(' severity_control_name ',' diagnostic_rule_name ',' ? ')'
|
|
@@ -91,13 +87,18 @@ function parseDiagnosticDirective(
|
|
|
91
87
|
return makeDirectiveElem(directive, token, stream, attributes);
|
|
92
88
|
}
|
|
93
89
|
|
|
90
|
+
function parseDirectiveName(ctx: ParsingContext): NameElem {
|
|
91
|
+
const nameToken = expectWord(ctx.stream, "Expected identifier in name list");
|
|
92
|
+
return makeNameElem(nameToken);
|
|
93
|
+
}
|
|
94
|
+
|
|
94
95
|
function makeDirectiveElem(
|
|
95
96
|
directive: EnableDirective | RequiresDirective | DiagnosticDirective,
|
|
96
97
|
token: WeslToken,
|
|
97
98
|
stream: WeslStream,
|
|
98
99
|
attributes?: AttributeElem[],
|
|
99
100
|
): DirectiveElem {
|
|
100
|
-
const start = attributes
|
|
101
|
+
const start = getStartWithAttributes(attributes, token.span[0]);
|
|
101
102
|
const end = stream.checkpoint();
|
|
102
103
|
const elem: DirectiveElem = { kind: "directive", directive, start, end };
|
|
103
104
|
attachAttributes(elem, attributes);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { AttributeElem, DoBlockElem } from "../AbstractElems.ts";
|
|
2
|
+
import type { Scope } from "../Scope.ts";
|
|
3
|
+
import { parseFnParams } from "./ParseFn.ts";
|
|
4
|
+
import { getStartWithAttributes, parseFunctionBody } from "./ParseStatement.ts";
|
|
5
|
+
import {
|
|
6
|
+
attachAttributes,
|
|
7
|
+
expectWord,
|
|
8
|
+
makeNameElem,
|
|
9
|
+
throwParseError,
|
|
10
|
+
} from "./ParseUtil.ts";
|
|
11
|
+
import type { ParsingContext } from "./ParsingContext.ts";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Grammar: do_block_decl : attribute* 'do' ident '(' param_list? ')' compound_statement
|
|
15
|
+
*
|
|
16
|
+
* `do` is a module-level declaration when the `doBlocks` extension is on;
|
|
17
|
+
* otherwise it stays a reserved word. The body is parsed structurally but
|
|
18
|
+
* inside a scope that is detached from the module scope graph, so its idents
|
|
19
|
+
* never reach bindIdents. do blocks are module-local, resolved later by AST
|
|
20
|
+
* name match.
|
|
21
|
+
*/
|
|
22
|
+
export function parseDoBlock(
|
|
23
|
+
ctx: ParsingContext,
|
|
24
|
+
attributes?: AttributeElem[],
|
|
25
|
+
): DoBlockElem | null {
|
|
26
|
+
if (!ctx.options.weslExtensions?.doBlocks) return null;
|
|
27
|
+
|
|
28
|
+
const { stream } = ctx;
|
|
29
|
+
const doToken = stream.matchText("do");
|
|
30
|
+
if (!doToken) return null;
|
|
31
|
+
|
|
32
|
+
const startPos = getStartWithAttributes(attributes, doToken.span[0]);
|
|
33
|
+
const nameToken = expectWord(stream, "Expected identifier after 'do'");
|
|
34
|
+
const name = makeNameElem(nameToken);
|
|
35
|
+
|
|
36
|
+
const parentScope = ctx.currentScope();
|
|
37
|
+
ctx.pushScope();
|
|
38
|
+
const doScope = ctx.currentScope();
|
|
39
|
+
|
|
40
|
+
const params = parseFnParams(ctx);
|
|
41
|
+
const body = parseFunctionBody(ctx);
|
|
42
|
+
if (!body) throwParseError(stream, "Expected do block body");
|
|
43
|
+
|
|
44
|
+
ctx.popScope();
|
|
45
|
+
detachScope(parentScope, doScope);
|
|
46
|
+
|
|
47
|
+
const doBlock: DoBlockElem = {
|
|
48
|
+
kind: "do",
|
|
49
|
+
name,
|
|
50
|
+
params,
|
|
51
|
+
body,
|
|
52
|
+
start: startPos,
|
|
53
|
+
end: stream.checkpoint(),
|
|
54
|
+
};
|
|
55
|
+
attachAttributes(doBlock, attributes);
|
|
56
|
+
return doBlock;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Remove a child scope from its parent so bindIdents never traverses it. */
|
|
60
|
+
function detachScope(parent: Scope, child: Scope): void {
|
|
61
|
+
const i = parent.contents.indexOf(child);
|
|
62
|
+
if (i >= 0) parent.contents.splice(i, 1);
|
|
63
|
+
}
|
|
@@ -26,7 +26,6 @@ import { parseIdent } from "./ParseIdent.ts";
|
|
|
26
26
|
import { parseTemplateParams } from "./ParseType.ts";
|
|
27
27
|
import {
|
|
28
28
|
expect,
|
|
29
|
-
expectExpression,
|
|
30
29
|
expectWord,
|
|
31
30
|
makeNameElem,
|
|
32
31
|
throwParseError,
|
|
@@ -145,10 +144,14 @@ function parseBinaryExpr(
|
|
|
145
144
|
if (isBinaryOpInContext(nextToken, inTemplate)) {
|
|
146
145
|
const nextPrec = getPrecedence(nextToken);
|
|
147
146
|
if (nextPrec > precedence) {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
147
|
+
const rightArgs: BinaryExprArgs = {
|
|
148
|
+
prec: nextPrec,
|
|
149
|
+
left: right,
|
|
150
|
+
group: "unary",
|
|
151
|
+
condRef,
|
|
152
|
+
inTemplate,
|
|
153
|
+
};
|
|
154
|
+
right = parseBinaryExpr(ctx, rightArgs).expr;
|
|
152
155
|
}
|
|
153
156
|
}
|
|
154
157
|
|
|
@@ -196,7 +199,6 @@ function parseParenExpr(
|
|
|
196
199
|
if (!expression) throwParseError(stream, "Expected expression after '('");
|
|
197
200
|
|
|
198
201
|
const close = expect(stream, ")", "expression");
|
|
199
|
-
|
|
200
202
|
const start = open.span[0];
|
|
201
203
|
const end = close.span[1];
|
|
202
204
|
return { kind: "parenthesized-expression", expression, start, end };
|
|
@@ -224,15 +226,13 @@ function parseTemplateElaboratedIdent(
|
|
|
224
226
|
if (!ctx.stream.nextTemplateStartToken()) return refIdent;
|
|
225
227
|
|
|
226
228
|
const templateParams = parseTemplateParams(ctx);
|
|
227
|
-
|
|
229
|
+
return {
|
|
228
230
|
kind: "type",
|
|
229
231
|
name: refIdent.ident,
|
|
230
232
|
templateParams,
|
|
231
233
|
start: refIdent.start,
|
|
232
234
|
end: ctx.stream.checkpoint(),
|
|
233
|
-
contents: [],
|
|
234
235
|
};
|
|
235
|
-
return typeRef;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
/** Parse postfix operators: member access, indexing, function calls. */
|
|
@@ -282,7 +282,8 @@ function parseIndexAccess(
|
|
|
282
282
|
): ComponentExpression | null {
|
|
283
283
|
const { stream } = ctx;
|
|
284
284
|
if (!stream.matchText("[")) return null;
|
|
285
|
-
const indexExpr =
|
|
285
|
+
const indexExpr = parseExpression(ctx);
|
|
286
|
+
if (!indexExpr) throwParseError(stream, "Expected expression in array index");
|
|
286
287
|
const closeBracket = expect(stream, "]", "array index");
|
|
287
288
|
return makeComponentExpression(base, indexExpr, closeBracket.span[1]);
|
|
288
289
|
}
|
package/src/parse/ParseFn.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AttributeElem,
|
|
3
|
-
DeclIdentElem,
|
|
4
3
|
FnElem,
|
|
5
4
|
FnParamElem,
|
|
6
|
-
GrammarElem,
|
|
7
|
-
StatementElem,
|
|
8
5
|
TypeRefElem,
|
|
9
6
|
} from "../AbstractElems.ts";
|
|
10
|
-
import { beginElem, finishElem } from "./ContentsHelpers.ts";
|
|
11
7
|
import { parseAttributeList } from "./ParseAttribute.ts";
|
|
12
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
finishStatement,
|
|
10
|
+
getStartWithAttributes,
|
|
11
|
+
parseFunctionBody,
|
|
12
|
+
} from "./ParseStatement.ts";
|
|
13
13
|
import { parseSimpleTypeRef } from "./ParseType.ts";
|
|
14
14
|
import {
|
|
15
15
|
attachAttributes,
|
|
16
|
+
attrsOrUndef,
|
|
16
17
|
createDeclIdentElem,
|
|
17
18
|
expect,
|
|
18
19
|
expectWord,
|
|
@@ -69,13 +70,6 @@ export function parseFnDecl(
|
|
|
69
70
|
returnAttributes,
|
|
70
71
|
start: startPos,
|
|
71
72
|
end: stream.checkpoint(),
|
|
72
|
-
contents: buildFnContents(
|
|
73
|
-
attributes,
|
|
74
|
-
declIdentElem,
|
|
75
|
-
params,
|
|
76
|
-
returnType,
|
|
77
|
-
body,
|
|
78
|
-
),
|
|
79
73
|
};
|
|
80
74
|
attachAttributes(fnElem, attributes);
|
|
81
75
|
linkDeclIdentElem(declIdentElem, fnElem);
|
|
@@ -83,7 +77,7 @@ export function parseFnDecl(
|
|
|
83
77
|
}
|
|
84
78
|
|
|
85
79
|
/** Grammar: '(' param_list? ')' where param_list : param ( ',' param )* ','? */
|
|
86
|
-
function parseFnParams(ctx: ParsingContext): FnParamElem[] {
|
|
80
|
+
export function parseFnParams(ctx: ParsingContext): FnParamElem[] {
|
|
87
81
|
const { stream } = ctx;
|
|
88
82
|
const params: FnParamElem[] = [];
|
|
89
83
|
|
|
@@ -115,37 +109,21 @@ function parseFnReturn(ctx: ParsingContext): {
|
|
|
115
109
|
const returnType = parseSimpleTypeRef(ctx);
|
|
116
110
|
if (!returnType) throwParseError(stream, "Expected type after '->'");
|
|
117
111
|
|
|
118
|
-
return { returnType, returnAttributes: attrs
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** Build contents array for function element */
|
|
122
|
-
function buildFnContents(
|
|
123
|
-
attributes: AttributeElem[] | undefined,
|
|
124
|
-
decl: DeclIdentElem,
|
|
125
|
-
params: FnParamElem[],
|
|
126
|
-
returnType: TypeRefElem | undefined,
|
|
127
|
-
body: StatementElem,
|
|
128
|
-
): GrammarElem[] {
|
|
129
|
-
const base = returnType
|
|
130
|
-
? [decl, ...params, returnType, body]
|
|
131
|
-
: [decl, ...params, body];
|
|
132
|
-
return attributes?.length ? [...attributes, ...base] : base;
|
|
112
|
+
return { returnType, returnAttributes: attrsOrUndef(attrs) };
|
|
133
113
|
}
|
|
134
114
|
|
|
135
115
|
/** Grammar: param : attribute* optionally_typed_ident */
|
|
136
116
|
function parseFnParam(ctx: ParsingContext): FnParamElem | null {
|
|
137
|
-
const
|
|
117
|
+
const attrs = parseAttributeList(ctx);
|
|
138
118
|
if (ctx.stream.peek()?.kind !== "word") return null;
|
|
119
|
+
const attributes = attrsOrUndef(attrs);
|
|
139
120
|
|
|
140
|
-
beginElem(ctx, "param", attributes.length ? attributes : undefined);
|
|
141
121
|
const name = parseTypedDecl(ctx, false);
|
|
142
122
|
if (!name)
|
|
143
123
|
throw new Error("Unexpected: peek succeeded but parseTypedDecl failed");
|
|
144
|
-
ctx.addElem(name);
|
|
145
124
|
|
|
146
125
|
const startPos = getStartWithAttributes(attributes, name.start);
|
|
147
|
-
const elem =
|
|
126
|
+
const elem = finishStatement("param", startPos, ctx, { name }, attributes);
|
|
148
127
|
linkDeclIdent(name, elem);
|
|
149
|
-
attachAttributes(elem, attributes.length > 0 ? attributes : undefined);
|
|
150
128
|
return elem;
|
|
151
129
|
}
|
|
@@ -2,19 +2,20 @@ import type {
|
|
|
2
2
|
AliasElem,
|
|
3
3
|
AttributeElem,
|
|
4
4
|
ConstAssertElem,
|
|
5
|
+
ExpressionElem,
|
|
5
6
|
GlobalVarElem,
|
|
7
|
+
NameElem,
|
|
6
8
|
} from "../AbstractElems.ts";
|
|
7
|
-
import {
|
|
8
|
-
import { getStartWithAttributes } from "./ParseStatement.ts";
|
|
9
|
+
import { finishStatement, getStartWithAttributes } from "./ParseStatement.ts";
|
|
9
10
|
import { parseSimpleTypeRef } from "./ParseType.ts";
|
|
10
11
|
import {
|
|
11
|
-
attachAttributes,
|
|
12
12
|
createDeclIdentElem,
|
|
13
13
|
expect,
|
|
14
14
|
expectExpression,
|
|
15
15
|
expectWord,
|
|
16
16
|
linkDeclIdent,
|
|
17
17
|
linkDeclIdentElem,
|
|
18
|
+
makeNameElem,
|
|
18
19
|
throwParseError,
|
|
19
20
|
} from "./ParseUtil.ts";
|
|
20
21
|
import { parseTypedDecl } from "./ParseValueDeclaration.ts";
|
|
@@ -34,24 +35,28 @@ export function parseGlobalVarDecl(
|
|
|
34
35
|
|
|
35
36
|
const startPos = getStartWithAttributes(attributes, varToken.span[0]);
|
|
36
37
|
ctx.pushScope("partial");
|
|
37
|
-
beginElem(ctx, "gvar", attributes);
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
const template = parseTemplateList(ctx);
|
|
40
40
|
|
|
41
41
|
const typedDecl = parseTypedDecl(ctx);
|
|
42
42
|
if (!typedDecl) throwParseError(stream, "Expected identifier after 'var'");
|
|
43
|
-
ctx.addElem(typedDecl);
|
|
44
43
|
|
|
44
|
+
let init: ExpressionElem | undefined;
|
|
45
45
|
if (stream.matchText("=")) {
|
|
46
|
-
expectExpression(ctx);
|
|
46
|
+
init = expectExpression(ctx);
|
|
47
47
|
}
|
|
48
48
|
expect(stream, ";", "var declaration");
|
|
49
49
|
|
|
50
50
|
typedDecl.decl.ident.dependentScope = ctx.currentScope();
|
|
51
51
|
ctx.popScope();
|
|
52
52
|
|
|
53
|
-
const varElem =
|
|
54
|
-
|
|
53
|
+
const varElem = finishStatement(
|
|
54
|
+
"gvar",
|
|
55
|
+
startPos,
|
|
56
|
+
ctx,
|
|
57
|
+
{ name: typedDecl, template, init },
|
|
58
|
+
attributes,
|
|
59
|
+
);
|
|
55
60
|
linkDeclIdent(typedDecl, varElem);
|
|
56
61
|
return varElem;
|
|
57
62
|
}
|
|
@@ -66,12 +71,10 @@ export function parseAliasDecl(
|
|
|
66
71
|
if (!aliasToken) return null;
|
|
67
72
|
|
|
68
73
|
const startPos = getStartWithAttributes(attributes, aliasToken.span[0]);
|
|
69
|
-
beginElem(ctx, "alias", attributes);
|
|
70
74
|
|
|
71
75
|
const nameToken = expectWord(stream, "Expected identifier after 'alias'");
|
|
72
76
|
|
|
73
77
|
const declIdentElem = createDeclIdentElem(ctx, nameToken, true);
|
|
74
|
-
ctx.addElem(declIdentElem);
|
|
75
78
|
ctx.saveIdent(declIdentElem.ident);
|
|
76
79
|
|
|
77
80
|
expect(stream, "=", "alias name");
|
|
@@ -80,18 +83,19 @@ export function parseAliasDecl(
|
|
|
80
83
|
const typeRef = parseSimpleTypeRef(ctx);
|
|
81
84
|
if (!typeRef)
|
|
82
85
|
throwParseError(stream, "Expected type after '=' in alias declaration");
|
|
83
|
-
ctx.addElem(typeRef);
|
|
84
86
|
|
|
85
87
|
declIdentElem.ident.dependentScope = ctx.currentScope();
|
|
86
88
|
ctx.popScope();
|
|
87
89
|
|
|
88
90
|
expect(stream, ";", "alias declaration");
|
|
89
91
|
|
|
90
|
-
const aliasElem =
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
const aliasElem = finishStatement(
|
|
93
|
+
"alias",
|
|
94
|
+
startPos,
|
|
95
|
+
ctx,
|
|
96
|
+
{ name: declIdentElem, typeRef },
|
|
97
|
+
attributes,
|
|
98
|
+
);
|
|
95
99
|
linkDeclIdentElem(declIdentElem, aliasElem);
|
|
96
100
|
return aliasElem;
|
|
97
101
|
}
|
|
@@ -105,27 +109,32 @@ export function parseConstAssert(
|
|
|
105
109
|
if (!assertToken) return null;
|
|
106
110
|
|
|
107
111
|
const startPos = getStartWithAttributes(attributes, assertToken.span[0]);
|
|
108
|
-
|
|
109
|
-
expectExpression(ctx);
|
|
112
|
+
const expression = expectExpression(ctx);
|
|
110
113
|
expect(ctx.stream, ";", "const_assert expression");
|
|
111
114
|
|
|
112
|
-
|
|
113
|
-
attachAttributes(elem, attributes);
|
|
114
|
-
return elem;
|
|
115
|
+
return finishStatement("assert", startPos, ctx, { expression }, attributes);
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
/**
|
|
118
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Parse an optional var template list `<storage, read_write>`. The entries are
|
|
120
|
+
* predeclared enumerants (address space / access mode), not user idents, so they
|
|
121
|
+
* are captured as unbound NameElems.
|
|
122
|
+
*/
|
|
123
|
+
export function parseTemplateList(ctx: ParsingContext): NameElem[] | undefined {
|
|
119
124
|
const { stream } = ctx;
|
|
120
|
-
if (!stream.nextTemplateStartToken()) return;
|
|
125
|
+
if (!stream.nextTemplateStartToken()) return undefined;
|
|
121
126
|
|
|
127
|
+
const names: NameElem[] = [];
|
|
122
128
|
while (true) {
|
|
123
129
|
const next = stream.peek();
|
|
124
130
|
if (!next) throwParseError(stream, "Unclosed template in var declaration");
|
|
125
131
|
if (next.text.startsWith(">")) {
|
|
126
132
|
stream.nextTemplateEndToken();
|
|
127
|
-
return;
|
|
133
|
+
return names;
|
|
128
134
|
}
|
|
129
|
-
stream.
|
|
135
|
+
const word = stream.matchKind("word");
|
|
136
|
+
if (word) names.push(makeNameElem(word));
|
|
137
|
+
else if (!stream.matchText(","))
|
|
138
|
+
throwParseError(stream, "Expected ',' or '>' in var template list");
|
|
130
139
|
}
|
|
131
140
|
}
|
package/src/parse/ParseIdent.ts
CHANGED
|
@@ -56,11 +56,10 @@ export function parseIdent(
|
|
|
56
56
|
|
|
57
57
|
const refIdentElem = makeRefIdentElem(ctx, ident, start, end);
|
|
58
58
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
59
|
+
// conditionRef idents (@if/@elif) stay out of the scope tree entirely.
|
|
60
|
+
// The RefIdentElem is never added to container contents - it lives only
|
|
61
|
+
// inside the expression tree built by parseExpression.
|
|
62
|
+
if (!conditionRef) ctx.saveIdent(ident);
|
|
64
63
|
|
|
65
64
|
return refIdentElem;
|
|
66
65
|
}
|