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
package/src/LowerAndEmit.ts
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AbstractElem,
|
|
3
|
+
AbstractElemBase,
|
|
4
|
+
AssignElem,
|
|
3
5
|
AttributeElem,
|
|
4
|
-
|
|
6
|
+
BlockElem,
|
|
7
|
+
CommentElem,
|
|
5
8
|
DeclIdentElem,
|
|
6
9
|
DirectiveElem,
|
|
7
10
|
ExpressionElem,
|
|
8
11
|
FnElem,
|
|
12
|
+
ForElem,
|
|
13
|
+
IfElem,
|
|
14
|
+
ModuleElem,
|
|
9
15
|
NameElem,
|
|
10
16
|
RefIdentElem,
|
|
17
|
+
Statement,
|
|
11
18
|
StructElem,
|
|
19
|
+
StructMemberElem,
|
|
20
|
+
SwitchClauseElem,
|
|
21
|
+
SwitchElem,
|
|
12
22
|
SyntheticElem,
|
|
13
|
-
|
|
23
|
+
TypedDeclElem,
|
|
24
|
+
TypeRefElem,
|
|
25
|
+
WhileElem,
|
|
14
26
|
} from "./AbstractElems.ts";
|
|
15
27
|
import { assertThatDebug, assertUnreachable } from "./Assertions.ts";
|
|
16
28
|
import { failIdentElem } from "./ClickableError.ts";
|
|
@@ -35,28 +47,115 @@ interface EmitContext {
|
|
|
35
47
|
srcBuilder: SrcMapBuilder;
|
|
36
48
|
conditions: Conditions;
|
|
37
49
|
extracting: boolean;
|
|
50
|
+
/** Current block nesting depth, for statement indentation. */
|
|
51
|
+
indent: number;
|
|
38
52
|
}
|
|
39
53
|
|
|
54
|
+
/** Declarations emitted structurally (var/let/const/override/gvar/alias/assert),
|
|
55
|
+
* in either local-statement or root-declaration position. */
|
|
56
|
+
type ValueDeclElem = Extract<
|
|
57
|
+
AbstractElem,
|
|
58
|
+
{ kind: "var" | "gvar" | "let" | "const" | "override" | "alias" | "assert" }
|
|
59
|
+
>;
|
|
60
|
+
|
|
61
|
+
/** Statement kinds that emitStatement must not follow with a ';': compound
|
|
62
|
+
* statements take none, locals already carry their own, empty needs none. */
|
|
63
|
+
const noSemicolon = new Set<Statement["kind"]>([
|
|
64
|
+
"block",
|
|
65
|
+
"if",
|
|
66
|
+
"for",
|
|
67
|
+
"while",
|
|
68
|
+
"loop",
|
|
69
|
+
"continuing",
|
|
70
|
+
"switch",
|
|
71
|
+
"empty",
|
|
72
|
+
"var",
|
|
73
|
+
"let",
|
|
74
|
+
"const",
|
|
75
|
+
"assert",
|
|
76
|
+
]);
|
|
77
|
+
|
|
40
78
|
/** Traverse the AST, starting from root elements, emitting WGSL for each. */
|
|
41
79
|
export function lowerAndEmit(params: EmitParams): void {
|
|
42
80
|
const { srcBuilder, rootElems, conditions } = params;
|
|
43
81
|
const { extracting = true, skipConditionalFiltering = false } = params;
|
|
44
82
|
|
|
45
|
-
const emitContext: EmitContext = {
|
|
83
|
+
const emitContext: EmitContext = {
|
|
84
|
+
conditions,
|
|
85
|
+
srcBuilder,
|
|
86
|
+
extracting,
|
|
87
|
+
indent: 0,
|
|
88
|
+
};
|
|
46
89
|
const validElements = skipConditionalFiltering
|
|
47
90
|
? rootElems
|
|
48
91
|
: filterValidElements(rootElems, conditions);
|
|
49
92
|
for (const e of validElements) lowerAndEmitElem(e, emitContext);
|
|
50
93
|
}
|
|
51
94
|
|
|
95
|
+
/** Format a diagnostic control as "(severity, rule)" for @diagnostic text. */
|
|
96
|
+
export function diagnosticControlToString(
|
|
97
|
+
severity: NameElem,
|
|
98
|
+
rule: [NameElem, NameElem | null],
|
|
99
|
+
): string {
|
|
100
|
+
const ruleStr = rule[0].name + (rule[1] !== null ? "." + rule[1].name : "");
|
|
101
|
+
return `(${severity.name}, ${ruleStr})`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Render an expression back to WGSL source text (template args elided as <...>). */
|
|
105
|
+
export function expressionToString(elem: ExpressionElem): string {
|
|
106
|
+
const { kind } = elem;
|
|
107
|
+
switch (kind) {
|
|
108
|
+
case "binary-expression": {
|
|
109
|
+
const left = expressionToString(elem.left);
|
|
110
|
+
const right = expressionToString(elem.right);
|
|
111
|
+
return `${left} ${elem.operator.value} ${right}`;
|
|
112
|
+
}
|
|
113
|
+
case "unary-expression":
|
|
114
|
+
return `${elem.operator.value}${expressionToString(elem.expression)}`;
|
|
115
|
+
case "ref":
|
|
116
|
+
return elem.ident.originalName;
|
|
117
|
+
case "literal":
|
|
118
|
+
return elem.value;
|
|
119
|
+
case "parenthesized-expression":
|
|
120
|
+
return `(${expressionToString(elem.expression)})`;
|
|
121
|
+
case "component-expression":
|
|
122
|
+
return `${expressionToString(elem.base)}[${elem.access}]`;
|
|
123
|
+
case "component-member-expression":
|
|
124
|
+
return `${expressionToString(elem.base)}.${elem.access}`;
|
|
125
|
+
case "call-expression": {
|
|
126
|
+
const fn = elem.function;
|
|
127
|
+
const name =
|
|
128
|
+
fn.kind === "ref" ? fn.ident.originalName : fn.name.originalName;
|
|
129
|
+
const targs = elem.templateArgs ? `<...>` : "";
|
|
130
|
+
const args = elem.arguments.map(expressionToString).join(", ");
|
|
131
|
+
return `${name}${targs}(${args})`;
|
|
132
|
+
}
|
|
133
|
+
case "type":
|
|
134
|
+
return elem.name.originalName;
|
|
135
|
+
default:
|
|
136
|
+
assertUnreachable(kind);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Trace through refersTo links until we find the declaration. */
|
|
141
|
+
export function findDecl(ident: Ident): DeclIdent {
|
|
142
|
+
let i: Ident | undefined = ident;
|
|
143
|
+
do {
|
|
144
|
+
if (i.kind === "decl") return i;
|
|
145
|
+
i = i.refersTo;
|
|
146
|
+
} while (i);
|
|
147
|
+
|
|
148
|
+
// TODO show source position if this can happen in a non buggy linker.
|
|
149
|
+
throw new Error(`unresolved identifer: ${ident.originalName}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
52
152
|
function lowerAndEmitElem(e: AbstractElem, ctx: EmitContext): void {
|
|
53
153
|
switch (e.kind) {
|
|
54
154
|
case "import":
|
|
55
155
|
return; // import statements are dropped from emitted text
|
|
156
|
+
case "do":
|
|
157
|
+
return; // do blocks are CPU-only, dropped from emitted text
|
|
56
158
|
|
|
57
|
-
case "text":
|
|
58
|
-
emitText(e, ctx);
|
|
59
|
-
return;
|
|
60
159
|
case "name":
|
|
61
160
|
emitName(e, ctx);
|
|
62
161
|
return;
|
|
@@ -82,19 +181,28 @@ function lowerAndEmitElem(e: AbstractElem, ctx: EmitContext): void {
|
|
|
82
181
|
return;
|
|
83
182
|
|
|
84
183
|
case "param":
|
|
184
|
+
emitAttributes(e.attributes, ctx);
|
|
185
|
+
emitTypedDecl(e.name, ctx);
|
|
186
|
+
return;
|
|
85
187
|
case "typeDecl":
|
|
188
|
+
emitTypedDecl(e, ctx);
|
|
189
|
+
return;
|
|
86
190
|
case "member":
|
|
87
|
-
|
|
191
|
+
emitMember(e, ctx);
|
|
192
|
+
return;
|
|
193
|
+
|
|
88
194
|
case "expression":
|
|
89
|
-
|
|
195
|
+
emitExpression(e.expression, ctx);
|
|
196
|
+
return;
|
|
197
|
+
|
|
198
|
+
// Switch clauses are normally emitted structurally by emitSwitch; handle the
|
|
199
|
+
// standalone case (e.g. a clause reached via a container walk) the same way.
|
|
90
200
|
case "switch-clause":
|
|
91
|
-
|
|
201
|
+
emitSwitchClause(e, ctx);
|
|
92
202
|
return;
|
|
93
203
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
case "stuff":
|
|
97
|
-
emitStuff(e, ctx);
|
|
204
|
+
case "type":
|
|
205
|
+
emitTypeRef(e, ctx);
|
|
98
206
|
return;
|
|
99
207
|
|
|
100
208
|
case "module":
|
|
@@ -103,8 +211,22 @@ function lowerAndEmitElem(e: AbstractElem, ctx: EmitContext): void {
|
|
|
103
211
|
|
|
104
212
|
case "var":
|
|
105
213
|
case "let":
|
|
106
|
-
case "
|
|
214
|
+
case "block":
|
|
215
|
+
case "if":
|
|
216
|
+
case "for":
|
|
217
|
+
case "while":
|
|
218
|
+
case "loop":
|
|
107
219
|
case "continuing":
|
|
220
|
+
case "switch":
|
|
221
|
+
case "return":
|
|
222
|
+
case "break":
|
|
223
|
+
case "continue":
|
|
224
|
+
case "discard":
|
|
225
|
+
case "assign":
|
|
226
|
+
case "increment":
|
|
227
|
+
case "decrement":
|
|
228
|
+
case "call":
|
|
229
|
+
case "empty":
|
|
108
230
|
emitStatement(e, ctx);
|
|
109
231
|
return;
|
|
110
232
|
|
|
@@ -118,12 +240,16 @@ function lowerAndEmitElem(e: AbstractElem, ctx: EmitContext): void {
|
|
|
118
240
|
|
|
119
241
|
case "fn":
|
|
120
242
|
emitRootElemNl(ctx);
|
|
243
|
+
emitRootLeading(e, ctx);
|
|
121
244
|
emitFn(e, ctx);
|
|
245
|
+
emitTrailingComments(e, ctx);
|
|
122
246
|
return;
|
|
123
247
|
|
|
124
248
|
case "struct":
|
|
125
249
|
emitRootElemNl(ctx);
|
|
250
|
+
emitRootLeading(e, ctx);
|
|
126
251
|
emitStruct(e, ctx);
|
|
252
|
+
emitTrailingComments(e, ctx);
|
|
127
253
|
return;
|
|
128
254
|
|
|
129
255
|
case "attribute":
|
|
@@ -131,7 +257,11 @@ function lowerAndEmitElem(e: AbstractElem, ctx: EmitContext): void {
|
|
|
131
257
|
return;
|
|
132
258
|
|
|
133
259
|
case "directive":
|
|
260
|
+
// each top-level directive on its own line (no source TextElems separate them)
|
|
261
|
+
ctx.srcBuilder.addNl();
|
|
262
|
+
emitRootLeading(e, ctx);
|
|
134
263
|
emitDirective(e, ctx);
|
|
264
|
+
emitTrailingComments(e, ctx);
|
|
135
265
|
return;
|
|
136
266
|
|
|
137
267
|
default:
|
|
@@ -139,52 +269,123 @@ function lowerAndEmitElem(e: AbstractElem, ctx: EmitContext): void {
|
|
|
139
269
|
}
|
|
140
270
|
}
|
|
141
271
|
|
|
142
|
-
function
|
|
143
|
-
|
|
272
|
+
function emitName(e: NameElem, ctx: EmitContext): void {
|
|
273
|
+
ctx.srcBuilder.add(e.name, e.start, e.end);
|
|
144
274
|
}
|
|
145
275
|
|
|
146
|
-
function
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
276
|
+
function emitSynthetic(e: SyntheticElem, ctx: EmitContext): void {
|
|
277
|
+
const { text } = e;
|
|
278
|
+
ctx.srcBuilder.addSynthetic(text, text, 0, text.length);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function emitRefIdent(e: RefIdentElem, ctx: EmitContext): void {
|
|
282
|
+
if (e.ident.std) {
|
|
283
|
+
ctx.srcBuilder.add(e.ident.originalName, e.start, e.end);
|
|
284
|
+
} else {
|
|
285
|
+
ctx.srcBuilder.add(displayName(findDecl(e.ident)), e.start, e.end);
|
|
155
286
|
}
|
|
156
287
|
}
|
|
157
288
|
|
|
158
|
-
function
|
|
159
|
-
e
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
289
|
+
function emitDeclIdent(e: DeclIdentElem, ctx: EmitContext): void {
|
|
290
|
+
ctx.srcBuilder.add(displayName(e.ident), e.start, e.end);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Emit an expression with any comments attached to it. Comments inside an
|
|
294
|
+
* expression (e.g. `foo(1, /* x *\/ 2)`) ride on the relevant sub-node and are
|
|
295
|
+
* emitted inline around it here. */
|
|
296
|
+
function emitExpression(e: ExpressionElem, ctx: EmitContext): void {
|
|
297
|
+
emitInlineLeading(e, ctx);
|
|
298
|
+
emitExpressionCore(e, ctx);
|
|
299
|
+
emitInlineTrailing(e, ctx);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function emitAttributes(
|
|
303
|
+
attributes: AttributeElem[] | undefined,
|
|
163
304
|
ctx: EmitContext,
|
|
164
305
|
): void {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
306
|
+
attributes?.forEach(a => {
|
|
307
|
+
emitInlineLeading(a, ctx);
|
|
308
|
+
if (emitAttribute(a, ctx)) {
|
|
309
|
+
emitInlineTrailing(a, ctx);
|
|
310
|
+
ctx.srcBuilder.add(" ", a.start, a.end);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Emit a declared identifier with its optional `: type` annotation. */
|
|
316
|
+
function emitTypedDecl(name: TypedDeclElem, ctx: EmitContext): void {
|
|
317
|
+
emitInlineLeading(name, ctx);
|
|
318
|
+
emitDeclIdent(name.decl, ctx);
|
|
319
|
+
if (name.typeRef) {
|
|
320
|
+
ctx.srcBuilder.appendNext(": ");
|
|
321
|
+
emitTypeRef(name.typeRef, ctx);
|
|
322
|
+
}
|
|
323
|
+
emitInlineTrailing(name, ctx);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** Emit a struct member from its typed fields: `[attrs] name: type`. */
|
|
327
|
+
function emitMember(member: StructMemberElem, ctx: EmitContext): void {
|
|
328
|
+
emitAttributes(member.attributes, ctx);
|
|
329
|
+
emitName(member.name, ctx);
|
|
330
|
+
ctx.srcBuilder.appendNext(": ");
|
|
331
|
+
emitTypeRef(member.typeRef, ctx);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** A `case sel, ...:` or `default:` clause with its `{ ... }` body. The selector
|
|
335
|
+
* colon is optional in WGSL but kept here as the canonical form. */
|
|
336
|
+
function emitSwitchClause(e: SwitchClauseElem, ctx: EmitContext): void {
|
|
337
|
+
const builder = ctx.srcBuilder;
|
|
338
|
+
emitLeadingComments(e, ctx);
|
|
339
|
+
newLine(ctx);
|
|
340
|
+
emitAttributes(e.attributes, ctx);
|
|
341
|
+
const defaultOnly = e.selectors.length === 1 && e.selectors[0] === "default";
|
|
342
|
+
if (defaultOnly) {
|
|
343
|
+
builder.appendNext("default");
|
|
344
|
+
} else {
|
|
345
|
+
builder.appendNext("case ");
|
|
346
|
+
e.selectors.forEach((sel, i) => {
|
|
347
|
+
if (i > 0) builder.appendNext(", ");
|
|
348
|
+
if (sel === "default") builder.appendNext("default");
|
|
349
|
+
else emitExpression(sel, ctx);
|
|
350
|
+
});
|
|
169
351
|
}
|
|
170
|
-
|
|
352
|
+
builder.appendNext(": ");
|
|
353
|
+
emitBlock(e.body, ctx);
|
|
354
|
+
emitTrailingComments(e, ctx);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** Emit a type reference structurally: name plus an optional <...> arg list. */
|
|
358
|
+
function emitTypeRef(e: TypeRefElem, ctx: EmitContext): void {
|
|
359
|
+
emitRefIdent(e.name.refIdentElem, ctx);
|
|
360
|
+
if (e.templateParams) emitTemplateArgs(e.templateParams, ctx);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function emitModule(e: ModuleElem, ctx: EmitContext): void {
|
|
364
|
+
// The module's typed children emit structurally, each handling its own
|
|
365
|
+
// leading blank lines (emitRootElemNl / emitRootDecl); no TextElems remain.
|
|
366
|
+
const validElements = filterValidElements(e.decls, ctx.conditions);
|
|
367
|
+
for (const child of validElements) lowerAndEmitElem(child, ctx);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** Emit one statement on its own line, with attached leading/trailing comments. */
|
|
371
|
+
function emitStatement(stmt: Statement, ctx: EmitContext): void {
|
|
372
|
+
emitLeadingComments(stmt, ctx);
|
|
373
|
+
newLine(ctx);
|
|
374
|
+
emitCoreSemi(stmt, ctx);
|
|
375
|
+
emitTrailingComments(stmt, ctx);
|
|
171
376
|
}
|
|
172
377
|
|
|
173
378
|
function emitRootDecl(
|
|
174
379
|
e: Extract<
|
|
175
|
-
|
|
380
|
+
ValueDeclElem,
|
|
176
381
|
{ kind: "override" | "const" | "assert" | "alias" | "gvar" }
|
|
177
382
|
>,
|
|
178
383
|
ctx: EmitContext,
|
|
179
384
|
): void {
|
|
180
385
|
emitRootElemNl(ctx);
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
emitAttributes(e.attributes, ctx);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
emitContentsWithTrimming(e, ctx);
|
|
386
|
+
emitRootLeading(e, ctx);
|
|
387
|
+
emitValueDecl(e, ctx);
|
|
388
|
+
emitTrailingComments(e, ctx);
|
|
188
389
|
}
|
|
189
390
|
|
|
190
391
|
/** Emit newlines between root elements. */
|
|
@@ -193,12 +394,14 @@ function emitRootElemNl(ctx: EmitContext): void {
|
|
|
193
394
|
ctx.srcBuilder.addNl();
|
|
194
395
|
}
|
|
195
396
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
397
|
+
/** Leading comments for a root declaration, each on its own line. The inter-decl
|
|
398
|
+
* spacing already left us at a fresh line, so a newline follows each comment
|
|
399
|
+
* (rather than preceding it as in emitLeadingComments). */
|
|
400
|
+
function emitRootLeading(e: AbstractElemBase, ctx: EmitContext): void {
|
|
401
|
+
for (const c of e.commentsBefore ?? []) {
|
|
402
|
+
emitComment(c, ctx);
|
|
403
|
+
newLine(ctx);
|
|
404
|
+
}
|
|
202
405
|
}
|
|
203
406
|
|
|
204
407
|
/** Emit function explicitly to control commas between conditional parameters. */
|
|
@@ -209,19 +412,17 @@ function emitFn(e: FnElem, ctx: EmitContext): void {
|
|
|
209
412
|
emitAttributes(attributes, ctx);
|
|
210
413
|
|
|
211
414
|
builder.add("fn ", name.start - 3, name.start);
|
|
415
|
+
emitInlineLeading(name, ctx);
|
|
212
416
|
emitDeclIdent(name, ctx);
|
|
417
|
+
emitInlineTrailing(name, ctx);
|
|
213
418
|
|
|
214
419
|
builder.appendNext("(");
|
|
215
420
|
const validParams = filterValidElements(params, conditions);
|
|
216
421
|
validParams.forEach((p, i) => {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (!attrsInContents) {
|
|
222
|
-
emitAttributes(p.attributes, ctx);
|
|
223
|
-
}
|
|
224
|
-
emitContentsNoWs(p as ContainerElem, ctx);
|
|
422
|
+
emitInlineLeading(p, ctx);
|
|
423
|
+
emitAttributes(p.attributes, ctx);
|
|
424
|
+
emitTypedDecl(p.name, ctx);
|
|
425
|
+
emitInlineTrailing(p, ctx);
|
|
225
426
|
if (i < validParams.length - 1) {
|
|
226
427
|
builder.appendNext(", ");
|
|
227
428
|
}
|
|
@@ -231,23 +432,21 @@ function emitFn(e: FnElem, ctx: EmitContext): void {
|
|
|
231
432
|
if (returnType) {
|
|
232
433
|
builder.appendNext("-> ");
|
|
233
434
|
emitAttributes(returnAttributes, ctx);
|
|
234
|
-
|
|
435
|
+
emitInlineLeading(returnType, ctx);
|
|
436
|
+
emitTypeRef(returnType, ctx);
|
|
235
437
|
builder.appendNext(" ");
|
|
236
438
|
}
|
|
237
439
|
|
|
238
|
-
|
|
440
|
+
emitBlock(body, ctx);
|
|
239
441
|
}
|
|
240
442
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
ctx.srcBuilder.add(" ", a.start, a.end);
|
|
249
|
-
}
|
|
250
|
-
});
|
|
443
|
+
/** Trailing comments: kept on the element's line, after its text. */
|
|
444
|
+
function emitTrailingComments(e: AbstractElemBase, ctx: EmitContext): void {
|
|
445
|
+
if (!e.commentsAfter) return;
|
|
446
|
+
for (const c of e.commentsAfter) {
|
|
447
|
+
ctx.srcBuilder.appendNext(" ");
|
|
448
|
+
emitComment(c, ctx);
|
|
449
|
+
}
|
|
251
450
|
}
|
|
252
451
|
|
|
253
452
|
/** Emit structs explicitly to control commas between conditional members. */
|
|
@@ -265,174 +464,26 @@ function emitStruct(e: StructElem, ctx: EmitContext): void {
|
|
|
265
464
|
|
|
266
465
|
emitAttributes(attributes, ctx);
|
|
267
466
|
srcBuilder.add("struct ", start, name.start);
|
|
467
|
+
emitInlineLeading(name, ctx);
|
|
268
468
|
emitDeclIdent(name, ctx);
|
|
469
|
+
emitInlineTrailing(name, ctx);
|
|
269
470
|
|
|
270
|
-
|
|
471
|
+
// a member with attached comments forces the multi-line form (the one-line
|
|
472
|
+
// `{ x: i32 }` form has nowhere to put an own-line comment)
|
|
473
|
+
if (validLength === 1 && !hasComments(validMembers[0])) {
|
|
271
474
|
srcBuilder.appendNext(" { ");
|
|
272
|
-
|
|
475
|
+
emitMember(validMembers[0], ctx);
|
|
273
476
|
srcBuilder.appendNext(" }");
|
|
274
477
|
srcBuilder.addNl();
|
|
275
478
|
} else {
|
|
276
479
|
srcBuilder.appendNext(" {");
|
|
277
480
|
srcBuilder.addNl();
|
|
278
|
-
|
|
279
|
-
validMembers.forEach(m => {
|
|
280
|
-
srcBuilder.appendNext(" ");
|
|
281
|
-
emitContentsNoWs(m as ContainerElem, ctx);
|
|
282
|
-
srcBuilder.appendNext(",");
|
|
283
|
-
srcBuilder.addNl();
|
|
284
|
-
});
|
|
285
|
-
|
|
481
|
+
for (const m of validMembers) emitMemberLine(m, ctx);
|
|
286
482
|
srcBuilder.appendNext("}");
|
|
287
483
|
srcBuilder.addNl();
|
|
288
484
|
}
|
|
289
485
|
}
|
|
290
486
|
|
|
291
|
-
function warnEmptyStruct(e: StructElem): void {
|
|
292
|
-
const { name, members } = e;
|
|
293
|
-
const condStr = members.length ? "(with current conditions)" : "";
|
|
294
|
-
const message = `struct '${name.ident.originalName}' has no members ${condStr}`;
|
|
295
|
-
failIdentElem(name, message);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function emitSynthetic(e: SyntheticElem, ctx: EmitContext): void {
|
|
299
|
-
const { text } = e;
|
|
300
|
-
ctx.srcBuilder.addSynthetic(text, text, 0, text.length);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function emitContents(elem: ContainerElem, ctx: EmitContext): void {
|
|
304
|
-
const validElements = filterValidElements(elem.contents, ctx.conditions);
|
|
305
|
-
for (const e of validElements) lowerAndEmitElem(e, ctx);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/** Emit contents with leading/trailing whitespace trimming (V2 parser). */
|
|
309
|
-
function emitContentsWithTrimming(elem: ContainerElem, ctx: EmitContext): void {
|
|
310
|
-
const validElements = filterValidElements(elem.contents, ctx.conditions);
|
|
311
|
-
|
|
312
|
-
// Find first/last non-conditional-attribute indices for trimming
|
|
313
|
-
const firstEmit = validElements.findIndex(e => !isConditionalAttr(e));
|
|
314
|
-
const lastEmit = validElements.findLastIndex(e => !isConditionalAttr(e));
|
|
315
|
-
|
|
316
|
-
validElements.forEach((elem, i) => {
|
|
317
|
-
if (elem.kind === "text") {
|
|
318
|
-
let text = elem.srcModule.src.slice(elem.start, elem.end);
|
|
319
|
-
if (i === firstEmit) text = text.trimStart();
|
|
320
|
-
if (i === lastEmit) text = text.trimEnd();
|
|
321
|
-
if (text) ctx.srcBuilder.add(text, elem.start, elem.end);
|
|
322
|
-
} else {
|
|
323
|
-
lowerAndEmitElem(elem, ctx);
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function isConditionalAttr(e: AbstractElem): boolean {
|
|
329
|
-
if (e.kind !== "attribute") return false;
|
|
330
|
-
const { kind } = e.attribute;
|
|
331
|
-
return kind === "@if" || kind === "@elif" || kind === "@else";
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/** Emit contents without whitespace. */
|
|
335
|
-
function emitContentsNoWs(elem: ContainerElem, ctx: EmitContext): void {
|
|
336
|
-
const validElements = filterValidElements(elem.contents, ctx.conditions);
|
|
337
|
-
validElements.forEach(e => {
|
|
338
|
-
if (e.kind === "text") {
|
|
339
|
-
const { srcModule, start, end } = e;
|
|
340
|
-
const text = srcModule.src.slice(start, end);
|
|
341
|
-
if (text.trim() === "") {
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
lowerAndEmitElem(e, ctx);
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function emitRefIdent(e: RefIdentElem, ctx: EmitContext): void {
|
|
350
|
-
if (e.ident.std) {
|
|
351
|
-
ctx.srcBuilder.add(e.ident.originalName, e.start, e.end);
|
|
352
|
-
} else {
|
|
353
|
-
const declIdent = findDecl(e.ident);
|
|
354
|
-
const mangledName = displayName(declIdent);
|
|
355
|
-
ctx.srcBuilder.add(mangledName!, e.start, e.end);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
function emitDeclIdent(e: DeclIdentElem, ctx: EmitContext): void {
|
|
360
|
-
const mangledName = displayName(e.ident);
|
|
361
|
-
ctx.srcBuilder.add(mangledName!, e.start, e.end);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
function emitExpression(e: ExpressionElem, ctx: EmitContext): void {
|
|
365
|
-
const { kind } = e;
|
|
366
|
-
|
|
367
|
-
if (kind === "literal") {
|
|
368
|
-
ctx.srcBuilder.add(e.value, e.start, e.end);
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
if (kind === "ref") {
|
|
373
|
-
emitRefIdent(e, ctx);
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
if (kind === "type") {
|
|
378
|
-
emitContents(e, ctx);
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
if (kind === "binary-expression") {
|
|
383
|
-
emitExpression(e.left, ctx);
|
|
384
|
-
ctx.srcBuilder.add(
|
|
385
|
-
` ${e.operator.value} `,
|
|
386
|
-
e.operator.span[0],
|
|
387
|
-
e.operator.span[1],
|
|
388
|
-
);
|
|
389
|
-
emitExpression(e.right, ctx);
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (kind === "unary-expression") {
|
|
394
|
-
ctx.srcBuilder.add(
|
|
395
|
-
e.operator.value,
|
|
396
|
-
e.operator.span[0],
|
|
397
|
-
e.operator.span[1],
|
|
398
|
-
);
|
|
399
|
-
emitExpression(e.expression, ctx);
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if (kind === "parenthesized-expression") {
|
|
404
|
-
emitExpression(e.expression, ctx);
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (kind === "call-expression") {
|
|
409
|
-
emitExpression(e.function, ctx);
|
|
410
|
-
if (e.templateArgs) {
|
|
411
|
-
for (const targ of e.templateArgs) lowerAndEmitElem(targ, ctx);
|
|
412
|
-
}
|
|
413
|
-
for (const arg of e.arguments) {
|
|
414
|
-
emitExpression(arg, ctx);
|
|
415
|
-
}
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (kind === "component-expression") {
|
|
420
|
-
emitExpression(e.base, ctx);
|
|
421
|
-
emitExpression(e.access, ctx);
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
if (kind === "component-member-expression") {
|
|
426
|
-
emitExpression(e.base, ctx);
|
|
427
|
-
if (e.access.kind === "name") {
|
|
428
|
-
ctx.srcBuilder.add(e.access.name, e.access.start, e.access.end);
|
|
429
|
-
}
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
assertUnreachable(kind);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
487
|
function emitAttribute(e: AttributeElem, ctx: EmitContext): boolean {
|
|
437
488
|
const { kind } = e.attribute;
|
|
438
489
|
|
|
@@ -449,18 +500,14 @@ function emitAttribute(e: AttributeElem, ctx: EmitContext): boolean {
|
|
|
449
500
|
}
|
|
450
501
|
|
|
451
502
|
if (kind === "@builtin") {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
e.start,
|
|
455
|
-
e.end,
|
|
456
|
-
);
|
|
503
|
+
const builtinStr = `@builtin(${e.attribute.param.name})`;
|
|
504
|
+
ctx.srcBuilder.add(builtinStr, e.start, e.end);
|
|
457
505
|
return true;
|
|
458
506
|
}
|
|
459
507
|
|
|
460
508
|
if (kind === "@diagnostic") {
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
diagnosticControlToString(e.attribute.severity, e.attribute.rule);
|
|
509
|
+
const { severity, rule } = e.attribute;
|
|
510
|
+
const diagStr = `@diagnostic${diagnosticControlToString(severity, rule)}`;
|
|
464
511
|
ctx.srcBuilder.add(diagStr, e.start, e.end);
|
|
465
512
|
return true;
|
|
466
513
|
}
|
|
@@ -474,68 +521,6 @@ function emitAttribute(e: AttributeElem, ctx: EmitContext): boolean {
|
|
|
474
521
|
assertUnreachable(kind);
|
|
475
522
|
}
|
|
476
523
|
|
|
477
|
-
function emitStandardAttribute(e: AttributeElem, ctx: EmitContext): void {
|
|
478
|
-
if (e.attribute.kind !== "@attribute") return;
|
|
479
|
-
|
|
480
|
-
const { params } = e.attribute;
|
|
481
|
-
if (!params || params.length === 0) {
|
|
482
|
-
ctx.srcBuilder.add("@" + e.attribute.name, e.start, e.end);
|
|
483
|
-
return;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
ctx.srcBuilder.add("@" + e.attribute.name + "(", e.start, params[0].start);
|
|
487
|
-
for (let i = 0; i < params.length; i++) {
|
|
488
|
-
emitContents(params[i], ctx);
|
|
489
|
-
if (i < params.length - 1) {
|
|
490
|
-
ctx.srcBuilder.add(",", params[i].end, params[i + 1].start);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
ctx.srcBuilder.add(")", params[params.length - 1].end, e.end);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
export function diagnosticControlToString(
|
|
497
|
-
severity: NameElem,
|
|
498
|
-
rule: [NameElem, NameElem | null],
|
|
499
|
-
): string {
|
|
500
|
-
const ruleStr = rule[0].name + (rule[1] !== null ? "." + rule[1].name : "");
|
|
501
|
-
return `(${severity.name}, ${ruleStr})`;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
export function expressionToString(elem: ExpressionElem): string {
|
|
505
|
-
const { kind } = elem;
|
|
506
|
-
switch (kind) {
|
|
507
|
-
case "binary-expression": {
|
|
508
|
-
const left = expressionToString(elem.left);
|
|
509
|
-
const right = expressionToString(elem.right);
|
|
510
|
-
return `${left} ${elem.operator.value} ${right}`;
|
|
511
|
-
}
|
|
512
|
-
case "unary-expression":
|
|
513
|
-
return `${elem.operator.value}${expressionToString(elem.expression)}`;
|
|
514
|
-
case "ref":
|
|
515
|
-
return elem.ident.originalName;
|
|
516
|
-
case "literal":
|
|
517
|
-
return elem.value;
|
|
518
|
-
case "parenthesized-expression":
|
|
519
|
-
return `(${expressionToString(elem.expression)})`;
|
|
520
|
-
case "component-expression":
|
|
521
|
-
return `${expressionToString(elem.base)}[${elem.access}]`;
|
|
522
|
-
case "component-member-expression":
|
|
523
|
-
return `${expressionToString(elem.base)}.${elem.access}`;
|
|
524
|
-
case "call-expression": {
|
|
525
|
-
const fn = elem.function;
|
|
526
|
-
const name =
|
|
527
|
-
fn.kind === "ref" ? fn.ident.originalName : fn.name.originalName;
|
|
528
|
-
const targs = elem.templateArgs ? `<...>` : "";
|
|
529
|
-
const args = elem.arguments.map(expressionToString).join(", ");
|
|
530
|
-
return `${name}${targs}(${args})`;
|
|
531
|
-
}
|
|
532
|
-
case "type":
|
|
533
|
-
return elem.name.originalName;
|
|
534
|
-
default:
|
|
535
|
-
assertUnreachable(kind);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
524
|
function emitDirective(e: DirectiveElem, ctx: EmitContext): void {
|
|
540
525
|
const { directive } = e;
|
|
541
526
|
const { kind } = directive;
|
|
@@ -566,16 +551,387 @@ function displayName(declIdent: DeclIdent): string {
|
|
|
566
551
|
return declIdent.mangledName || declIdent.originalName;
|
|
567
552
|
}
|
|
568
553
|
|
|
569
|
-
/**
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
554
|
+
/** Leading comments on an inline node: block comments get a trailing space, line
|
|
555
|
+
* comments force a newline so the following code is not swallowed. */
|
|
556
|
+
function emitInlineLeading(e: AbstractElemBase, ctx: EmitContext): void {
|
|
557
|
+
for (const c of e.commentsBefore ?? []) {
|
|
558
|
+
emitComment(c, ctx);
|
|
559
|
+
if (c.style === "line") newLine(ctx);
|
|
560
|
+
else ctx.srcBuilder.appendNext(" ");
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function emitExpressionCore(e: ExpressionElem, ctx: EmitContext): void {
|
|
565
|
+
const builder = ctx.srcBuilder;
|
|
566
|
+
switch (e.kind) {
|
|
567
|
+
case "literal":
|
|
568
|
+
builder.add(e.value, e.start, e.end);
|
|
569
|
+
return;
|
|
570
|
+
case "ref":
|
|
571
|
+
emitRefIdent(e, ctx);
|
|
572
|
+
return;
|
|
573
|
+
case "type":
|
|
574
|
+
emitTypeRef(e, ctx);
|
|
575
|
+
return;
|
|
576
|
+
case "binary-expression": {
|
|
577
|
+
const [start, end] = e.operator.span;
|
|
578
|
+
emitExpression(e.left, ctx);
|
|
579
|
+
builder.add(` ${e.operator.value} `, start, end);
|
|
580
|
+
emitExpression(e.right, ctx);
|
|
581
|
+
return;
|
|
575
582
|
}
|
|
576
|
-
|
|
577
|
-
|
|
583
|
+
case "unary-expression": {
|
|
584
|
+
const { value, start, end } = e.operator;
|
|
585
|
+
builder.add(value, start, end);
|
|
586
|
+
emitExpression(e.expression, ctx);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
case "parenthesized-expression":
|
|
590
|
+
builder.appendNext("(");
|
|
591
|
+
emitExpression(e.expression, ctx);
|
|
592
|
+
builder.appendNext(")");
|
|
593
|
+
return;
|
|
594
|
+
case "call-expression":
|
|
595
|
+
emitExpression(e.function, ctx);
|
|
596
|
+
if (e.templateArgs) emitTemplateArgs(e.templateArgs, ctx);
|
|
597
|
+
builder.appendNext("(");
|
|
598
|
+
e.arguments.forEach((arg, i) => {
|
|
599
|
+
if (i > 0) builder.appendNext(", ");
|
|
600
|
+
emitExpression(arg, ctx);
|
|
601
|
+
});
|
|
602
|
+
builder.appendNext(")");
|
|
603
|
+
return;
|
|
604
|
+
case "component-expression":
|
|
605
|
+
emitExpression(e.base, ctx);
|
|
606
|
+
builder.appendNext("[");
|
|
607
|
+
emitExpression(e.access, ctx);
|
|
608
|
+
builder.appendNext("]");
|
|
609
|
+
return;
|
|
610
|
+
case "component-member-expression":
|
|
611
|
+
emitExpression(e.base, ctx);
|
|
612
|
+
builder.add("." + e.access.name, e.access.start, e.access.end);
|
|
613
|
+
return;
|
|
614
|
+
default:
|
|
615
|
+
assertUnreachable(e);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
578
618
|
|
|
579
|
-
|
|
580
|
-
|
|
619
|
+
/** Trailing comments on an inline node: a leading space, plus a newline after a
|
|
620
|
+
* line comment so following code stays off its line. */
|
|
621
|
+
function emitInlineTrailing(e: AbstractElemBase, ctx: EmitContext): void {
|
|
622
|
+
for (const c of e.commentsAfter ?? []) {
|
|
623
|
+
ctx.srcBuilder.appendNext(" ");
|
|
624
|
+
emitComment(c, ctx);
|
|
625
|
+
if (c.style === "line") newLine(ctx);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/** Leading comments: each on its own indented line above the element. */
|
|
630
|
+
function emitLeadingComments(e: AbstractElemBase, ctx: EmitContext): void {
|
|
631
|
+
if (!e.commentsBefore) return;
|
|
632
|
+
for (const c of e.commentsBefore) {
|
|
633
|
+
if (c.blankBefore) ctx.srcBuilder.addNl();
|
|
634
|
+
newLine(ctx);
|
|
635
|
+
emitComment(c, ctx);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/** Start a fresh line at the current indent. */
|
|
640
|
+
function newLine(ctx: EmitContext): void {
|
|
641
|
+
ctx.srcBuilder.addNl();
|
|
642
|
+
if (ctx.indent > 0) ctx.srcBuilder.appendNext(" ".repeat(ctx.indent));
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/** Emit a `{ ... }` block, one statement per indented line. A block with no
|
|
646
|
+
* statements collapses to `{ }` unless it holds dangling inner comments. */
|
|
647
|
+
function emitBlock(e: BlockElem, ctx: EmitContext): void {
|
|
648
|
+
emitAttributes(e.attributes, ctx);
|
|
649
|
+
const stmts = filterValidElements(e.body, ctx.conditions);
|
|
650
|
+
if (stmts.length === 0 && !e.innerComments?.length) {
|
|
651
|
+
ctx.srcBuilder.appendNext("{ }");
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
ctx.srcBuilder.appendNext("{");
|
|
655
|
+
const inner = childIndent(ctx);
|
|
656
|
+
for (const comment of e.innerComments ?? []) {
|
|
657
|
+
newLine(inner);
|
|
658
|
+
emitComment(comment, inner);
|
|
659
|
+
}
|
|
660
|
+
for (const stmt of stmts) emitStatement(stmt, inner);
|
|
661
|
+
newLine(ctx);
|
|
662
|
+
ctx.srcBuilder.appendNext("}");
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/** Emit a comma-separated template argument list: <a, b, c>. */
|
|
666
|
+
function emitTemplateArgs(args: ExpressionElem[], ctx: EmitContext): void {
|
|
667
|
+
ctx.srcBuilder.appendNext("<");
|
|
668
|
+
args.forEach((a, i) => {
|
|
669
|
+
if (i > 0) ctx.srcBuilder.appendNext(", ");
|
|
670
|
+
emitExpression(a, ctx);
|
|
671
|
+
});
|
|
672
|
+
ctx.srcBuilder.appendNext(">");
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/** Emit a statement's syntax followed by ';' unless its kind takes none. */
|
|
676
|
+
function emitCoreSemi(stmt: Statement, ctx: EmitContext): void {
|
|
677
|
+
emitStatementCore(stmt, ctx);
|
|
678
|
+
if (!noSemicolon.has(stmt.kind)) ctx.srcBuilder.appendNext(";");
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/** Emit a declaration from its typed fields, including its trailing ';':
|
|
682
|
+
* `[attrs] var<...> name: T = init;`, `const name = init;`, `override n;`,
|
|
683
|
+
* `alias name = T;`, `const_assert expr;`. */
|
|
684
|
+
function emitValueDecl(e: ValueDeclElem, ctx: EmitContext): void {
|
|
685
|
+
emitAttributes(e.attributes, ctx);
|
|
686
|
+
const builder = ctx.srcBuilder;
|
|
687
|
+
switch (e.kind) {
|
|
688
|
+
case "var":
|
|
689
|
+
case "gvar":
|
|
690
|
+
builder.appendNext("var");
|
|
691
|
+
if (e.template) emitVarTemplate(e.template, ctx);
|
|
692
|
+
builder.appendNext(" ");
|
|
693
|
+
emitTypedDecl(e.name, ctx);
|
|
694
|
+
emitInit(e.init, ctx);
|
|
695
|
+
break;
|
|
696
|
+
case "let":
|
|
697
|
+
case "const":
|
|
698
|
+
case "override":
|
|
699
|
+
builder.appendNext(`${e.kind} `);
|
|
700
|
+
emitTypedDecl(e.name, ctx);
|
|
701
|
+
emitInit(e.init, ctx);
|
|
702
|
+
break;
|
|
703
|
+
case "alias":
|
|
704
|
+
builder.appendNext("alias ");
|
|
705
|
+
emitInlineLeading(e.name, ctx);
|
|
706
|
+
emitDeclIdent(e.name, ctx);
|
|
707
|
+
emitInlineTrailing(e.name, ctx);
|
|
708
|
+
builder.appendNext(" = ");
|
|
709
|
+
emitTypeRef(e.typeRef, ctx);
|
|
710
|
+
break;
|
|
711
|
+
case "assert":
|
|
712
|
+
builder.appendNext("const_assert ");
|
|
713
|
+
emitExpression(e.expression, ctx);
|
|
714
|
+
break;
|
|
715
|
+
default:
|
|
716
|
+
assertUnreachable(e);
|
|
717
|
+
}
|
|
718
|
+
builder.appendNext(";");
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function emitComment(c: CommentElem, ctx: EmitContext): void {
|
|
722
|
+
ctx.srcBuilder.add(c.srcModule.src.slice(c.start, c.end), c.start, c.end);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function warnEmptyStruct(e: StructElem): void {
|
|
726
|
+
const { name, members } = e;
|
|
727
|
+
const condStr = members.length ? "(with current conditions)" : "";
|
|
728
|
+
const message = `struct '${name.ident.originalName}' has no members ${condStr}`;
|
|
729
|
+
failIdentElem(name, message);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/** Whether an element carries any attached comments. */
|
|
733
|
+
function hasComments(e: AbstractElemBase): boolean {
|
|
734
|
+
return !!(e.commentsBefore?.length || e.commentsAfter?.length);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/** Emit one struct member on its own line: leading comments above, `name: type,`,
|
|
738
|
+
* then trailing comments inline. */
|
|
739
|
+
function emitMemberLine(m: StructMemberElem, ctx: EmitContext): void {
|
|
740
|
+
const builder = ctx.srcBuilder;
|
|
741
|
+
for (const c of m.commentsBefore ?? []) {
|
|
742
|
+
builder.appendNext(" ");
|
|
743
|
+
emitComment(c, ctx);
|
|
744
|
+
builder.addNl();
|
|
745
|
+
}
|
|
746
|
+
builder.appendNext(" ");
|
|
747
|
+
emitMember(m, ctx);
|
|
748
|
+
builder.appendNext(",");
|
|
749
|
+
for (const c of m.commentsAfter ?? []) {
|
|
750
|
+
builder.appendNext(" ");
|
|
751
|
+
emitComment(c, ctx);
|
|
752
|
+
}
|
|
753
|
+
builder.addNl();
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function emitStandardAttribute(e: AttributeElem, ctx: EmitContext): void {
|
|
757
|
+
if (e.attribute.kind !== "@attribute") return;
|
|
758
|
+
|
|
759
|
+
const { params } = e.attribute;
|
|
760
|
+
if (!params || params.length === 0) {
|
|
761
|
+
ctx.srcBuilder.add("@" + e.attribute.name, e.start, e.end);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
ctx.srcBuilder.add("@" + e.attribute.name + "(", e.start, params[0].start);
|
|
766
|
+
params.forEach((param, i) => {
|
|
767
|
+
if (i > 0) ctx.srcBuilder.appendNext(", ");
|
|
768
|
+
emitExpression(param.expression, ctx);
|
|
769
|
+
});
|
|
770
|
+
ctx.srcBuilder.add(")", params[params.length - 1].end, e.end);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/** A child context indented one level deeper. */
|
|
774
|
+
function childIndent(ctx: EmitContext): EmitContext {
|
|
775
|
+
return { ...ctx, indent: ctx.indent + 1 };
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/** Emit a statement's syntax, without surrounding line breaks, ';', or comments. */
|
|
779
|
+
function emitStatementCore(stmt: Statement, ctx: EmitContext): void {
|
|
780
|
+
if (
|
|
781
|
+
stmt.kind === "var" ||
|
|
782
|
+
stmt.kind === "let" ||
|
|
783
|
+
stmt.kind === "const" ||
|
|
784
|
+
stmt.kind === "assert"
|
|
785
|
+
) {
|
|
786
|
+
emitValueDecl(stmt, ctx);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
// a block prints its own attributes (before its '{'); everything else prints
|
|
790
|
+
// them before its keyword.
|
|
791
|
+
if (stmt.kind !== "block") emitAttributes(stmt.attributes, ctx);
|
|
792
|
+
const builder = ctx.srcBuilder;
|
|
793
|
+
switch (stmt.kind) {
|
|
794
|
+
case "block":
|
|
795
|
+
emitBlock(stmt, ctx);
|
|
796
|
+
return;
|
|
797
|
+
case "if":
|
|
798
|
+
emitIf(stmt, ctx);
|
|
799
|
+
return;
|
|
800
|
+
case "for":
|
|
801
|
+
emitFor(stmt, ctx);
|
|
802
|
+
return;
|
|
803
|
+
case "while":
|
|
804
|
+
emitWhile(stmt, ctx);
|
|
805
|
+
return;
|
|
806
|
+
case "loop":
|
|
807
|
+
builder.appendNext("loop ");
|
|
808
|
+
emitBlock(stmt.body, ctx);
|
|
809
|
+
return;
|
|
810
|
+
case "continuing":
|
|
811
|
+
builder.appendNext("continuing ");
|
|
812
|
+
emitBlock(stmt.body, ctx);
|
|
813
|
+
return;
|
|
814
|
+
case "switch":
|
|
815
|
+
emitSwitch(stmt, ctx);
|
|
816
|
+
return;
|
|
817
|
+
case "return":
|
|
818
|
+
builder.appendNext("return");
|
|
819
|
+
if (stmt.value) {
|
|
820
|
+
builder.appendNext(" ");
|
|
821
|
+
emitExpression(stmt.value, ctx);
|
|
822
|
+
}
|
|
823
|
+
return;
|
|
824
|
+
case "break":
|
|
825
|
+
builder.appendNext("break");
|
|
826
|
+
if (stmt.condition) {
|
|
827
|
+
builder.appendNext(" if ");
|
|
828
|
+
emitExpression(stmt.condition, ctx);
|
|
829
|
+
}
|
|
830
|
+
return;
|
|
831
|
+
case "continue":
|
|
832
|
+
builder.appendNext("continue");
|
|
833
|
+
return;
|
|
834
|
+
case "discard":
|
|
835
|
+
builder.appendNext("discard");
|
|
836
|
+
return;
|
|
837
|
+
case "assign":
|
|
838
|
+
emitAssign(stmt, ctx);
|
|
839
|
+
return;
|
|
840
|
+
case "increment":
|
|
841
|
+
emitExpression(stmt.target, ctx);
|
|
842
|
+
builder.appendNext("++");
|
|
843
|
+
return;
|
|
844
|
+
case "decrement":
|
|
845
|
+
emitExpression(stmt.target, ctx);
|
|
846
|
+
builder.appendNext("--");
|
|
847
|
+
return;
|
|
848
|
+
case "call":
|
|
849
|
+
emitExpression(stmt.call, ctx);
|
|
850
|
+
return;
|
|
851
|
+
case "empty":
|
|
852
|
+
return;
|
|
853
|
+
default:
|
|
854
|
+
assertUnreachable(stmt);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/** Emit a var's `<address_space[, access_mode]>` enumerant template. */
|
|
859
|
+
function emitVarTemplate(template: NameElem[], ctx: EmitContext): void {
|
|
860
|
+
const builder = ctx.srcBuilder;
|
|
861
|
+
builder.appendNext("<");
|
|
862
|
+
template.forEach((name, i) => {
|
|
863
|
+
if (i > 0) builder.appendNext(", ");
|
|
864
|
+
emitName(name, ctx);
|
|
865
|
+
});
|
|
866
|
+
builder.appendNext(">");
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/** Emit a ` = init` clause, if present. */
|
|
870
|
+
function emitInit(init: ExpressionElem | undefined, ctx: EmitContext): void {
|
|
871
|
+
if (!init) return;
|
|
872
|
+
ctx.srcBuilder.appendNext(" = ");
|
|
873
|
+
emitExpression(init, ctx);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/** if / else-if / else: a nested IfElem prints as `else if`, a BlockElem as `else`. */
|
|
877
|
+
function emitIf(e: IfElem, ctx: EmitContext): void {
|
|
878
|
+
const builder = ctx.srcBuilder;
|
|
879
|
+
builder.appendNext("if ");
|
|
880
|
+
emitExpression(e.condition, ctx);
|
|
881
|
+
builder.appendNext(" ");
|
|
882
|
+
emitBlock(e.body, ctx);
|
|
883
|
+
if (e.else) {
|
|
884
|
+
builder.appendNext(" else ");
|
|
885
|
+
if (e.else.kind === "if") emitIf(e.else, ctx);
|
|
886
|
+
else emitBlock(e.else, ctx);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/** for (init; condition; update) { ... }. The init takes a ';' like any
|
|
891
|
+
* statement; the update is the last clause before ')', so it gets none. */
|
|
892
|
+
function emitFor(e: ForElem, ctx: EmitContext): void {
|
|
893
|
+
const builder = ctx.srcBuilder;
|
|
894
|
+
builder.appendNext("for (");
|
|
895
|
+
if (e.init) emitCoreSemi(e.init, ctx);
|
|
896
|
+
else builder.appendNext(";");
|
|
897
|
+
builder.appendNext(" ");
|
|
898
|
+
if (e.condition) emitExpression(e.condition, ctx);
|
|
899
|
+
builder.appendNext("; ");
|
|
900
|
+
if (e.update) emitStatementCore(e.update, ctx);
|
|
901
|
+
builder.appendNext(") ");
|
|
902
|
+
emitBlock(e.body, ctx);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function emitWhile(e: WhileElem, ctx: EmitContext): void {
|
|
906
|
+
ctx.srcBuilder.appendNext("while ");
|
|
907
|
+
emitExpression(e.condition, ctx);
|
|
908
|
+
ctx.srcBuilder.appendNext(" ");
|
|
909
|
+
emitBlock(e.body, ctx);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function emitSwitch(e: SwitchElem, ctx: EmitContext): void {
|
|
913
|
+
const builder = ctx.srcBuilder;
|
|
914
|
+
builder.appendNext("switch ");
|
|
915
|
+
emitExpression(e.selector, ctx);
|
|
916
|
+
builder.appendNext(" ");
|
|
917
|
+
emitAttributes(e.bodyAttributes, ctx);
|
|
918
|
+
builder.appendNext("{");
|
|
919
|
+
const inner = childIndent(ctx);
|
|
920
|
+
for (const clause of filterValidElements(e.clauses, ctx.conditions)) {
|
|
921
|
+
emitSwitchClause(clause, inner);
|
|
922
|
+
}
|
|
923
|
+
newLine(ctx);
|
|
924
|
+
builder.appendNext("}");
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/** lhs op rhs, with the phony target printed as `_`. */
|
|
928
|
+
function emitAssign(e: AssignElem, ctx: EmitContext): void {
|
|
929
|
+
if (e.lhs.kind === "phony") {
|
|
930
|
+
ctx.srcBuilder.add("_", e.lhs.span[0], e.lhs.span[1]);
|
|
931
|
+
} else {
|
|
932
|
+
emitExpression(e.lhs, ctx);
|
|
933
|
+
}
|
|
934
|
+
const [start, end] = e.op.span;
|
|
935
|
+
ctx.srcBuilder.add(` ${e.op.value} `, start, end);
|
|
936
|
+
emitExpression(e.rhs, ctx);
|
|
581
937
|
}
|