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/dist/index.js
CHANGED
|
@@ -283,12 +283,13 @@ const isBrowser = "document" in globalThis;
|
|
|
283
283
|
/** Throw an error with an embedded source map so that browser users can
|
|
284
284
|
* click on the error in the browser debug console and see the wesl source code. */
|
|
285
285
|
function throwClickableError(params) {
|
|
286
|
-
const { url, text, lineNumber, lineColumn, length, error } = params;
|
|
286
|
+
const { url, text, lineNumber, lineColumn, length, offset, error } = params;
|
|
287
287
|
const weslLocation = {
|
|
288
288
|
file: url,
|
|
289
289
|
line: lineNumber,
|
|
290
290
|
column: lineColumn,
|
|
291
|
-
length
|
|
291
|
+
length,
|
|
292
|
+
offset
|
|
292
293
|
};
|
|
293
294
|
error.weslLocation = weslLocation;
|
|
294
295
|
if (!isBrowser) throw error;
|
|
@@ -349,6 +350,7 @@ function failIdentElem(identElem, msg = "") {
|
|
|
349
350
|
lineNumber,
|
|
350
351
|
lineColumn,
|
|
351
352
|
length: end - start,
|
|
353
|
+
offset: start,
|
|
352
354
|
error: new Error(detailedMessage)
|
|
353
355
|
});
|
|
354
356
|
}
|
|
@@ -497,6 +499,120 @@ function childIdent(child) {
|
|
|
497
499
|
return !childScope(child);
|
|
498
500
|
}
|
|
499
501
|
//#endregion
|
|
502
|
+
//#region src/LinkerUtil.ts
|
|
503
|
+
/** A module's top-level declarations of a given kind, narrowed to that elem type. */
|
|
504
|
+
function declsOfKind(moduleElem, kind) {
|
|
505
|
+
return moduleElem.decls.filter((e) => e.kind === kind);
|
|
506
|
+
}
|
|
507
|
+
/** Visit an elem and all its descendants, parent before children (pre-order). */
|
|
508
|
+
function visitAst(elem, visitor) {
|
|
509
|
+
visitor(elem);
|
|
510
|
+
for (const child of childElems(elem)) visitAst(child, visitor);
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Child elems of any AST node: the typed structural fields (attributes plus
|
|
514
|
+
* body / condition / decls / ...) in source order. Returns [] for leaf elems.
|
|
515
|
+
*/
|
|
516
|
+
function childElems(elem) {
|
|
517
|
+
return [...elem.attributes ?? [], ...structuralFields(elem)];
|
|
518
|
+
}
|
|
519
|
+
function identElemLog(identElem, ...messages) {
|
|
520
|
+
srcLog(identElem.srcModule.src, [identElem.start, identElem.end], ...messages);
|
|
521
|
+
}
|
|
522
|
+
/** The child elems held in a node's typed fields, in source order (attributes
|
|
523
|
+
* are added separately by {@link childElems}). Empty for leaf kinds. The
|
|
524
|
+
* switch is exhaustive: a new elem kind without a case fails to compile in the
|
|
525
|
+
* default, so children can't be silently dropped. */
|
|
526
|
+
function structuralFields(elem) {
|
|
527
|
+
switch (elem.kind) {
|
|
528
|
+
case "module": return elem.decls;
|
|
529
|
+
case "var":
|
|
530
|
+
case "gvar": return [
|
|
531
|
+
...elem.template ?? [],
|
|
532
|
+
elem.name,
|
|
533
|
+
...elem.init ? [elem.init] : []
|
|
534
|
+
];
|
|
535
|
+
case "let":
|
|
536
|
+
case "const":
|
|
537
|
+
case "override": return [elem.name, ...elem.init ? [elem.init] : []];
|
|
538
|
+
case "alias": return [elem.name, elem.typeRef];
|
|
539
|
+
case "assert": return [elem.expression];
|
|
540
|
+
case "struct": return [elem.name, ...elem.members];
|
|
541
|
+
case "member": return [elem.name, elem.typeRef];
|
|
542
|
+
case "type": return [elem.name.refIdentElem, ...elem.templateParams ?? []];
|
|
543
|
+
case "expression": return [elem.expression];
|
|
544
|
+
case "binary-expression": return [elem.left, elem.right];
|
|
545
|
+
case "unary-expression":
|
|
546
|
+
case "parenthesized-expression": return [elem.expression];
|
|
547
|
+
case "component-expression": return [elem.base, elem.access];
|
|
548
|
+
case "component-member-expression": return [elem.base, elem.access];
|
|
549
|
+
case "call-expression": return [
|
|
550
|
+
elem.function,
|
|
551
|
+
...elem.templateArgs ?? [],
|
|
552
|
+
...elem.arguments
|
|
553
|
+
];
|
|
554
|
+
case "param": return [elem.name];
|
|
555
|
+
case "typeDecl": return elem.typeRef ? [elem.decl, elem.typeRef] : [elem.decl];
|
|
556
|
+
case "fn": return [
|
|
557
|
+
elem.name,
|
|
558
|
+
...elem.params,
|
|
559
|
+
...elem.returnType ? [elem.returnType] : [],
|
|
560
|
+
elem.body
|
|
561
|
+
];
|
|
562
|
+
case "block": return elem.body;
|
|
563
|
+
case "if": return [
|
|
564
|
+
elem.condition,
|
|
565
|
+
elem.body,
|
|
566
|
+
...elem.else ? [elem.else] : []
|
|
567
|
+
];
|
|
568
|
+
case "for": return [
|
|
569
|
+
elem.init,
|
|
570
|
+
elem.condition,
|
|
571
|
+
elem.update,
|
|
572
|
+
elem.body
|
|
573
|
+
].filter(isDefined);
|
|
574
|
+
case "while": return [elem.condition, elem.body];
|
|
575
|
+
case "loop":
|
|
576
|
+
case "continuing": return [elem.body];
|
|
577
|
+
case "switch": return [
|
|
578
|
+
elem.selector,
|
|
579
|
+
...elem.bodyAttributes ?? [],
|
|
580
|
+
...elem.clauses
|
|
581
|
+
];
|
|
582
|
+
case "switch-clause": return [...exprSelectors(elem.selectors), elem.body];
|
|
583
|
+
case "return": return elem.value ? [elem.value] : [];
|
|
584
|
+
case "break": return elem.condition ? [elem.condition] : [];
|
|
585
|
+
case "assign": return elem.lhs.kind === "phony" ? [elem.rhs] : [elem.lhs, elem.rhs];
|
|
586
|
+
case "increment":
|
|
587
|
+
case "decrement": return [elem.target];
|
|
588
|
+
case "call": return [elem.call];
|
|
589
|
+
case "continue":
|
|
590
|
+
case "discard":
|
|
591
|
+
case "empty": return [];
|
|
592
|
+
case "do": return [
|
|
593
|
+
elem.name,
|
|
594
|
+
...elem.params,
|
|
595
|
+
elem.body
|
|
596
|
+
];
|
|
597
|
+
case "import":
|
|
598
|
+
case "directive":
|
|
599
|
+
case "attribute":
|
|
600
|
+
case "name":
|
|
601
|
+
case "literal":
|
|
602
|
+
case "ref":
|
|
603
|
+
case "decl":
|
|
604
|
+
case "synthetic": return [];
|
|
605
|
+
default: return [];
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
function isDefined(value) {
|
|
609
|
+
return value !== void 0;
|
|
610
|
+
}
|
|
611
|
+
/** Drop the `"default"` sentinel, keeping only real case-selector expressions. */
|
|
612
|
+
function exprSelectors(selectors) {
|
|
613
|
+
return selectors.filter((s) => s !== "default");
|
|
614
|
+
}
|
|
615
|
+
//#endregion
|
|
500
616
|
//#region src/StandardTypes.ts
|
|
501
617
|
const stdFns = `bitcast all any select arrayLength
|
|
502
618
|
abs acos acosh asin asinh atan atanh atan2 ceil clamp cos cosh
|
|
@@ -598,6 +714,22 @@ function stdEnumerant(name) {
|
|
|
598
714
|
}
|
|
599
715
|
//#endregion
|
|
600
716
|
//#region src/LowerAndEmit.ts
|
|
717
|
+
/** Statement kinds that emitStatement must not follow with a ';': compound
|
|
718
|
+
* statements take none, locals already carry their own, empty needs none. */
|
|
719
|
+
const noSemicolon = new Set([
|
|
720
|
+
"block",
|
|
721
|
+
"if",
|
|
722
|
+
"for",
|
|
723
|
+
"while",
|
|
724
|
+
"loop",
|
|
725
|
+
"continuing",
|
|
726
|
+
"switch",
|
|
727
|
+
"empty",
|
|
728
|
+
"var",
|
|
729
|
+
"let",
|
|
730
|
+
"const",
|
|
731
|
+
"assert"
|
|
732
|
+
]);
|
|
601
733
|
/** Traverse the AST, starting from root elements, emitting WGSL for each. */
|
|
602
734
|
function lowerAndEmit(params) {
|
|
603
735
|
const { srcBuilder, rootElems, conditions } = params;
|
|
@@ -605,17 +737,53 @@ function lowerAndEmit(params) {
|
|
|
605
737
|
const emitContext = {
|
|
606
738
|
conditions,
|
|
607
739
|
srcBuilder,
|
|
608
|
-
extracting
|
|
740
|
+
extracting,
|
|
741
|
+
indent: 0
|
|
609
742
|
};
|
|
610
743
|
const validElements = skipConditionalFiltering ? rootElems : filterValidElements(rootElems, conditions);
|
|
611
744
|
for (const e of validElements) lowerAndEmitElem(e, emitContext);
|
|
612
745
|
}
|
|
746
|
+
/** Format a diagnostic control as "(severity, rule)" for @diagnostic text. */
|
|
747
|
+
function diagnosticControlToString(severity, rule) {
|
|
748
|
+
const ruleStr = rule[0].name + (rule[1] !== null ? "." + rule[1].name : "");
|
|
749
|
+
return `(${severity.name}, ${ruleStr})`;
|
|
750
|
+
}
|
|
751
|
+
/** Render an expression back to WGSL source text (template args elided as <...>). */
|
|
752
|
+
function expressionToString(elem) {
|
|
753
|
+
const { kind } = elem;
|
|
754
|
+
switch (kind) {
|
|
755
|
+
case "binary-expression": {
|
|
756
|
+
const left = expressionToString(elem.left);
|
|
757
|
+
const right = expressionToString(elem.right);
|
|
758
|
+
return `${left} ${elem.operator.value} ${right}`;
|
|
759
|
+
}
|
|
760
|
+
case "unary-expression": return `${elem.operator.value}${expressionToString(elem.expression)}`;
|
|
761
|
+
case "ref": return elem.ident.originalName;
|
|
762
|
+
case "literal": return elem.value;
|
|
763
|
+
case "parenthesized-expression": return `(${expressionToString(elem.expression)})`;
|
|
764
|
+
case "component-expression": return `${expressionToString(elem.base)}[${elem.access}]`;
|
|
765
|
+
case "component-member-expression": return `${expressionToString(elem.base)}.${elem.access}`;
|
|
766
|
+
case "call-expression": {
|
|
767
|
+
const fn = elem.function;
|
|
768
|
+
return `${fn.kind === "ref" ? fn.ident.originalName : fn.name.originalName}${elem.templateArgs ? `<...>` : ""}(${elem.arguments.map(expressionToString).join(", ")})`;
|
|
769
|
+
}
|
|
770
|
+
case "type": return elem.name.originalName;
|
|
771
|
+
default: assertUnreachable(kind);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
/** Trace through refersTo links until we find the declaration. */
|
|
775
|
+
function findDecl(ident) {
|
|
776
|
+
let i = ident;
|
|
777
|
+
do {
|
|
778
|
+
if (i.kind === "decl") return i;
|
|
779
|
+
i = i.refersTo;
|
|
780
|
+
} while (i);
|
|
781
|
+
throw new Error(`unresolved identifer: ${ident.originalName}`);
|
|
782
|
+
}
|
|
613
783
|
function lowerAndEmitElem(e, ctx) {
|
|
614
784
|
switch (e.kind) {
|
|
615
785
|
case "import": return;
|
|
616
|
-
case "
|
|
617
|
-
emitText(e, ctx);
|
|
618
|
-
return;
|
|
786
|
+
case "do": return;
|
|
619
787
|
case "name":
|
|
620
788
|
emitName(e, ctx);
|
|
621
789
|
return;
|
|
@@ -638,25 +806,46 @@ function lowerAndEmitElem(e, ctx) {
|
|
|
638
806
|
emitExpression(e, ctx);
|
|
639
807
|
return;
|
|
640
808
|
case "param":
|
|
809
|
+
emitAttributes(e.attributes, ctx);
|
|
810
|
+
emitTypedDecl(e.name, ctx);
|
|
811
|
+
return;
|
|
641
812
|
case "typeDecl":
|
|
813
|
+
emitTypedDecl(e, ctx);
|
|
814
|
+
return;
|
|
642
815
|
case "member":
|
|
643
|
-
|
|
816
|
+
emitMember(e, ctx);
|
|
817
|
+
return;
|
|
644
818
|
case "expression":
|
|
645
|
-
|
|
819
|
+
emitExpression(e.expression, ctx);
|
|
820
|
+
return;
|
|
646
821
|
case "switch-clause":
|
|
647
|
-
|
|
822
|
+
emitSwitchClause(e, ctx);
|
|
648
823
|
return;
|
|
649
|
-
case "
|
|
650
|
-
|
|
824
|
+
case "type":
|
|
825
|
+
emitTypeRef(e, ctx);
|
|
651
826
|
return;
|
|
652
827
|
case "module":
|
|
653
828
|
emitModule(e, ctx);
|
|
654
829
|
return;
|
|
655
830
|
case "var":
|
|
656
831
|
case "let":
|
|
657
|
-
case "
|
|
832
|
+
case "block":
|
|
833
|
+
case "if":
|
|
834
|
+
case "for":
|
|
835
|
+
case "while":
|
|
836
|
+
case "loop":
|
|
658
837
|
case "continuing":
|
|
659
|
-
|
|
838
|
+
case "switch":
|
|
839
|
+
case "return":
|
|
840
|
+
case "break":
|
|
841
|
+
case "continue":
|
|
842
|
+
case "discard":
|
|
843
|
+
case "assign":
|
|
844
|
+
case "increment":
|
|
845
|
+
case "decrement":
|
|
846
|
+
case "call":
|
|
847
|
+
case "empty":
|
|
848
|
+
emitStatement(e, ctx);
|
|
660
849
|
return;
|
|
661
850
|
case "override":
|
|
662
851
|
case "const":
|
|
@@ -667,52 +856,131 @@ function lowerAndEmitElem(e, ctx) {
|
|
|
667
856
|
return;
|
|
668
857
|
case "fn":
|
|
669
858
|
emitRootElemNl(ctx);
|
|
859
|
+
emitRootLeading(e, ctx);
|
|
670
860
|
emitFn(e, ctx);
|
|
861
|
+
emitTrailingComments(e, ctx);
|
|
671
862
|
return;
|
|
672
863
|
case "struct":
|
|
673
864
|
emitRootElemNl(ctx);
|
|
865
|
+
emitRootLeading(e, ctx);
|
|
674
866
|
emitStruct(e, ctx);
|
|
867
|
+
emitTrailingComments(e, ctx);
|
|
675
868
|
return;
|
|
676
869
|
case "attribute":
|
|
677
870
|
emitAttribute(e, ctx);
|
|
678
871
|
return;
|
|
679
872
|
case "directive":
|
|
873
|
+
ctx.srcBuilder.addNl();
|
|
874
|
+
emitRootLeading(e, ctx);
|
|
680
875
|
emitDirective(e, ctx);
|
|
876
|
+
emitTrailingComments(e, ctx);
|
|
681
877
|
return;
|
|
682
878
|
default: assertUnreachable(e);
|
|
683
879
|
}
|
|
684
880
|
}
|
|
685
|
-
function
|
|
686
|
-
|
|
881
|
+
function emitName(e, ctx) {
|
|
882
|
+
ctx.srcBuilder.add(e.name, e.start, e.end);
|
|
687
883
|
}
|
|
688
|
-
function
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
884
|
+
function emitSynthetic(e, ctx) {
|
|
885
|
+
const { text } = e;
|
|
886
|
+
ctx.srcBuilder.addSynthetic(text, text, 0, text.length);
|
|
887
|
+
}
|
|
888
|
+
function emitRefIdent(e, ctx) {
|
|
889
|
+
if (e.ident.std) ctx.srcBuilder.add(e.ident.originalName, e.start, e.end);
|
|
890
|
+
else ctx.srcBuilder.add(displayName(findDecl(e.ident)), e.start, e.end);
|
|
891
|
+
}
|
|
892
|
+
function emitDeclIdent(e, ctx) {
|
|
893
|
+
ctx.srcBuilder.add(displayName(e.ident), e.start, e.end);
|
|
894
|
+
}
|
|
895
|
+
/** Emit an expression with any comments attached to it. Comments inside an
|
|
896
|
+
* expression (e.g. `foo(1, /* x *\/ 2)`) ride on the relevant sub-node and are
|
|
897
|
+
* emitted inline around it here. */
|
|
898
|
+
function emitExpression(e, ctx) {
|
|
899
|
+
emitInlineLeading(e, ctx);
|
|
900
|
+
emitExpressionCore(e, ctx);
|
|
901
|
+
emitInlineTrailing(e, ctx);
|
|
902
|
+
}
|
|
903
|
+
function emitAttributes(attributes, ctx) {
|
|
904
|
+
attributes?.forEach((a) => {
|
|
905
|
+
emitInlineLeading(a, ctx);
|
|
906
|
+
if (emitAttribute(a, ctx)) {
|
|
907
|
+
emitInlineTrailing(a, ctx);
|
|
908
|
+
ctx.srcBuilder.add(" ", a.start, a.end);
|
|
693
909
|
}
|
|
694
|
-
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
/** Emit a declared identifier with its optional `: type` annotation. */
|
|
913
|
+
function emitTypedDecl(name, ctx) {
|
|
914
|
+
emitInlineLeading(name, ctx);
|
|
915
|
+
emitDeclIdent(name.decl, ctx);
|
|
916
|
+
if (name.typeRef) {
|
|
917
|
+
ctx.srcBuilder.appendNext(": ");
|
|
918
|
+
emitTypeRef(name.typeRef, ctx);
|
|
919
|
+
}
|
|
920
|
+
emitInlineTrailing(name, ctx);
|
|
921
|
+
}
|
|
922
|
+
/** Emit a struct member from its typed fields: `[attrs] name: type`. */
|
|
923
|
+
function emitMember(member, ctx) {
|
|
924
|
+
emitAttributes(member.attributes, ctx);
|
|
925
|
+
emitName(member.name, ctx);
|
|
926
|
+
ctx.srcBuilder.appendNext(": ");
|
|
927
|
+
emitTypeRef(member.typeRef, ctx);
|
|
928
|
+
}
|
|
929
|
+
/** A `case sel, ...:` or `default:` clause with its `{ ... }` body. The selector
|
|
930
|
+
* colon is optional in WGSL but kept here as the canonical form. */
|
|
931
|
+
function emitSwitchClause(e, ctx) {
|
|
932
|
+
const builder = ctx.srcBuilder;
|
|
933
|
+
emitLeadingComments(e, ctx);
|
|
934
|
+
newLine(ctx);
|
|
935
|
+
emitAttributes(e.attributes, ctx);
|
|
936
|
+
if (e.selectors.length === 1 && e.selectors[0] === "default") builder.appendNext("default");
|
|
937
|
+
else {
|
|
938
|
+
builder.appendNext("case ");
|
|
939
|
+
e.selectors.forEach((sel, i) => {
|
|
940
|
+
if (i > 0) builder.appendNext(", ");
|
|
941
|
+
if (sel === "default") builder.appendNext("default");
|
|
942
|
+
else emitExpression(sel, ctx);
|
|
943
|
+
});
|
|
695
944
|
}
|
|
945
|
+
builder.appendNext(": ");
|
|
946
|
+
emitBlock(e.body, ctx);
|
|
947
|
+
emitTrailingComments(e, ctx);
|
|
948
|
+
}
|
|
949
|
+
/** Emit a type reference structurally: name plus an optional <...> arg list. */
|
|
950
|
+
function emitTypeRef(e, ctx) {
|
|
951
|
+
emitRefIdent(e.name.refIdentElem, ctx);
|
|
952
|
+
if (e.templateParams) emitTemplateArgs(e.templateParams, ctx);
|
|
696
953
|
}
|
|
697
|
-
function
|
|
698
|
-
|
|
699
|
-
|
|
954
|
+
function emitModule(e, ctx) {
|
|
955
|
+
const validElements = filterValidElements(e.decls, ctx.conditions);
|
|
956
|
+
for (const child of validElements) lowerAndEmitElem(child, ctx);
|
|
957
|
+
}
|
|
958
|
+
/** Emit one statement on its own line, with attached leading/trailing comments. */
|
|
959
|
+
function emitStatement(stmt, ctx) {
|
|
960
|
+
emitLeadingComments(stmt, ctx);
|
|
961
|
+
newLine(ctx);
|
|
962
|
+
emitCoreSemi(stmt, ctx);
|
|
963
|
+
emitTrailingComments(stmt, ctx);
|
|
700
964
|
}
|
|
701
965
|
function emitRootDecl(e, ctx) {
|
|
702
966
|
emitRootElemNl(ctx);
|
|
703
|
-
|
|
704
|
-
|
|
967
|
+
emitRootLeading(e, ctx);
|
|
968
|
+
emitValueDecl(e, ctx);
|
|
969
|
+
emitTrailingComments(e, ctx);
|
|
705
970
|
}
|
|
706
971
|
/** Emit newlines between root elements. */
|
|
707
972
|
function emitRootElemNl(ctx) {
|
|
708
973
|
ctx.srcBuilder.addNl();
|
|
709
974
|
ctx.srcBuilder.addNl();
|
|
710
975
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
function
|
|
715
|
-
|
|
976
|
+
/** Leading comments for a root declaration, each on its own line. The inter-decl
|
|
977
|
+
* spacing already left us at a fresh line, so a newline follows each comment
|
|
978
|
+
* (rather than preceding it as in emitLeadingComments). */
|
|
979
|
+
function emitRootLeading(e, ctx) {
|
|
980
|
+
for (const c of e.commentsBefore ?? []) {
|
|
981
|
+
emitComment(c, ctx);
|
|
982
|
+
newLine(ctx);
|
|
983
|
+
}
|
|
716
984
|
}
|
|
717
985
|
/** Emit function explicitly to control commas between conditional parameters. */
|
|
718
986
|
function emitFn(e, ctx) {
|
|
@@ -720,27 +988,35 @@ function emitFn(e, ctx) {
|
|
|
720
988
|
const { conditions, srcBuilder: builder } = ctx;
|
|
721
989
|
emitAttributes(attributes, ctx);
|
|
722
990
|
builder.add("fn ", name.start - 3, name.start);
|
|
991
|
+
emitInlineLeading(name, ctx);
|
|
723
992
|
emitDeclIdent(name, ctx);
|
|
993
|
+
emitInlineTrailing(name, ctx);
|
|
724
994
|
builder.appendNext("(");
|
|
725
995
|
const validParams = filterValidElements(params, conditions);
|
|
726
996
|
validParams.forEach((p, i) => {
|
|
727
|
-
|
|
728
|
-
|
|
997
|
+
emitInlineLeading(p, ctx);
|
|
998
|
+
emitAttributes(p.attributes, ctx);
|
|
999
|
+
emitTypedDecl(p.name, ctx);
|
|
1000
|
+
emitInlineTrailing(p, ctx);
|
|
729
1001
|
if (i < validParams.length - 1) builder.appendNext(", ");
|
|
730
1002
|
});
|
|
731
1003
|
builder.appendNext(") ");
|
|
732
1004
|
if (returnType) {
|
|
733
1005
|
builder.appendNext("-> ");
|
|
734
1006
|
emitAttributes(returnAttributes, ctx);
|
|
735
|
-
|
|
1007
|
+
emitInlineLeading(returnType, ctx);
|
|
1008
|
+
emitTypeRef(returnType, ctx);
|
|
736
1009
|
builder.appendNext(" ");
|
|
737
1010
|
}
|
|
738
|
-
|
|
1011
|
+
emitBlock(body, ctx);
|
|
739
1012
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1013
|
+
/** Trailing comments: kept on the element's line, after its text. */
|
|
1014
|
+
function emitTrailingComments(e, ctx) {
|
|
1015
|
+
if (!e.commentsAfter) return;
|
|
1016
|
+
for (const c of e.commentsAfter) {
|
|
1017
|
+
ctx.srcBuilder.appendNext(" ");
|
|
1018
|
+
emitComment(c, ctx);
|
|
1019
|
+
}
|
|
744
1020
|
}
|
|
745
1021
|
/** Emit structs explicitly to control commas between conditional members. */
|
|
746
1022
|
function emitStruct(e, ctx) {
|
|
@@ -754,125 +1030,22 @@ function emitStruct(e, ctx) {
|
|
|
754
1030
|
}
|
|
755
1031
|
emitAttributes(attributes, ctx);
|
|
756
1032
|
srcBuilder.add("struct ", start, name.start);
|
|
1033
|
+
emitInlineLeading(name, ctx);
|
|
757
1034
|
emitDeclIdent(name, ctx);
|
|
758
|
-
|
|
1035
|
+
emitInlineTrailing(name, ctx);
|
|
1036
|
+
if (validLength === 1 && !hasComments(validMembers[0])) {
|
|
759
1037
|
srcBuilder.appendNext(" { ");
|
|
760
|
-
|
|
1038
|
+
emitMember(validMembers[0], ctx);
|
|
761
1039
|
srcBuilder.appendNext(" }");
|
|
762
1040
|
srcBuilder.addNl();
|
|
763
1041
|
} else {
|
|
764
1042
|
srcBuilder.appendNext(" {");
|
|
765
1043
|
srcBuilder.addNl();
|
|
766
|
-
|
|
767
|
-
srcBuilder.appendNext(" ");
|
|
768
|
-
emitContentsNoWs(m, ctx);
|
|
769
|
-
srcBuilder.appendNext(",");
|
|
770
|
-
srcBuilder.addNl();
|
|
771
|
-
});
|
|
1044
|
+
for (const m of validMembers) emitMemberLine(m, ctx);
|
|
772
1045
|
srcBuilder.appendNext("}");
|
|
773
1046
|
srcBuilder.addNl();
|
|
774
1047
|
}
|
|
775
1048
|
}
|
|
776
|
-
function warnEmptyStruct(e) {
|
|
777
|
-
const { name, members } = e;
|
|
778
|
-
const condStr = members.length ? "(with current conditions)" : "";
|
|
779
|
-
failIdentElem(name, `struct '${name.ident.originalName}' has no members ${condStr}`);
|
|
780
|
-
}
|
|
781
|
-
function emitSynthetic(e, ctx) {
|
|
782
|
-
const { text } = e;
|
|
783
|
-
ctx.srcBuilder.addSynthetic(text, text, 0, text.length);
|
|
784
|
-
}
|
|
785
|
-
function emitContents(elem, ctx) {
|
|
786
|
-
const validElements = filterValidElements(elem.contents, ctx.conditions);
|
|
787
|
-
for (const e of validElements) lowerAndEmitElem(e, ctx);
|
|
788
|
-
}
|
|
789
|
-
/** Emit contents with leading/trailing whitespace trimming (V2 parser). */
|
|
790
|
-
function emitContentsWithTrimming(elem, ctx) {
|
|
791
|
-
const validElements = filterValidElements(elem.contents, ctx.conditions);
|
|
792
|
-
const firstEmit = validElements.findIndex((e) => !isConditionalAttr(e));
|
|
793
|
-
const lastEmit = validElements.findLastIndex((e) => !isConditionalAttr(e));
|
|
794
|
-
validElements.forEach((elem, i) => {
|
|
795
|
-
if (elem.kind === "text") {
|
|
796
|
-
let text = elem.srcModule.src.slice(elem.start, elem.end);
|
|
797
|
-
if (i === firstEmit) text = text.trimStart();
|
|
798
|
-
if (i === lastEmit) text = text.trimEnd();
|
|
799
|
-
if (text) ctx.srcBuilder.add(text, elem.start, elem.end);
|
|
800
|
-
} else lowerAndEmitElem(elem, ctx);
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
function isConditionalAttr(e) {
|
|
804
|
-
if (e.kind !== "attribute") return false;
|
|
805
|
-
const { kind } = e.attribute;
|
|
806
|
-
return kind === "@if" || kind === "@elif" || kind === "@else";
|
|
807
|
-
}
|
|
808
|
-
/** Emit contents without whitespace. */
|
|
809
|
-
function emitContentsNoWs(elem, ctx) {
|
|
810
|
-
filterValidElements(elem.contents, ctx.conditions).forEach((e) => {
|
|
811
|
-
if (e.kind === "text") {
|
|
812
|
-
const { srcModule, start, end } = e;
|
|
813
|
-
if (srcModule.src.slice(start, end).trim() === "") return;
|
|
814
|
-
}
|
|
815
|
-
lowerAndEmitElem(e, ctx);
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
function emitRefIdent(e, ctx) {
|
|
819
|
-
if (e.ident.std) ctx.srcBuilder.add(e.ident.originalName, e.start, e.end);
|
|
820
|
-
else {
|
|
821
|
-
const mangledName = displayName(findDecl(e.ident));
|
|
822
|
-
ctx.srcBuilder.add(mangledName, e.start, e.end);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
function emitDeclIdent(e, ctx) {
|
|
826
|
-
const mangledName = displayName(e.ident);
|
|
827
|
-
ctx.srcBuilder.add(mangledName, e.start, e.end);
|
|
828
|
-
}
|
|
829
|
-
function emitExpression(e, ctx) {
|
|
830
|
-
const { kind } = e;
|
|
831
|
-
if (kind === "literal") {
|
|
832
|
-
ctx.srcBuilder.add(e.value, e.start, e.end);
|
|
833
|
-
return;
|
|
834
|
-
}
|
|
835
|
-
if (kind === "ref") {
|
|
836
|
-
emitRefIdent(e, ctx);
|
|
837
|
-
return;
|
|
838
|
-
}
|
|
839
|
-
if (kind === "type") {
|
|
840
|
-
emitContents(e, ctx);
|
|
841
|
-
return;
|
|
842
|
-
}
|
|
843
|
-
if (kind === "binary-expression") {
|
|
844
|
-
emitExpression(e.left, ctx);
|
|
845
|
-
ctx.srcBuilder.add(` ${e.operator.value} `, e.operator.span[0], e.operator.span[1]);
|
|
846
|
-
emitExpression(e.right, ctx);
|
|
847
|
-
return;
|
|
848
|
-
}
|
|
849
|
-
if (kind === "unary-expression") {
|
|
850
|
-
ctx.srcBuilder.add(e.operator.value, e.operator.span[0], e.operator.span[1]);
|
|
851
|
-
emitExpression(e.expression, ctx);
|
|
852
|
-
return;
|
|
853
|
-
}
|
|
854
|
-
if (kind === "parenthesized-expression") {
|
|
855
|
-
emitExpression(e.expression, ctx);
|
|
856
|
-
return;
|
|
857
|
-
}
|
|
858
|
-
if (kind === "call-expression") {
|
|
859
|
-
emitExpression(e.function, ctx);
|
|
860
|
-
if (e.templateArgs) for (const targ of e.templateArgs) lowerAndEmitElem(targ, ctx);
|
|
861
|
-
for (const arg of e.arguments) emitExpression(arg, ctx);
|
|
862
|
-
return;
|
|
863
|
-
}
|
|
864
|
-
if (kind === "component-expression") {
|
|
865
|
-
emitExpression(e.base, ctx);
|
|
866
|
-
emitExpression(e.access, ctx);
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
if (kind === "component-member-expression") {
|
|
870
|
-
emitExpression(e.base, ctx);
|
|
871
|
-
if (e.access.kind === "name") ctx.srcBuilder.add(e.access.name, e.access.start, e.access.end);
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
assertUnreachable(kind);
|
|
875
|
-
}
|
|
876
1049
|
function emitAttribute(e, ctx) {
|
|
877
1050
|
const { kind } = e.attribute;
|
|
878
1051
|
if (kind === "@if" || kind === "@elif" || kind === "@else") return false;
|
|
@@ -882,11 +1055,13 @@ function emitAttribute(e, ctx) {
|
|
|
882
1055
|
return true;
|
|
883
1056
|
}
|
|
884
1057
|
if (kind === "@builtin") {
|
|
885
|
-
|
|
1058
|
+
const builtinStr = `@builtin(${e.attribute.param.name})`;
|
|
1059
|
+
ctx.srcBuilder.add(builtinStr, e.start, e.end);
|
|
886
1060
|
return true;
|
|
887
1061
|
}
|
|
888
1062
|
if (kind === "@diagnostic") {
|
|
889
|
-
const
|
|
1063
|
+
const { severity, rule } = e.attribute;
|
|
1064
|
+
const diagStr = `@diagnostic${diagnosticControlToString(severity, rule)}`;
|
|
890
1065
|
ctx.srcBuilder.add(diagStr, e.start, e.end);
|
|
891
1066
|
return true;
|
|
892
1067
|
}
|
|
@@ -897,46 +1072,6 @@ function emitAttribute(e, ctx) {
|
|
|
897
1072
|
}
|
|
898
1073
|
assertUnreachable(kind);
|
|
899
1074
|
}
|
|
900
|
-
function emitStandardAttribute(e, ctx) {
|
|
901
|
-
if (e.attribute.kind !== "@attribute") return;
|
|
902
|
-
const { params } = e.attribute;
|
|
903
|
-
if (!params || params.length === 0) {
|
|
904
|
-
ctx.srcBuilder.add("@" + e.attribute.name, e.start, e.end);
|
|
905
|
-
return;
|
|
906
|
-
}
|
|
907
|
-
ctx.srcBuilder.add("@" + e.attribute.name + "(", e.start, params[0].start);
|
|
908
|
-
for (let i = 0; i < params.length; i++) {
|
|
909
|
-
emitContents(params[i], ctx);
|
|
910
|
-
if (i < params.length - 1) ctx.srcBuilder.add(",", params[i].end, params[i + 1].start);
|
|
911
|
-
}
|
|
912
|
-
ctx.srcBuilder.add(")", params[params.length - 1].end, e.end);
|
|
913
|
-
}
|
|
914
|
-
function diagnosticControlToString(severity, rule) {
|
|
915
|
-
const ruleStr = rule[0].name + (rule[1] !== null ? "." + rule[1].name : "");
|
|
916
|
-
return `(${severity.name}, ${ruleStr})`;
|
|
917
|
-
}
|
|
918
|
-
function expressionToString(elem) {
|
|
919
|
-
const { kind } = elem;
|
|
920
|
-
switch (kind) {
|
|
921
|
-
case "binary-expression": {
|
|
922
|
-
const left = expressionToString(elem.left);
|
|
923
|
-
const right = expressionToString(elem.right);
|
|
924
|
-
return `${left} ${elem.operator.value} ${right}`;
|
|
925
|
-
}
|
|
926
|
-
case "unary-expression": return `${elem.operator.value}${expressionToString(elem.expression)}`;
|
|
927
|
-
case "ref": return elem.ident.originalName;
|
|
928
|
-
case "literal": return elem.value;
|
|
929
|
-
case "parenthesized-expression": return `(${expressionToString(elem.expression)})`;
|
|
930
|
-
case "component-expression": return `${expressionToString(elem.base)}[${elem.access}]`;
|
|
931
|
-
case "component-member-expression": return `${expressionToString(elem.base)}.${elem.access}`;
|
|
932
|
-
case "call-expression": {
|
|
933
|
-
const fn = elem.function;
|
|
934
|
-
return `${fn.kind === "ref" ? fn.ident.originalName : fn.name.originalName}${elem.templateArgs ? `<...>` : ""}(${elem.arguments.map(expressionToString).join(", ")})`;
|
|
935
|
-
}
|
|
936
|
-
case "type": return elem.name.originalName;
|
|
937
|
-
default: assertUnreachable(kind);
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
1075
|
function emitDirective(e, ctx) {
|
|
941
1076
|
const { directive } = e;
|
|
942
1077
|
const { kind } = directive;
|
|
@@ -958,14 +1093,351 @@ function displayName(declIdent) {
|
|
|
958
1093
|
}
|
|
959
1094
|
return declIdent.mangledName || declIdent.originalName;
|
|
960
1095
|
}
|
|
961
|
-
/**
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1096
|
+
/** Leading comments on an inline node: block comments get a trailing space, line
|
|
1097
|
+
* comments force a newline so the following code is not swallowed. */
|
|
1098
|
+
function emitInlineLeading(e, ctx) {
|
|
1099
|
+
for (const c of e.commentsBefore ?? []) {
|
|
1100
|
+
emitComment(c, ctx);
|
|
1101
|
+
if (c.style === "line") newLine(ctx);
|
|
1102
|
+
else ctx.srcBuilder.appendNext(" ");
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
function emitExpressionCore(e, ctx) {
|
|
1106
|
+
const builder = ctx.srcBuilder;
|
|
1107
|
+
switch (e.kind) {
|
|
1108
|
+
case "literal":
|
|
1109
|
+
builder.add(e.value, e.start, e.end);
|
|
1110
|
+
return;
|
|
1111
|
+
case "ref":
|
|
1112
|
+
emitRefIdent(e, ctx);
|
|
1113
|
+
return;
|
|
1114
|
+
case "type":
|
|
1115
|
+
emitTypeRef(e, ctx);
|
|
1116
|
+
return;
|
|
1117
|
+
case "binary-expression": {
|
|
1118
|
+
const [start, end] = e.operator.span;
|
|
1119
|
+
emitExpression(e.left, ctx);
|
|
1120
|
+
builder.add(` ${e.operator.value} `, start, end);
|
|
1121
|
+
emitExpression(e.right, ctx);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
case "unary-expression": {
|
|
1125
|
+
const { value, start, end } = e.operator;
|
|
1126
|
+
builder.add(value, start, end);
|
|
1127
|
+
emitExpression(e.expression, ctx);
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
case "parenthesized-expression":
|
|
1131
|
+
builder.appendNext("(");
|
|
1132
|
+
emitExpression(e.expression, ctx);
|
|
1133
|
+
builder.appendNext(")");
|
|
1134
|
+
return;
|
|
1135
|
+
case "call-expression":
|
|
1136
|
+
emitExpression(e.function, ctx);
|
|
1137
|
+
if (e.templateArgs) emitTemplateArgs(e.templateArgs, ctx);
|
|
1138
|
+
builder.appendNext("(");
|
|
1139
|
+
e.arguments.forEach((arg, i) => {
|
|
1140
|
+
if (i > 0) builder.appendNext(", ");
|
|
1141
|
+
emitExpression(arg, ctx);
|
|
1142
|
+
});
|
|
1143
|
+
builder.appendNext(")");
|
|
1144
|
+
return;
|
|
1145
|
+
case "component-expression":
|
|
1146
|
+
emitExpression(e.base, ctx);
|
|
1147
|
+
builder.appendNext("[");
|
|
1148
|
+
emitExpression(e.access, ctx);
|
|
1149
|
+
builder.appendNext("]");
|
|
1150
|
+
return;
|
|
1151
|
+
case "component-member-expression":
|
|
1152
|
+
emitExpression(e.base, ctx);
|
|
1153
|
+
builder.add("." + e.access.name, e.access.start, e.access.end);
|
|
1154
|
+
return;
|
|
1155
|
+
default: assertUnreachable(e);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
/** Trailing comments on an inline node: a leading space, plus a newline after a
|
|
1159
|
+
* line comment so following code stays off its line. */
|
|
1160
|
+
function emitInlineTrailing(e, ctx) {
|
|
1161
|
+
for (const c of e.commentsAfter ?? []) {
|
|
1162
|
+
ctx.srcBuilder.appendNext(" ");
|
|
1163
|
+
emitComment(c, ctx);
|
|
1164
|
+
if (c.style === "line") newLine(ctx);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
/** Leading comments: each on its own indented line above the element. */
|
|
1168
|
+
function emitLeadingComments(e, ctx) {
|
|
1169
|
+
if (!e.commentsBefore) return;
|
|
1170
|
+
for (const c of e.commentsBefore) {
|
|
1171
|
+
if (c.blankBefore) ctx.srcBuilder.addNl();
|
|
1172
|
+
newLine(ctx);
|
|
1173
|
+
emitComment(c, ctx);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
/** Start a fresh line at the current indent. */
|
|
1177
|
+
function newLine(ctx) {
|
|
1178
|
+
ctx.srcBuilder.addNl();
|
|
1179
|
+
if (ctx.indent > 0) ctx.srcBuilder.appendNext(" ".repeat(ctx.indent));
|
|
1180
|
+
}
|
|
1181
|
+
/** Emit a `{ ... }` block, one statement per indented line. A block with no
|
|
1182
|
+
* statements collapses to `{ }` unless it holds dangling inner comments. */
|
|
1183
|
+
function emitBlock(e, ctx) {
|
|
1184
|
+
emitAttributes(e.attributes, ctx);
|
|
1185
|
+
const stmts = filterValidElements(e.body, ctx.conditions);
|
|
1186
|
+
if (stmts.length === 0 && !e.innerComments?.length) {
|
|
1187
|
+
ctx.srcBuilder.appendNext("{ }");
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
ctx.srcBuilder.appendNext("{");
|
|
1191
|
+
const inner = childIndent(ctx);
|
|
1192
|
+
for (const comment of e.innerComments ?? []) {
|
|
1193
|
+
newLine(inner);
|
|
1194
|
+
emitComment(comment, inner);
|
|
1195
|
+
}
|
|
1196
|
+
for (const stmt of stmts) emitStatement(stmt, inner);
|
|
1197
|
+
newLine(ctx);
|
|
1198
|
+
ctx.srcBuilder.appendNext("}");
|
|
1199
|
+
}
|
|
1200
|
+
/** Emit a comma-separated template argument list: <a, b, c>. */
|
|
1201
|
+
function emitTemplateArgs(args, ctx) {
|
|
1202
|
+
ctx.srcBuilder.appendNext("<");
|
|
1203
|
+
args.forEach((a, i) => {
|
|
1204
|
+
if (i > 0) ctx.srcBuilder.appendNext(", ");
|
|
1205
|
+
emitExpression(a, ctx);
|
|
1206
|
+
});
|
|
1207
|
+
ctx.srcBuilder.appendNext(">");
|
|
1208
|
+
}
|
|
1209
|
+
/** Emit a statement's syntax followed by ';' unless its kind takes none. */
|
|
1210
|
+
function emitCoreSemi(stmt, ctx) {
|
|
1211
|
+
emitStatementCore(stmt, ctx);
|
|
1212
|
+
if (!noSemicolon.has(stmt.kind)) ctx.srcBuilder.appendNext(";");
|
|
1213
|
+
}
|
|
1214
|
+
/** Emit a declaration from its typed fields, including its trailing ';':
|
|
1215
|
+
* `[attrs] var<...> name: T = init;`, `const name = init;`, `override n;`,
|
|
1216
|
+
* `alias name = T;`, `const_assert expr;`. */
|
|
1217
|
+
function emitValueDecl(e, ctx) {
|
|
1218
|
+
emitAttributes(e.attributes, ctx);
|
|
1219
|
+
const builder = ctx.srcBuilder;
|
|
1220
|
+
switch (e.kind) {
|
|
1221
|
+
case "var":
|
|
1222
|
+
case "gvar":
|
|
1223
|
+
builder.appendNext("var");
|
|
1224
|
+
if (e.template) emitVarTemplate(e.template, ctx);
|
|
1225
|
+
builder.appendNext(" ");
|
|
1226
|
+
emitTypedDecl(e.name, ctx);
|
|
1227
|
+
emitInit(e.init, ctx);
|
|
1228
|
+
break;
|
|
1229
|
+
case "let":
|
|
1230
|
+
case "const":
|
|
1231
|
+
case "override":
|
|
1232
|
+
builder.appendNext(`${e.kind} `);
|
|
1233
|
+
emitTypedDecl(e.name, ctx);
|
|
1234
|
+
emitInit(e.init, ctx);
|
|
1235
|
+
break;
|
|
1236
|
+
case "alias":
|
|
1237
|
+
builder.appendNext("alias ");
|
|
1238
|
+
emitInlineLeading(e.name, ctx);
|
|
1239
|
+
emitDeclIdent(e.name, ctx);
|
|
1240
|
+
emitInlineTrailing(e.name, ctx);
|
|
1241
|
+
builder.appendNext(" = ");
|
|
1242
|
+
emitTypeRef(e.typeRef, ctx);
|
|
1243
|
+
break;
|
|
1244
|
+
case "assert":
|
|
1245
|
+
builder.appendNext("const_assert ");
|
|
1246
|
+
emitExpression(e.expression, ctx);
|
|
1247
|
+
break;
|
|
1248
|
+
default: assertUnreachable(e);
|
|
1249
|
+
}
|
|
1250
|
+
builder.appendNext(";");
|
|
1251
|
+
}
|
|
1252
|
+
function emitComment(c, ctx) {
|
|
1253
|
+
ctx.srcBuilder.add(c.srcModule.src.slice(c.start, c.end), c.start, c.end);
|
|
1254
|
+
}
|
|
1255
|
+
function warnEmptyStruct(e) {
|
|
1256
|
+
const { name, members } = e;
|
|
1257
|
+
const condStr = members.length ? "(with current conditions)" : "";
|
|
1258
|
+
failIdentElem(name, `struct '${name.ident.originalName}' has no members ${condStr}`);
|
|
1259
|
+
}
|
|
1260
|
+
/** Whether an element carries any attached comments. */
|
|
1261
|
+
function hasComments(e) {
|
|
1262
|
+
return !!(e.commentsBefore?.length || e.commentsAfter?.length);
|
|
1263
|
+
}
|
|
1264
|
+
/** Emit one struct member on its own line: leading comments above, `name: type,`,
|
|
1265
|
+
* then trailing comments inline. */
|
|
1266
|
+
function emitMemberLine(m, ctx) {
|
|
1267
|
+
const builder = ctx.srcBuilder;
|
|
1268
|
+
for (const c of m.commentsBefore ?? []) {
|
|
1269
|
+
builder.appendNext(" ");
|
|
1270
|
+
emitComment(c, ctx);
|
|
1271
|
+
builder.addNl();
|
|
1272
|
+
}
|
|
1273
|
+
builder.appendNext(" ");
|
|
1274
|
+
emitMember(m, ctx);
|
|
1275
|
+
builder.appendNext(",");
|
|
1276
|
+
for (const c of m.commentsAfter ?? []) {
|
|
1277
|
+
builder.appendNext(" ");
|
|
1278
|
+
emitComment(c, ctx);
|
|
1279
|
+
}
|
|
1280
|
+
builder.addNl();
|
|
1281
|
+
}
|
|
1282
|
+
function emitStandardAttribute(e, ctx) {
|
|
1283
|
+
if (e.attribute.kind !== "@attribute") return;
|
|
1284
|
+
const { params } = e.attribute;
|
|
1285
|
+
if (!params || params.length === 0) {
|
|
1286
|
+
ctx.srcBuilder.add("@" + e.attribute.name, e.start, e.end);
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
ctx.srcBuilder.add("@" + e.attribute.name + "(", e.start, params[0].start);
|
|
1290
|
+
params.forEach((param, i) => {
|
|
1291
|
+
if (i > 0) ctx.srcBuilder.appendNext(", ");
|
|
1292
|
+
emitExpression(param.expression, ctx);
|
|
1293
|
+
});
|
|
1294
|
+
ctx.srcBuilder.add(")", params[params.length - 1].end, e.end);
|
|
1295
|
+
}
|
|
1296
|
+
/** A child context indented one level deeper. */
|
|
1297
|
+
function childIndent(ctx) {
|
|
1298
|
+
return {
|
|
1299
|
+
...ctx,
|
|
1300
|
+
indent: ctx.indent + 1
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
/** Emit a statement's syntax, without surrounding line breaks, ';', or comments. */
|
|
1304
|
+
function emitStatementCore(stmt, ctx) {
|
|
1305
|
+
if (stmt.kind === "var" || stmt.kind === "let" || stmt.kind === "const" || stmt.kind === "assert") {
|
|
1306
|
+
emitValueDecl(stmt, ctx);
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
if (stmt.kind !== "block") emitAttributes(stmt.attributes, ctx);
|
|
1310
|
+
const builder = ctx.srcBuilder;
|
|
1311
|
+
switch (stmt.kind) {
|
|
1312
|
+
case "block":
|
|
1313
|
+
emitBlock(stmt, ctx);
|
|
1314
|
+
return;
|
|
1315
|
+
case "if":
|
|
1316
|
+
emitIf(stmt, ctx);
|
|
1317
|
+
return;
|
|
1318
|
+
case "for":
|
|
1319
|
+
emitFor(stmt, ctx);
|
|
1320
|
+
return;
|
|
1321
|
+
case "while":
|
|
1322
|
+
emitWhile(stmt, ctx);
|
|
1323
|
+
return;
|
|
1324
|
+
case "loop":
|
|
1325
|
+
builder.appendNext("loop ");
|
|
1326
|
+
emitBlock(stmt.body, ctx);
|
|
1327
|
+
return;
|
|
1328
|
+
case "continuing":
|
|
1329
|
+
builder.appendNext("continuing ");
|
|
1330
|
+
emitBlock(stmt.body, ctx);
|
|
1331
|
+
return;
|
|
1332
|
+
case "switch":
|
|
1333
|
+
emitSwitch(stmt, ctx);
|
|
1334
|
+
return;
|
|
1335
|
+
case "return":
|
|
1336
|
+
builder.appendNext("return");
|
|
1337
|
+
if (stmt.value) {
|
|
1338
|
+
builder.appendNext(" ");
|
|
1339
|
+
emitExpression(stmt.value, ctx);
|
|
1340
|
+
}
|
|
1341
|
+
return;
|
|
1342
|
+
case "break":
|
|
1343
|
+
builder.appendNext("break");
|
|
1344
|
+
if (stmt.condition) {
|
|
1345
|
+
builder.appendNext(" if ");
|
|
1346
|
+
emitExpression(stmt.condition, ctx);
|
|
1347
|
+
}
|
|
1348
|
+
return;
|
|
1349
|
+
case "continue":
|
|
1350
|
+
builder.appendNext("continue");
|
|
1351
|
+
return;
|
|
1352
|
+
case "discard":
|
|
1353
|
+
builder.appendNext("discard");
|
|
1354
|
+
return;
|
|
1355
|
+
case "assign":
|
|
1356
|
+
emitAssign(stmt, ctx);
|
|
1357
|
+
return;
|
|
1358
|
+
case "increment":
|
|
1359
|
+
emitExpression(stmt.target, ctx);
|
|
1360
|
+
builder.appendNext("++");
|
|
1361
|
+
return;
|
|
1362
|
+
case "decrement":
|
|
1363
|
+
emitExpression(stmt.target, ctx);
|
|
1364
|
+
builder.appendNext("--");
|
|
1365
|
+
return;
|
|
1366
|
+
case "call":
|
|
1367
|
+
emitExpression(stmt.call, ctx);
|
|
1368
|
+
return;
|
|
1369
|
+
case "empty": return;
|
|
1370
|
+
default: assertUnreachable(stmt);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
/** Emit a var's `<address_space[, access_mode]>` enumerant template. */
|
|
1374
|
+
function emitVarTemplate(template, ctx) {
|
|
1375
|
+
const builder = ctx.srcBuilder;
|
|
1376
|
+
builder.appendNext("<");
|
|
1377
|
+
template.forEach((name, i) => {
|
|
1378
|
+
if (i > 0) builder.appendNext(", ");
|
|
1379
|
+
emitName(name, ctx);
|
|
1380
|
+
});
|
|
1381
|
+
builder.appendNext(">");
|
|
1382
|
+
}
|
|
1383
|
+
/** Emit a ` = init` clause, if present. */
|
|
1384
|
+
function emitInit(init, ctx) {
|
|
1385
|
+
if (!init) return;
|
|
1386
|
+
ctx.srcBuilder.appendNext(" = ");
|
|
1387
|
+
emitExpression(init, ctx);
|
|
1388
|
+
}
|
|
1389
|
+
/** if / else-if / else: a nested IfElem prints as `else if`, a BlockElem as `else`. */
|
|
1390
|
+
function emitIf(e, ctx) {
|
|
1391
|
+
const builder = ctx.srcBuilder;
|
|
1392
|
+
builder.appendNext("if ");
|
|
1393
|
+
emitExpression(e.condition, ctx);
|
|
1394
|
+
builder.appendNext(" ");
|
|
1395
|
+
emitBlock(e.body, ctx);
|
|
1396
|
+
if (e.else) {
|
|
1397
|
+
builder.appendNext(" else ");
|
|
1398
|
+
if (e.else.kind === "if") emitIf(e.else, ctx);
|
|
1399
|
+
else emitBlock(e.else, ctx);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
/** for (init; condition; update) { ... }. The init takes a ';' like any
|
|
1403
|
+
* statement; the update is the last clause before ')', so it gets none. */
|
|
1404
|
+
function emitFor(e, ctx) {
|
|
1405
|
+
const builder = ctx.srcBuilder;
|
|
1406
|
+
builder.appendNext("for (");
|
|
1407
|
+
if (e.init) emitCoreSemi(e.init, ctx);
|
|
1408
|
+
else builder.appendNext(";");
|
|
1409
|
+
builder.appendNext(" ");
|
|
1410
|
+
if (e.condition) emitExpression(e.condition, ctx);
|
|
1411
|
+
builder.appendNext("; ");
|
|
1412
|
+
if (e.update) emitStatementCore(e.update, ctx);
|
|
1413
|
+
builder.appendNext(") ");
|
|
1414
|
+
emitBlock(e.body, ctx);
|
|
1415
|
+
}
|
|
1416
|
+
function emitWhile(e, ctx) {
|
|
1417
|
+
ctx.srcBuilder.appendNext("while ");
|
|
1418
|
+
emitExpression(e.condition, ctx);
|
|
1419
|
+
ctx.srcBuilder.appendNext(" ");
|
|
1420
|
+
emitBlock(e.body, ctx);
|
|
1421
|
+
}
|
|
1422
|
+
function emitSwitch(e, ctx) {
|
|
1423
|
+
const builder = ctx.srcBuilder;
|
|
1424
|
+
builder.appendNext("switch ");
|
|
1425
|
+
emitExpression(e.selector, ctx);
|
|
1426
|
+
builder.appendNext(" ");
|
|
1427
|
+
emitAttributes(e.bodyAttributes, ctx);
|
|
1428
|
+
builder.appendNext("{");
|
|
1429
|
+
const inner = childIndent(ctx);
|
|
1430
|
+
for (const clause of filterValidElements(e.clauses, ctx.conditions)) emitSwitchClause(clause, inner);
|
|
1431
|
+
newLine(ctx);
|
|
1432
|
+
builder.appendNext("}");
|
|
1433
|
+
}
|
|
1434
|
+
/** lhs op rhs, with the phony target printed as `_`. */
|
|
1435
|
+
function emitAssign(e, ctx) {
|
|
1436
|
+
if (e.lhs.kind === "phony") ctx.srcBuilder.add("_", e.lhs.span[0], e.lhs.span[1]);
|
|
1437
|
+
else emitExpression(e.lhs, ctx);
|
|
1438
|
+
const [start, end] = e.op.span;
|
|
1439
|
+
ctx.srcBuilder.add(` ${e.op.value} `, start, end);
|
|
1440
|
+
emitExpression(e.rhs, ctx);
|
|
969
1441
|
}
|
|
970
1442
|
//#endregion
|
|
971
1443
|
//#region src/debug/ImportToString.ts
|
|
@@ -1050,19 +1522,25 @@ function astToString(elem, indent = 0) {
|
|
|
1050
1522
|
const str = new LineWrapper(indent, maxLineLength);
|
|
1051
1523
|
str.add(kind);
|
|
1052
1524
|
addElemFields(elem, str);
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
if (
|
|
1525
|
+
addCommentFields(elem, str);
|
|
1526
|
+
const children = childElems(elem);
|
|
1527
|
+
if (children.length) {
|
|
1056
1528
|
str.nl();
|
|
1529
|
+
const childStrings = children.map((e) => astToString(e, indent + 2));
|
|
1057
1530
|
str.addBlock(childStrings.join("\n"), false);
|
|
1058
1531
|
}
|
|
1059
1532
|
return str.result;
|
|
1060
1533
|
}
|
|
1534
|
+
/** @return string representation of an attribute (for test/debug) */
|
|
1535
|
+
function attributeToString(attr) {
|
|
1536
|
+
const str = new LineWrapper(0, maxLineLength);
|
|
1537
|
+
addAttributeFields(attr, str);
|
|
1538
|
+
return str.result;
|
|
1539
|
+
}
|
|
1061
1540
|
function addElemFields(elem, str) {
|
|
1062
1541
|
const { kind } = elem;
|
|
1063
|
-
if (kind === "assert" || kind === "
|
|
1064
|
-
if (kind === "
|
|
1065
|
-
else if (kind === "var" || kind === "let" || kind === "gvar" || kind === "const" || kind === "override") {
|
|
1542
|
+
if (kind === "assert" || kind === "expression" || kind === "module" || kind === "param" || kind === "switch-clause") return;
|
|
1543
|
+
if (kind === "var" || kind === "let" || kind === "gvar" || kind === "const" || kind === "override") {
|
|
1066
1544
|
addTypedDeclIdent(elem.name, str);
|
|
1067
1545
|
listAttributeElems(elem.attributes, str);
|
|
1068
1546
|
} else if (kind === "struct") str.add(" " + elem.name.ident.originalName);
|
|
@@ -1070,22 +1548,14 @@ function addElemFields(elem, str) {
|
|
|
1070
1548
|
listAttributeElems(elem.attributes, str);
|
|
1071
1549
|
str.add(` ${elem.name.name}: ${typeRefElemToString(elem.typeRef)}`);
|
|
1072
1550
|
} else if (kind === "name") str.add(" " + elem.name);
|
|
1073
|
-
else if (kind === "
|
|
1074
|
-
|
|
1075
|
-
str.add(` ${elem.name.ident.originalName}.${elem.member.name}${extra}`);
|
|
1076
|
-
} else if (kind === "fn") addFnFields(elem, str);
|
|
1551
|
+
else if (kind === "fn") addFnFields(elem, str);
|
|
1552
|
+
else if (kind === "do") addDoFields(elem, str);
|
|
1077
1553
|
else if (kind === "alias") {
|
|
1078
1554
|
const prefix = elem.name.ident.kind === "decl" ? "%" : "";
|
|
1079
1555
|
str.add(` ${prefix}${elem.name.ident.originalName}=${typeRefElemToString(elem.typeRef)}`);
|
|
1080
1556
|
} else if (kind === "attribute") addAttributeFields(elem.attribute, str);
|
|
1081
|
-
else if (kind === "
|
|
1082
|
-
|
|
1083
|
-
str.add(" " + contents);
|
|
1084
|
-
} else if (kind === "type") {
|
|
1085
|
-
const nameStr = typeof elem.name === "string" ? elem.name : elem.name.originalName;
|
|
1086
|
-
const params = elem.templateParams?.map(templateParamToString).join(", ");
|
|
1087
|
-
str.add(params ? ` ${nameStr}<${params}>` : ` ${nameStr}`);
|
|
1088
|
-
} else if (kind === "synthetic") str.add(` '${elem.text}'`);
|
|
1557
|
+
else if (kind === "type") str.add(" " + typeRefElemToString(elem));
|
|
1558
|
+
else if (kind === "synthetic") str.add(` '${elem.text}'`);
|
|
1089
1559
|
else if (kind === "import") {
|
|
1090
1560
|
str.add(" " + importToString(elem.imports));
|
|
1091
1561
|
listAttributeElems(elem.attributes, str);
|
|
@@ -1093,7 +1563,7 @@ function addElemFields(elem, str) {
|
|
|
1093
1563
|
else if (kind === "typeDecl") addTypedDeclIdent(elem, str);
|
|
1094
1564
|
else if (kind === "decl") str.add(" %" + elem.ident.originalName);
|
|
1095
1565
|
else if (kind === "directive") addDirective(elem, str);
|
|
1096
|
-
else if (kind === "
|
|
1566
|
+
else if (kind === "block" || kind === "if" || kind === "for" || kind === "while" || kind === "loop" || kind === "continuing" || kind === "switch" || kind === "return" || kind === "break" || kind === "continue" || kind === "discard" || kind === "assign" || kind === "increment" || kind === "decrement" || kind === "call" || kind === "empty") listAttributeElems(elem.attributes, str);
|
|
1097
1567
|
else if (kind === "literal") str.add(` literal(${elem.value})`);
|
|
1098
1568
|
else if (kind === "binary-expression") str.add(` binop(${elem.operator.value})`);
|
|
1099
1569
|
else if (kind === "unary-expression") str.add(` unop(${elem.operator.value})`);
|
|
@@ -1103,6 +1573,14 @@ function addElemFields(elem, str) {
|
|
|
1103
1573
|
else if (kind === "component-member-expression") str.add(" .");
|
|
1104
1574
|
else assertUnreachable(kind);
|
|
1105
1575
|
}
|
|
1576
|
+
/** Show attached leading/trailing comments to verify comment attachment. */
|
|
1577
|
+
function addCommentFields(elem, str) {
|
|
1578
|
+
if (!("start" in elem)) return;
|
|
1579
|
+
const { commentsBefore, commentsAfter } = elem;
|
|
1580
|
+
if (commentsBefore?.length) str.add(commentsToString("before", commentsBefore));
|
|
1581
|
+
if (commentsAfter?.length) str.add(commentsToString("after", commentsAfter));
|
|
1582
|
+
if ("innerComments" in elem && elem.innerComments?.length) str.add(commentsToString("inner", elem.innerComments));
|
|
1583
|
+
}
|
|
1106
1584
|
function addAttributeFields(attr, str) {
|
|
1107
1585
|
const { kind } = attr;
|
|
1108
1586
|
if (kind === "@attribute") {
|
|
@@ -1116,35 +1594,33 @@ function addAttributeFields(attr, str) {
|
|
|
1116
1594
|
else if (kind === "@interpolate") str.add(` @interpolate(${attr.params.map((v) => v.name).join(", ")})`);
|
|
1117
1595
|
else assertUnreachable(kind);
|
|
1118
1596
|
}
|
|
1119
|
-
/** @return string representation of an attribute (for test/debug) */
|
|
1120
|
-
function attributeToString(attr) {
|
|
1121
|
-
const str = new LineWrapper(0, maxLineLength);
|
|
1122
|
-
addAttributeFields(attr, str);
|
|
1123
|
-
return str.result;
|
|
1124
|
-
}
|
|
1125
1597
|
function addTypedDeclIdent(elem, str) {
|
|
1126
1598
|
const { decl, typeRef } = elem;
|
|
1127
1599
|
str.add(" %" + decl.ident.originalName);
|
|
1128
1600
|
if (typeRef) str.add(" : " + typeRefElemToString(typeRef));
|
|
1129
1601
|
}
|
|
1130
|
-
function addFnFields(elem, str) {
|
|
1131
|
-
const { name, params, returnType, attributes } = elem;
|
|
1132
|
-
const paramStrs = params.map((p) => {
|
|
1133
|
-
const { originalName } = p.name.decl.ident;
|
|
1134
|
-
return `${originalName}: ${typeRefElemToString(p.name.typeRef)}`;
|
|
1135
|
-
});
|
|
1136
|
-
str.add(` ${name.ident.originalName}(${paramStrs.join(", ")})`);
|
|
1137
|
-
listAttributeElems(attributes, str);
|
|
1138
|
-
if (returnType) str.add(" -> " + typeRefElemToString(returnType));
|
|
1139
|
-
}
|
|
1140
1602
|
/** show attribute names in short form to verify collection */
|
|
1141
1603
|
function listAttributeElems(attrs, str) {
|
|
1142
1604
|
attrs?.forEach((a) => {
|
|
1143
1605
|
str.add(" " + attributeName(a.attribute));
|
|
1144
1606
|
});
|
|
1145
1607
|
}
|
|
1146
|
-
function
|
|
1147
|
-
|
|
1608
|
+
function typeRefElemToString(elem) {
|
|
1609
|
+
if (!elem) return "?type?";
|
|
1610
|
+
const nameStr = typeof elem.name === "string" ? elem.name : elem.name.originalName;
|
|
1611
|
+
const params = elem.templateParams?.map(templateParamToString).join(", ");
|
|
1612
|
+
return params ? `${nameStr}<${params}>` : nameStr;
|
|
1613
|
+
}
|
|
1614
|
+
function addFnFields(elem, str) {
|
|
1615
|
+
const { name, params, returnType, attributes } = elem;
|
|
1616
|
+
str.add(` ${name.ident.originalName}(${paramListToString(params)})`);
|
|
1617
|
+
listAttributeElems(attributes, str);
|
|
1618
|
+
if (returnType) str.add(" -> " + typeRefElemToString(returnType));
|
|
1619
|
+
}
|
|
1620
|
+
function addDoFields(elem, str) {
|
|
1621
|
+
const { name, params, attributes } = elem;
|
|
1622
|
+
str.add(` ${name.name}(${paramListToString(params)})`);
|
|
1623
|
+
listAttributeElems(attributes, str);
|
|
1148
1624
|
}
|
|
1149
1625
|
function addDirective(elem, str) {
|
|
1150
1626
|
const { directive, attributes } = elem;
|
|
@@ -1157,30 +1633,29 @@ function addDirective(elem, str) {
|
|
|
1157
1633
|
else assertUnreachable(kind);
|
|
1158
1634
|
listAttributeElems(attributes, str);
|
|
1159
1635
|
}
|
|
1636
|
+
function commentsToString(label, comments) {
|
|
1637
|
+
return ` ${label}[${comments.map((c) => {
|
|
1638
|
+
const text = c.srcModule.src.slice(c.start, c.end);
|
|
1639
|
+
return c.blankBefore ? `'${text}'(blank)` : `'${text}'`;
|
|
1640
|
+
}).join(", ")}]`;
|
|
1641
|
+
}
|
|
1160
1642
|
function unknownExpressionToString(elem) {
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
}).join(" ");
|
|
1643
|
+
return astToString(elem.expression);
|
|
1644
|
+
}
|
|
1645
|
+
function attributeName(attr) {
|
|
1646
|
+
return attr.kind === "@attribute" ? "@" + attr.name : attr.kind;
|
|
1166
1647
|
}
|
|
1167
1648
|
function templateParamToString(p) {
|
|
1168
1649
|
if (typeof p === "string") return p;
|
|
1169
1650
|
if (p.kind === "type") return typeRefElemToString(p);
|
|
1170
|
-
return
|
|
1171
|
-
}
|
|
1172
|
-
function typeRefElemToString(elem) {
|
|
1173
|
-
if (!elem) return "?type?";
|
|
1174
|
-
const nameStr = typeof elem.name === "string" ? elem.name : elem.name.originalName;
|
|
1175
|
-
const params = elem.templateParams?.map(templateParamToString).join(", ");
|
|
1176
|
-
return params ? `${nameStr}<${params}>` : nameStr;
|
|
1651
|
+
return expressionToString(p);
|
|
1177
1652
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
return
|
|
1183
|
-
}).join(" ");
|
|
1653
|
+
/** @return "name: type, ..." for a fn/do parameter list. */
|
|
1654
|
+
function paramListToString(params) {
|
|
1655
|
+
return params.map((p) => {
|
|
1656
|
+
const { originalName } = p.name.decl.ident;
|
|
1657
|
+
return `${originalName}: ${typeRefElemToString(p.name.typeRef)}`;
|
|
1658
|
+
}).join(", ");
|
|
1184
1659
|
}
|
|
1185
1660
|
//#endregion
|
|
1186
1661
|
//#region src/debug/ScopeToString.ts
|
|
@@ -1257,7 +1732,7 @@ function liveDeclsToString(liveDecls) {
|
|
|
1257
1732
|
/**
|
|
1258
1733
|
* Construct a globally unique name based on the declaration
|
|
1259
1734
|
* module path separated by underscores.
|
|
1260
|
-
* Corresponds to "Underscore-count mangling" from [NameMangling.md](https://github.com/
|
|
1735
|
+
* Corresponds to "Underscore-count mangling" from [NameMangling.md](https://github.com/webgpu-tools/wesl-spec/blob/main/NameMangling.md)
|
|
1261
1736
|
*/
|
|
1262
1737
|
function underscoreMangle(decl, srcModule) {
|
|
1263
1738
|
const { modulePath } = srcModule;
|
|
@@ -1363,53 +1838,184 @@ var ParseError = class extends Error {
|
|
|
1363
1838
|
}
|
|
1364
1839
|
};
|
|
1365
1840
|
//#endregion
|
|
1366
|
-
//#region src/parse/
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1841
|
+
//#region src/parse/AttachComments.ts
|
|
1842
|
+
const tab = 9;
|
|
1843
|
+
const lineFeed = 10;
|
|
1844
|
+
const verticalTab = 11;
|
|
1845
|
+
const formFeed = 12;
|
|
1846
|
+
const carriageReturn = 13;
|
|
1847
|
+
const space = 32;
|
|
1848
|
+
const nextLine = 133;
|
|
1849
|
+
const leftToRightMark = 8206;
|
|
1850
|
+
const rightToLeftMark = 8207;
|
|
1851
|
+
const lineSeparator = 8232;
|
|
1852
|
+
const paragraphSeparator = 8233;
|
|
1853
|
+
/**
|
|
1854
|
+
* Attach every recorded comment to a node in the parsed tree.
|
|
1855
|
+
*
|
|
1856
|
+
* Each comment run (a contiguous group of comments between two real tokens) is
|
|
1857
|
+
* anchored to the deepest node that contains it in a gap between children, then
|
|
1858
|
+
* distributed to the surrounding children:
|
|
1859
|
+
* - a comment that begins its own line leads the next child (`commentsBefore`);
|
|
1860
|
+
* - an inline comment trails the previous child (`commentsAfter`), unless it
|
|
1861
|
+
* hugs the next child -- only blank space and comments, no separator token,
|
|
1862
|
+
* between them -- in which case it leads the next child instead;
|
|
1863
|
+
* - comments before a closing token with no following child trail the last child,
|
|
1864
|
+
* or land in an empty block's `innerComments`.
|
|
1865
|
+
*
|
|
1866
|
+
* Descending into expressions (via {@link childElems}) means interior comments
|
|
1867
|
+
* like the one in `foo(1, /* x *\/ 2)` are preserved on the `2`, not dropped.
|
|
1868
|
+
*/
|
|
1869
|
+
function attachComments(ctx, moduleElem) {
|
|
1870
|
+
const { srcModule } = ctx.state.stable;
|
|
1871
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1872
|
+
for (const run of ctx.stream.commentRuns()) distribute(deepestContaining(moduleElem, run[0].start, runEnd(run), cache), run, srcModule, cache);
|
|
1873
|
+
}
|
|
1874
|
+
/** The deepest node whose span contains the whole [start, end) range, found by
|
|
1875
|
+
* descending into the child that brackets it. Comments live in gaps between
|
|
1876
|
+
* tokens, so the result is the node holding the gap, with the run between two
|
|
1877
|
+
* of its children. */
|
|
1878
|
+
function deepestContaining(root, start, end, cache) {
|
|
1879
|
+
let node = root;
|
|
1880
|
+
while (true) {
|
|
1881
|
+
const child = positionedChildren(node, cache).find((c) => c.start <= start && end <= c.end);
|
|
1882
|
+
if (!child) return node;
|
|
1883
|
+
node = child;
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
function runEnd(run) {
|
|
1887
|
+
return run[run.length - 1].end;
|
|
1888
|
+
}
|
|
1889
|
+
/** Split a comment run between the previous child (trailing) and the next child
|
|
1890
|
+
* (leading) of its anchor, or onto the last child / inner comments when it
|
|
1891
|
+
* dangles before a closing token. */
|
|
1892
|
+
function distribute(anchor, run, srcModule, cache) {
|
|
1893
|
+
const children = positionedChildren(anchor, cache);
|
|
1894
|
+
const start = run[0].start;
|
|
1895
|
+
const end = runEnd(run);
|
|
1896
|
+
const prev = children.findLast((c) => c.end <= start);
|
|
1897
|
+
const next = children.find((c) => c.start >= end);
|
|
1898
|
+
if (!next) {
|
|
1899
|
+
if (prev) addComments(prev, "commentsAfter", run, srcModule);
|
|
1900
|
+
else if (anchor.kind === "block") anchor.innerComments = run.map((t) => makeComment(t, srcModule));
|
|
1901
|
+
else addComments(anchor, "commentsBefore", run, srcModule);
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
const split = splitPoint(prev, next, run, srcModule.src);
|
|
1905
|
+
if (prev && split > 0) addComments(prev, "commentsAfter", run.slice(0, split), srcModule);
|
|
1906
|
+
if (split < run.length) addComments(next, "commentsBefore", run.slice(split), srcModule);
|
|
1907
|
+
}
|
|
1908
|
+
function positionedChildren(node, cache) {
|
|
1909
|
+
let children = cache.get(node);
|
|
1910
|
+
if (children === void 0) {
|
|
1911
|
+
children = positioned(childElems(node));
|
|
1912
|
+
cache.set(node, children);
|
|
1913
|
+
}
|
|
1914
|
+
return children;
|
|
1915
|
+
}
|
|
1916
|
+
/** Append converted comments to an element's leading or trailing list. */
|
|
1917
|
+
function addComments(elem, field, trivia, srcModule) {
|
|
1918
|
+
const comments = trivia.map((t) => makeComment(t, srcModule));
|
|
1919
|
+
const existing = elem[field];
|
|
1920
|
+
elem[field] = existing ? [...existing, ...comments] : comments;
|
|
1921
|
+
}
|
|
1922
|
+
function makeComment(trivia, srcModule) {
|
|
1923
|
+
const { style, start, end } = trivia;
|
|
1924
|
+
const comment = {
|
|
1925
|
+
kind: "comment",
|
|
1926
|
+
style,
|
|
1395
1927
|
start,
|
|
1396
1928
|
end,
|
|
1397
1929
|
srcModule
|
|
1398
1930
|
};
|
|
1931
|
+
if (lineBreaksBefore(srcModule.src, start) >= 2) comment.blankBefore = true;
|
|
1932
|
+
return comment;
|
|
1399
1933
|
}
|
|
1400
|
-
/**
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1934
|
+
/**
|
|
1935
|
+
* Index into `run` where it flips from trailing `prev` to leading `next`:
|
|
1936
|
+
* caller attaches run[0, split) to `prev` and run[split, end) to `next`.
|
|
1937
|
+
*
|
|
1938
|
+
* The aim is to keep each comment with the code it describes, so it stays
|
|
1939
|
+
* meaningful after the tree is reordered or reformatted. That follows how people
|
|
1940
|
+
* write comments: a comment on its own line documents what comes after it, while
|
|
1941
|
+
* a comment sharing a line with code documents that code. Hence:
|
|
1942
|
+
* - with no previous child the whole run leads (split 0);
|
|
1943
|
+
* - otherwise the split is the first comment that begins its own line;
|
|
1944
|
+
* - failing that (an all-inline run), it is the start of the suffix that hugs
|
|
1945
|
+
* `next` on the same line, so the comment in `foo(1, /* x *\/ 2)` documents,
|
|
1946
|
+
* and lands on, the `2`.
|
|
1947
|
+
*/
|
|
1948
|
+
function splitPoint(prev, next, run, src) {
|
|
1949
|
+
if (!prev) return 0;
|
|
1950
|
+
const ownLine = firstOwnLine(run, src);
|
|
1951
|
+
return ownLine >= 0 ? ownLine : hugsNextStart(run, next.start, src);
|
|
1952
|
+
}
|
|
1953
|
+
/** Source-positioned children, sorted in source order. Synthetic elems (no
|
|
1954
|
+
* source position) cannot anchor comments and are dropped. Children usually
|
|
1955
|
+
* arrive already in source order, so the sort is skipped when possible.
|
|
1956
|
+
* Filtering and order-checking share one pass: this runs per node, so it's
|
|
1957
|
+
* kept deliberately lean. */
|
|
1958
|
+
function positioned(elems) {
|
|
1959
|
+
const result = [];
|
|
1960
|
+
let sorted = true;
|
|
1961
|
+
let lastStart = -1;
|
|
1962
|
+
for (const e of elems) {
|
|
1963
|
+
if (e.kind === "synthetic") continue;
|
|
1964
|
+
if (e.start < lastStart) sorted = false;
|
|
1965
|
+
lastStart = e.start;
|
|
1966
|
+
result.push(e);
|
|
1967
|
+
}
|
|
1968
|
+
if (!sorted) result.sort((a, b) => a.start - b.start);
|
|
1969
|
+
return result;
|
|
1970
|
+
}
|
|
1971
|
+
/** Count line breaks in the whitespace run immediately before `pos`, capped at 2:
|
|
1972
|
+
* callers only need none / one line break / a blank line. Scans backward over
|
|
1973
|
+
* blankspace, stopping at the first non-blankspace char, so cost is the gap
|
|
1974
|
+
* length, not the source length. `\r\n` counts as one break. */
|
|
1975
|
+
function lineBreaksBefore(src, pos) {
|
|
1976
|
+
let count = 0;
|
|
1977
|
+
for (let i = pos - 1; i >= 0; i--) {
|
|
1978
|
+
const c = src.charCodeAt(i);
|
|
1979
|
+
if (isLineBreak(c)) {
|
|
1980
|
+
if (c === lineFeed && src.charCodeAt(i - 1) === carriageReturn) i--;
|
|
1981
|
+
} else if (isInlineSpace(c)) continue;
|
|
1982
|
+
else break;
|
|
1983
|
+
if (++count >= 2) return 2;
|
|
1984
|
+
}
|
|
1985
|
+
return count;
|
|
1986
|
+
}
|
|
1987
|
+
/** Index of the first comment that begins its own line (after a line break),
|
|
1988
|
+
* or -1 if none do. */
|
|
1989
|
+
function firstOwnLine(run, src) {
|
|
1990
|
+
return run.findIndex((t) => lineBreaksBefore(src, t.start) >= 1);
|
|
1991
|
+
}
|
|
1992
|
+
/** Start index of the trailing suffix of `run` that is joined to `next` by
|
|
1993
|
+
* same-line whitespace only (no line break, no separator token between them).
|
|
1994
|
+
* Returns run.length when nothing hugs `next`. */
|
|
1995
|
+
function hugsNextStart(run, nextStart, src) {
|
|
1996
|
+
let suffixStart = run.length;
|
|
1997
|
+
let rightStart = nextStart;
|
|
1998
|
+
for (let i = run.length - 1; i >= 0; i--) {
|
|
1999
|
+
const comment = run[i];
|
|
2000
|
+
if (!sameLineGap(src, comment.end, rightStart)) break;
|
|
2001
|
+
suffixStart = i;
|
|
2002
|
+
rightStart = comment.start;
|
|
2003
|
+
}
|
|
2004
|
+
return suffixStart;
|
|
2005
|
+
}
|
|
2006
|
+
/** A WGSL line break code point. `\r\n` is two of these; callers coalesce it. */
|
|
2007
|
+
function isLineBreak(c) {
|
|
2008
|
+
return c === lineFeed || c === carriageReturn || c === verticalTab || c === formFeed || c === nextLine || c === lineSeparator || c === paragraphSeparator;
|
|
2009
|
+
}
|
|
2010
|
+
/** WGSL blankspace that stays on the same line (not a line break). */
|
|
2011
|
+
function isInlineSpace(c) {
|
|
2012
|
+
return c === space || c === tab || c === leftToRightMark || c === rightToLeftMark;
|
|
2013
|
+
}
|
|
2014
|
+
/** True when [from, to) is inline blankspace only (no line break): the two ends
|
|
2015
|
+
* sit on the same line with nothing but same-line spaces between. */
|
|
2016
|
+
function sameLineGap(src, from, to) {
|
|
2017
|
+
for (let i = from; i < to; i++) if (!isInlineSpace(src.charCodeAt(i))) return false;
|
|
2018
|
+
return true;
|
|
1413
2019
|
}
|
|
1414
2020
|
//#endregion
|
|
1415
2021
|
//#region src/parse/ExpressionUtil.ts
|
|
@@ -1423,9 +2029,11 @@ function makeLiteral(token) {
|
|
|
1423
2029
|
};
|
|
1424
2030
|
}
|
|
1425
2031
|
function makeUnaryOperator(token) {
|
|
2032
|
+
const [start, end] = token.span;
|
|
1426
2033
|
return {
|
|
1427
2034
|
value: token.text,
|
|
1428
|
-
|
|
2035
|
+
start,
|
|
2036
|
+
end
|
|
1429
2037
|
};
|
|
1430
2038
|
}
|
|
1431
2039
|
function makeBinaryOperator(token) {
|
|
@@ -1435,12 +2043,11 @@ function makeBinaryOperator(token) {
|
|
|
1435
2043
|
};
|
|
1436
2044
|
}
|
|
1437
2045
|
function makeUnaryExpression(operator, expr) {
|
|
1438
|
-
const [start] = operator.span;
|
|
1439
2046
|
return {
|
|
1440
2047
|
kind: "unary-expression",
|
|
1441
2048
|
operator,
|
|
1442
2049
|
expression: expr,
|
|
1443
|
-
start,
|
|
2050
|
+
start: operator.start,
|
|
1444
2051
|
end: expr.end
|
|
1445
2052
|
};
|
|
1446
2053
|
}
|
|
@@ -1501,13 +2108,16 @@ function expectWord(stream, errorMsg) {
|
|
|
1501
2108
|
stream.nextToken();
|
|
1502
2109
|
return token;
|
|
1503
2110
|
}
|
|
1504
|
-
/** Parse
|
|
2111
|
+
/** Parse a required expression, throwing if absent. */
|
|
1505
2112
|
function expectExpression(ctx, errorMsg = "Expected expression") {
|
|
1506
2113
|
const expr = parseExpression(ctx);
|
|
1507
2114
|
if (!expr) throwParseError(ctx.stream, errorMsg);
|
|
1508
|
-
if (ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
1509
2115
|
return expr;
|
|
1510
2116
|
}
|
|
2117
|
+
/** Parse an optional expression. */
|
|
2118
|
+
function parseContentExpression(ctx, opts) {
|
|
2119
|
+
return parseExpression(ctx, opts);
|
|
2120
|
+
}
|
|
1511
2121
|
/** Throw a ParseError at the current/next token position. */
|
|
1512
2122
|
function throwParseError(stream, message) {
|
|
1513
2123
|
const weslStream = stream;
|
|
@@ -1563,18 +2173,27 @@ function makeRefIdentElem(ctx, refIdent, start, end) {
|
|
|
1563
2173
|
refIdent.refIdentElem = elem;
|
|
1564
2174
|
return elem;
|
|
1565
2175
|
}
|
|
1566
|
-
|
|
2176
|
+
/** @return true if the attribute is a conditional (@if, @elif, @else) */
|
|
2177
|
+
function isConditionalAttribute(attr) {
|
|
1567
2178
|
const { kind } = attr;
|
|
1568
2179
|
return kind === "@if" || kind === "@elif" || kind === "@else";
|
|
1569
2180
|
}
|
|
1570
2181
|
/** @return true if any attribute is a conditional (@if, @elif, @else) */
|
|
1571
2182
|
function hasConditionalAttribute(attributes) {
|
|
1572
|
-
return attributes.some((attr) => isConditionalAttribute
|
|
2183
|
+
return attributes.some((attr) => isConditionalAttribute(attr.attribute));
|
|
2184
|
+
}
|
|
2185
|
+
/** The first conditional (@if, @elif, @else) attribute in the list, if any. */
|
|
2186
|
+
function conditionalAttribute(attributes) {
|
|
2187
|
+
return attributes.find((a) => isConditionalAttribute(a.attribute))?.attribute;
|
|
1573
2188
|
}
|
|
1574
2189
|
/** Attach non-empty attributes array to element. */
|
|
1575
2190
|
function attachAttributes(elem, attributes) {
|
|
1576
2191
|
if (attributes?.length) elem.attributes = attributes;
|
|
1577
2192
|
}
|
|
2193
|
+
/** Normalize an empty attribute list to undefined (the elem's "no attributes"). */
|
|
2194
|
+
function attrsOrUndef(attrs) {
|
|
2195
|
+
return attrs.length ? attrs : void 0;
|
|
2196
|
+
}
|
|
1578
2197
|
/** Link a DeclIdentElem's ident to its parent declaration. */
|
|
1579
2198
|
function linkDeclIdentElem(declIdentElem, declElem) {
|
|
1580
2199
|
declIdentElem.ident.declElem = declElem;
|
|
@@ -1717,10 +2336,7 @@ function parseIdent(ctx, conditionRef) {
|
|
|
1717
2336
|
const ident = ctx.createRefIdent(parts.join("::"));
|
|
1718
2337
|
if (conditionRef) ident.conditionRef = true;
|
|
1719
2338
|
const refIdentElem = makeRefIdentElem(ctx, ident, start, end);
|
|
1720
|
-
if (!conditionRef)
|
|
1721
|
-
ctx.saveIdent(ident);
|
|
1722
|
-
ctx.addElem(refIdentElem);
|
|
1723
|
-
}
|
|
2339
|
+
if (!conditionRef) ctx.saveIdent(ident);
|
|
1724
2340
|
return refIdentElem;
|
|
1725
2341
|
}
|
|
1726
2342
|
/** Check if token is valid as a path segment (word or allowed keyword). */
|
|
@@ -1742,19 +2358,20 @@ function parseSimpleTypeRef(ctx) {
|
|
|
1742
2358
|
if (!path) return null;
|
|
1743
2359
|
const { parts, start, end: nameEnd } = path;
|
|
1744
2360
|
const refIdent = ctx.createRefIdent(parts.join("::"));
|
|
1745
|
-
|
|
1746
|
-
const refIdentElem = makeRefIdentElem(ctx, refIdent, start, nameEnd);
|
|
2361
|
+
makeRefIdentElem(ctx, refIdent, start, nameEnd);
|
|
1747
2362
|
ctx.saveIdent(refIdent);
|
|
1748
|
-
|
|
1749
|
-
|
|
2363
|
+
return {
|
|
2364
|
+
kind: "type",
|
|
1750
2365
|
name: refIdent,
|
|
1751
|
-
templateParams: ctx.stream.nextTemplateStartToken() ? parseTemplateParams(ctx) : void 0
|
|
1752
|
-
|
|
2366
|
+
templateParams: ctx.stream.nextTemplateStartToken() ? parseTemplateParams(ctx) : void 0,
|
|
2367
|
+
start,
|
|
2368
|
+
end: ctx.stream.checkpoint()
|
|
2369
|
+
};
|
|
1753
2370
|
}
|
|
1754
2371
|
/** Parse comma-separated template parameters until closing '>'. */
|
|
1755
2372
|
function parseTemplateParams(ctx) {
|
|
1756
2373
|
const { stream } = ctx;
|
|
1757
|
-
if (consumeTemplateEnd(stream))
|
|
2374
|
+
if (consumeTemplateEnd(stream)) throwParseError(stream, "Empty template parameter list '<>'");
|
|
1758
2375
|
const params = [parseTemplateParam(ctx)];
|
|
1759
2376
|
while (stream.matchText(",")) params.push(parseTemplateParam(ctx));
|
|
1760
2377
|
if (!consumeTemplateEnd(stream)) throwParseError(stream, "Expected '>' or ',' after template parameter");
|
|
@@ -1769,8 +2386,8 @@ function consumeTemplateEnd(stream) {
|
|
|
1769
2386
|
/** Grammar: template_arg_expression : expression */
|
|
1770
2387
|
function parseTemplateParam(ctx) {
|
|
1771
2388
|
const expr = parseExpression(ctx, { inTemplate: true });
|
|
1772
|
-
if (expr)
|
|
1773
|
-
|
|
2389
|
+
if (!expr) throwParseError(ctx.stream, "Expected expression in template parameters");
|
|
2390
|
+
return expr;
|
|
1774
2391
|
}
|
|
1775
2392
|
//#endregion
|
|
1776
2393
|
//#region src/parse/ParseCall.ts
|
|
@@ -1781,7 +2398,7 @@ function parseTemplateParam(ctx) {
|
|
|
1781
2398
|
function parseCallSuffix(ctx, current, parseExpr) {
|
|
1782
2399
|
if (current.kind !== "ref" && current.kind !== "type") return null;
|
|
1783
2400
|
const { stream } = ctx;
|
|
1784
|
-
const templateArgs = current.kind === "
|
|
2401
|
+
const templateArgs = current.kind === "ref" ? parseCallTemplateArgs(ctx) : null;
|
|
1785
2402
|
if (!stream.matchText("(")) return null;
|
|
1786
2403
|
const args = [];
|
|
1787
2404
|
while (true) {
|
|
@@ -1946,8 +2563,7 @@ function parseTemplateElaboratedIdent(ctx, conditionRef) {
|
|
|
1946
2563
|
name: refIdent.ident,
|
|
1947
2564
|
templateParams,
|
|
1948
2565
|
start: refIdent.start,
|
|
1949
|
-
end: ctx.stream.checkpoint()
|
|
1950
|
-
contents: []
|
|
2566
|
+
end: ctx.stream.checkpoint()
|
|
1951
2567
|
};
|
|
1952
2568
|
}
|
|
1953
2569
|
/** Parse postfix operators: member access, indexing, function calls. */
|
|
@@ -1974,7 +2590,9 @@ function parseMemberAccess(ctx, base) {
|
|
|
1974
2590
|
function parseIndexAccess(ctx, base) {
|
|
1975
2591
|
const { stream } = ctx;
|
|
1976
2592
|
if (!stream.matchText("[")) return null;
|
|
1977
|
-
|
|
2593
|
+
const indexExpr = parseExpression(ctx);
|
|
2594
|
+
if (!indexExpr) throwParseError(stream, "Expected expression in array index");
|
|
2595
|
+
return makeComponentExpression(base, indexExpr, expect(stream, "]", "array index").span[1]);
|
|
1978
2596
|
}
|
|
1979
2597
|
//#endregion
|
|
1980
2598
|
//#region src/parse/ParseAttribute.ts
|
|
@@ -1995,6 +2613,20 @@ function parseElseAttribute(ctx) {
|
|
|
1995
2613
|
function parseElifAttribute(ctx) {
|
|
1996
2614
|
return parseConditionalAttribute(ctx, "elif", makeElifAttribute);
|
|
1997
2615
|
}
|
|
2616
|
+
/** Parse WESL conditional attributes (@if, @elif, @else) */
|
|
2617
|
+
function parseWeslConditional(ctx) {
|
|
2618
|
+
const { stream } = ctx;
|
|
2619
|
+
const peeked = stream.peek();
|
|
2620
|
+
if (peeked?.text !== "@") return null;
|
|
2621
|
+
const startPos = peeked.span[0];
|
|
2622
|
+
const ifAttr = parseIfAttribute(ctx);
|
|
2623
|
+
if (ifAttr) return attributeElem(ifAttr, startPos, stream.checkpoint());
|
|
2624
|
+
const elifAttr = parseElifAttribute(ctx);
|
|
2625
|
+
if (elifAttr) return attributeElem(elifAttr, startPos, stream.checkpoint());
|
|
2626
|
+
const elseAttr = parseElseAttribute(ctx);
|
|
2627
|
+
if (elseAttr) return attributeElem(elseAttr, startPos, stream.checkpoint());
|
|
2628
|
+
return null;
|
|
2629
|
+
}
|
|
1998
2630
|
/**
|
|
1999
2631
|
* Grammar: attribute :
|
|
2000
2632
|
* '@' ident_pattern_token argument_expression_list ?
|
|
@@ -2005,15 +2637,8 @@ function parseElifAttribute(ctx) {
|
|
|
2005
2637
|
* WESL extensions: @if, @elif, @else
|
|
2006
2638
|
*/
|
|
2007
2639
|
function parseAttribute(ctx) {
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
if (!stream.matchText("@")) return null;
|
|
2011
|
-
stream.reset(startPos);
|
|
2012
|
-
const weslAttr = parseWeslConditional(ctx);
|
|
2013
|
-
if (weslAttr) return weslAttr;
|
|
2014
|
-
const stdAttr = parseStandardAttribute(ctx);
|
|
2015
|
-
if (stdAttr) return stdAttr;
|
|
2016
|
-
return null;
|
|
2640
|
+
if (ctx.stream.peek()?.text !== "@") return null;
|
|
2641
|
+
return parseWeslConditional(ctx) ?? parseStandardAttribute(ctx);
|
|
2017
2642
|
}
|
|
2018
2643
|
/** Parse `@if(expr)` or `@elif(expr)` conditional attributes. */
|
|
2019
2644
|
function parseConditionalAttribute(ctx, keyword, makeAttr) {
|
|
@@ -2022,13 +2647,15 @@ function parseConditionalAttribute(ctx, keyword, makeAttr) {
|
|
|
2022
2647
|
if (!stream.matchSequence("@", keyword)) return null;
|
|
2023
2648
|
expect(stream, "(", `@${keyword}`);
|
|
2024
2649
|
const expr = parseExpression(ctx, true);
|
|
2025
|
-
if (!expr)
|
|
2650
|
+
if (!expr) throwParseError(stream, `Expected expression after @${keyword}(`);
|
|
2026
2651
|
stream.matchText(",");
|
|
2027
2652
|
expect(stream, ")", `@${keyword} expression`);
|
|
2028
|
-
return makeAttr(
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2653
|
+
return makeAttr({
|
|
2654
|
+
kind: "translate-time-expression",
|
|
2655
|
+
expression: expr,
|
|
2656
|
+
start: startPos,
|
|
2657
|
+
end: stream.checkpoint()
|
|
2658
|
+
});
|
|
2032
2659
|
}
|
|
2033
2660
|
function makeIfAttribute(param) {
|
|
2034
2661
|
return {
|
|
@@ -2045,19 +2672,13 @@ function makeElifAttribute(param) {
|
|
|
2045
2672
|
param
|
|
2046
2673
|
};
|
|
2047
2674
|
}
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
if (ifAttr) return attributeElem(ifAttr, startPos, stream.checkpoint());
|
|
2056
|
-
const elifAttr = parseElifAttribute(ctx);
|
|
2057
|
-
if (elifAttr) return attributeElem(elifAttr, startPos, stream.checkpoint());
|
|
2058
|
-
const elseAttr = parseElseAttribute(ctx);
|
|
2059
|
-
if (elseAttr) return attributeElem(elseAttr, startPos, stream.checkpoint());
|
|
2060
|
-
return null;
|
|
2675
|
+
function attributeElem(attribute, start, end) {
|
|
2676
|
+
return {
|
|
2677
|
+
kind: "attribute",
|
|
2678
|
+
attribute,
|
|
2679
|
+
start,
|
|
2680
|
+
end
|
|
2681
|
+
};
|
|
2061
2682
|
}
|
|
2062
2683
|
/** Parse a standard attribute (not @if/@elif/@else) */
|
|
2063
2684
|
function parseStandardAttribute(ctx) {
|
|
@@ -2067,7 +2688,7 @@ function parseStandardAttribute(ctx) {
|
|
|
2067
2688
|
if (!atToken) return null;
|
|
2068
2689
|
const startPos = atToken.span[0];
|
|
2069
2690
|
const nameToken = stream.peek();
|
|
2070
|
-
if (
|
|
2691
|
+
if (nameToken?.kind !== "word" && nameToken?.kind !== "keyword") {
|
|
2071
2692
|
stream.reset(resetPos);
|
|
2072
2693
|
return null;
|
|
2073
2694
|
}
|
|
@@ -2090,22 +2711,6 @@ function parseStandardAttribute(ctx) {
|
|
|
2090
2711
|
params
|
|
2091
2712
|
}, startPos, stream.checkpoint());
|
|
2092
2713
|
}
|
|
2093
|
-
function makeTranslateTimeExpressionElem(args) {
|
|
2094
|
-
return {
|
|
2095
|
-
kind: "translate-time-expression",
|
|
2096
|
-
expression: args.value,
|
|
2097
|
-
span: args.span
|
|
2098
|
-
};
|
|
2099
|
-
}
|
|
2100
|
-
function attributeElem(attribute, start, end) {
|
|
2101
|
-
return {
|
|
2102
|
-
kind: "attribute",
|
|
2103
|
-
attribute,
|
|
2104
|
-
start,
|
|
2105
|
-
end,
|
|
2106
|
-
contents: []
|
|
2107
|
-
};
|
|
2108
|
-
}
|
|
2109
2714
|
function parseBuiltinAttribute(ctx, startPos) {
|
|
2110
2715
|
const { stream } = ctx;
|
|
2111
2716
|
expect(stream, "(", "@builtin");
|
|
@@ -2126,9 +2731,6 @@ function parseInterpolateAttribute(ctx, startPos) {
|
|
|
2126
2731
|
params
|
|
2127
2732
|
}, startPos, stream.checkpoint());
|
|
2128
2733
|
}
|
|
2129
|
-
function parseNameElem(ctx) {
|
|
2130
|
-
return makeNameElem(expectWord(ctx.stream, "Expected identifier"));
|
|
2131
|
-
}
|
|
2132
2734
|
/** @diagnostic(severity, rule) or @diagnostic(severity, namespace.rule) */
|
|
2133
2735
|
function parseDiagnosticAttribute(ctx, startPos) {
|
|
2134
2736
|
const { stream } = ctx;
|
|
@@ -2150,78 +2752,20 @@ function parseDiagnosticAttribute(ctx, startPos) {
|
|
|
2150
2752
|
function parseAttributeParams(ctx) {
|
|
2151
2753
|
return parseCommaList(ctx, parseAttrParam);
|
|
2152
2754
|
}
|
|
2755
|
+
function parseNameElem(ctx) {
|
|
2756
|
+
return makeNameElem(expectWord(ctx.stream, "Expected identifier"));
|
|
2757
|
+
}
|
|
2153
2758
|
function parseAttrParam(ctx) {
|
|
2154
2759
|
const { stream } = ctx;
|
|
2155
2760
|
const start = stream.checkpoint();
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
const end = stream.checkpoint();
|
|
2761
|
+
const expression = parseExpression(ctx);
|
|
2762
|
+
if (!expression) throwParseError(stream, "Expected attribute parameter");
|
|
2159
2763
|
return {
|
|
2160
2764
|
kind: "expression",
|
|
2765
|
+
expression,
|
|
2161
2766
|
start,
|
|
2162
|
-
end,
|
|
2163
|
-
contents: finishContents(ctx, start, end)
|
|
2164
|
-
};
|
|
2165
|
-
}
|
|
2166
|
-
//#endregion
|
|
2167
|
-
//#region src/parse/ParseDirective.ts
|
|
2168
|
-
/** Grammar: global_directive : diagnostic_directive | enable_directive | requires_directive */
|
|
2169
|
-
function parseDirective(ctx) {
|
|
2170
|
-
const { stream } = ctx;
|
|
2171
|
-
const startPos = stream.checkpoint();
|
|
2172
|
-
const attributes = parseAttributeList(ctx);
|
|
2173
|
-
const attrs = attributes.length > 0 ? attributes : void 0;
|
|
2174
|
-
const result = parseExtensionDirective(ctx, "enable", attrs) || parseExtensionDirective(ctx, "requires", attrs) || parseDiagnosticDirective(ctx, attrs);
|
|
2175
|
-
if (!result) stream.reset(startPos);
|
|
2176
|
-
return result;
|
|
2177
|
-
}
|
|
2178
|
-
/** Grammar: enable_directive | requires_directive : keyword extension_list ';' */
|
|
2179
|
-
function parseExtensionDirective(ctx, keyword, attributes) {
|
|
2180
|
-
const { stream } = ctx;
|
|
2181
|
-
const token = stream.matchText(keyword);
|
|
2182
|
-
if (!token) return null;
|
|
2183
|
-
const extensions = parseCommaList(ctx, parseDirectiveName);
|
|
2184
|
-
expect(stream, ";", `${keyword} directive`);
|
|
2185
|
-
return makeDirectiveElem({
|
|
2186
|
-
kind: keyword,
|
|
2187
|
-
extensions
|
|
2188
|
-
}, token, stream, attributes);
|
|
2189
|
-
}
|
|
2190
|
-
function parseDirectiveName(ctx) {
|
|
2191
|
-
return makeNameElem(expectWord(ctx.stream, "Expected identifier in name list"));
|
|
2192
|
-
}
|
|
2193
|
-
/**
|
|
2194
|
-
* Grammar: diagnostic_directive : 'diagnostic' diagnostic_control ';'
|
|
2195
|
-
* Grammar: diagnostic_control : '(' severity_control_name ',' diagnostic_rule_name ',' ? ')'
|
|
2196
|
-
*/
|
|
2197
|
-
function parseDiagnosticDirective(ctx, attributes) {
|
|
2198
|
-
const { stream } = ctx;
|
|
2199
|
-
const token = stream.matchText("diagnostic");
|
|
2200
|
-
if (!token) return null;
|
|
2201
|
-
expect(stream, "(", "diagnostic");
|
|
2202
|
-
const severity = makeNameElem(expectWord(stream, "Expected severity in diagnostic"));
|
|
2203
|
-
expect(stream, ",", "diagnostic severity");
|
|
2204
|
-
const ruleName = makeNameElem(expectWord(stream, "Expected rule name in diagnostic"));
|
|
2205
|
-
let subrule = null;
|
|
2206
|
-
if (stream.matchText(".")) subrule = makeNameElem(expectWord(stream, "Expected subrule name after '.'"));
|
|
2207
|
-
stream.matchText(",");
|
|
2208
|
-
expect(stream, ")", "diagnostic rule");
|
|
2209
|
-
expect(stream, ";", "diagnostic directive");
|
|
2210
|
-
return makeDirectiveElem({
|
|
2211
|
-
kind: "diagnostic",
|
|
2212
|
-
severity,
|
|
2213
|
-
rule: [ruleName, subrule]
|
|
2214
|
-
}, token, stream, attributes);
|
|
2215
|
-
}
|
|
2216
|
-
function makeDirectiveElem(directive, token, stream, attributes) {
|
|
2217
|
-
const elem = {
|
|
2218
|
-
kind: "directive",
|
|
2219
|
-
directive,
|
|
2220
|
-
start: attributes?.[0]?.start ?? token.span[0],
|
|
2221
2767
|
end: stream.checkpoint()
|
|
2222
2768
|
};
|
|
2223
|
-
attachAttributes(elem, attributes);
|
|
2224
|
-
return elem;
|
|
2225
2769
|
}
|
|
2226
2770
|
//#endregion
|
|
2227
2771
|
//#region src/parse/ParseControlFlow.ts
|
|
@@ -2232,37 +2776,50 @@ function makeDirectiveElem(directive, token, stream, attributes) {
|
|
|
2232
2776
|
function parseIfStatement(ctx, attributes) {
|
|
2233
2777
|
const startPos = beginStatement(ctx, "if", attributes);
|
|
2234
2778
|
if (startPos === null) return null;
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2779
|
+
return finishStatement("if", startPos, ctx, {
|
|
2780
|
+
condition: expectExpression(ctx, "Expected condition after 'if'"),
|
|
2781
|
+
body: expectCompound(ctx, "Expected '{' after if condition"),
|
|
2782
|
+
else: parseElseChain(ctx)
|
|
2783
|
+
}, attributes);
|
|
2240
2784
|
}
|
|
2241
2785
|
/** Grammar: switch_statement : attribute* 'switch' expression switch_body */
|
|
2242
2786
|
function parseSwitchStatement(ctx, attributes) {
|
|
2243
2787
|
const startPos = beginStatement(ctx, "switch", attributes);
|
|
2244
2788
|
if (startPos === null) return null;
|
|
2245
|
-
expectExpression(ctx, "Expected expression after 'switch'");
|
|
2246
|
-
expectSwitchClauses(ctx);
|
|
2247
|
-
return
|
|
2789
|
+
const selector = expectExpression(ctx, "Expected expression after 'switch'");
|
|
2790
|
+
const { bodyAttributes, clauses } = expectSwitchClauses(ctx);
|
|
2791
|
+
return finishStatement("switch", startPos, ctx, {
|
|
2792
|
+
selector,
|
|
2793
|
+
clauses,
|
|
2794
|
+
bodyAttributes
|
|
2795
|
+
}, attributes);
|
|
2248
2796
|
}
|
|
2249
2797
|
/**
|
|
2250
2798
|
* Grammar: else_if_clause : 'else' 'if' expression compound_statement
|
|
2251
2799
|
* Grammar: else_clause : 'else' compound_statement
|
|
2800
|
+
*
|
|
2801
|
+
* An else-if nests as an IfElem in the outer if's `else` field; a plain else is
|
|
2802
|
+
* a BlockElem. Emit and the AST dump read these typed fields.
|
|
2252
2803
|
*/
|
|
2253
2804
|
function parseElseChain(ctx) {
|
|
2254
2805
|
const { stream } = ctx;
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2806
|
+
const elseToken = stream.matchText("else");
|
|
2807
|
+
if (!elseToken) return void 0;
|
|
2808
|
+
if (stream.matchText("if")) {
|
|
2809
|
+
const condition = expectExpression(ctx, "Expected expression after 'else if'");
|
|
2810
|
+
const body = expectCompound(ctx, "Expected '{' after else if");
|
|
2811
|
+
const elseBranch = parseElseChain(ctx);
|
|
2812
|
+
const end = stream.checkpoint();
|
|
2813
|
+
return {
|
|
2814
|
+
kind: "if",
|
|
2815
|
+
condition,
|
|
2816
|
+
body,
|
|
2817
|
+
else: elseBranch,
|
|
2818
|
+
start: elseToken.span[0],
|
|
2819
|
+
end
|
|
2820
|
+
};
|
|
2265
2821
|
}
|
|
2822
|
+
return expectCompound(ctx, "Expected '{' after else");
|
|
2266
2823
|
}
|
|
2267
2824
|
/**
|
|
2268
2825
|
* Grammar: switch_body : attribute* '{' switch_clause+ '}'
|
|
@@ -2272,27 +2829,43 @@ function parseElseChain(ctx) {
|
|
|
2272
2829
|
*/
|
|
2273
2830
|
function expectSwitchClauses(ctx) {
|
|
2274
2831
|
const { stream } = ctx;
|
|
2275
|
-
parseAttributeList(ctx);
|
|
2832
|
+
const bodyAttrs = parseAttributeList(ctx);
|
|
2276
2833
|
expect(stream, "{", "switch expression");
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2834
|
+
const clauses = [];
|
|
2835
|
+
while (!stream.matchText("}")) clauses.push(parseSwitchClause(ctx));
|
|
2836
|
+
return {
|
|
2837
|
+
bodyAttributes: attrsOrUndef(bodyAttrs),
|
|
2838
|
+
clauses
|
|
2839
|
+
};
|
|
2840
|
+
}
|
|
2841
|
+
/** Parse one 'case'/'default' clause (the keyword has not yet been consumed). */
|
|
2842
|
+
function parseSwitchClause(ctx) {
|
|
2843
|
+
const { stream } = ctx;
|
|
2844
|
+
const attrs = attrsOrUndef(parseAttributeList(ctx));
|
|
2845
|
+
const caseTok = stream.matchText("case");
|
|
2846
|
+
const keyword = caseTok ?? stream.matchText("default");
|
|
2847
|
+
if (!keyword) throwParseError(stream, "Expected 'case', 'default', or '}' in switch");
|
|
2848
|
+
const clauseStart = getStartWithAttributes(attrs, keyword.span[0]);
|
|
2849
|
+
let selectors;
|
|
2850
|
+
let body;
|
|
2851
|
+
if (caseTok) {
|
|
2852
|
+
selectors = parseCaseSelectors(ctx);
|
|
2853
|
+
body = parseCaseBody(ctx, "Expected '{' after case value");
|
|
2854
|
+
} else {
|
|
2855
|
+
selectors = ["default"];
|
|
2856
|
+
body = parseCaseBody(ctx, "Expected '{' after 'default'");
|
|
2289
2857
|
}
|
|
2858
|
+
return finishStatement("switch-clause", clauseStart, ctx, {
|
|
2859
|
+
selectors,
|
|
2860
|
+
body
|
|
2861
|
+
}, attrs);
|
|
2290
2862
|
}
|
|
2291
2863
|
/** Grammar: case_selectors : case_selector (',' case_selector)* ','? */
|
|
2292
2864
|
function parseCaseSelectors(ctx) {
|
|
2293
2865
|
const { stream } = ctx;
|
|
2294
|
-
expectExpression(ctx, "Expected expression after 'case'");
|
|
2295
|
-
while (stream.matchText(",")) expectExpression(ctx, "Expected expression after ',' in case values");
|
|
2866
|
+
const selectors = [expectExpression(ctx, "Expected expression after 'case'")];
|
|
2867
|
+
while (stream.matchText(",")) selectors.push(expectExpression(ctx, "Expected expression after ',' in case values"));
|
|
2868
|
+
return selectors;
|
|
2296
2869
|
}
|
|
2297
2870
|
/**
|
|
2298
2871
|
* Grammar: case_clause : 'case' case_selectors ':'? compound_statement
|
|
@@ -2300,16 +2873,15 @@ function parseCaseSelectors(ctx) {
|
|
|
2300
2873
|
*/
|
|
2301
2874
|
function parseCaseBody(ctx, errorMsg) {
|
|
2302
2875
|
ctx.stream.matchText(":");
|
|
2303
|
-
const
|
|
2304
|
-
const body = parseCompoundStatement(ctx, bodyAttrs.length > 0 ? bodyAttrs : void 0);
|
|
2876
|
+
const body = parseCompoundStatement(ctx, attrsOrUndef(parseAttributeList(ctx)));
|
|
2305
2877
|
if (!body) throwParseError(ctx.stream, errorMsg);
|
|
2306
|
-
|
|
2878
|
+
return body;
|
|
2307
2879
|
}
|
|
2308
2880
|
//#endregion
|
|
2309
2881
|
//#region src/parse/ParseValueDeclaration.ts
|
|
2310
2882
|
/** Grammar: 'const' optionally_typed_ident '=' expression (global or local) */
|
|
2311
2883
|
function parseConstDecl(ctx, attributes) {
|
|
2312
|
-
return parseValueDecl(ctx, "const", true, isModuleScope(
|
|
2884
|
+
return parseValueDecl(ctx, "const", true, ctx.isModuleScope(), attributes);
|
|
2313
2885
|
}
|
|
2314
2886
|
/** Grammar: 'override' optionally_typed_ident ( '=' expression )? */
|
|
2315
2887
|
function parseOverrideDecl(ctx, attributes) {
|
|
@@ -2320,20 +2892,16 @@ function parseTypedDecl(ctx, isGlobal = true) {
|
|
|
2320
2892
|
const nameToken = ctx.stream.matchKind("word");
|
|
2321
2893
|
if (!nameToken) return null;
|
|
2322
2894
|
const start = nameToken.span[0];
|
|
2323
|
-
beginElem(ctx, "typeDecl");
|
|
2324
2895
|
const decl = createDeclIdentElem(ctx, nameToken, isGlobal);
|
|
2325
|
-
ctx.addElem(decl);
|
|
2326
2896
|
ctx.saveIdent(decl.ident);
|
|
2327
2897
|
const { typeRef, typeScope } = parseOptionalType(ctx);
|
|
2328
|
-
const end = ctx.stream.checkpoint();
|
|
2329
2898
|
return {
|
|
2330
2899
|
kind: "typeDecl",
|
|
2331
2900
|
decl,
|
|
2332
2901
|
typeRef,
|
|
2333
2902
|
typeScope,
|
|
2334
2903
|
start,
|
|
2335
|
-
end
|
|
2336
|
-
contents: finishContents(ctx, start, end)
|
|
2904
|
+
end: ctx.stream.checkpoint()
|
|
2337
2905
|
};
|
|
2338
2906
|
}
|
|
2339
2907
|
/** Shared parser for const/override declarations. */
|
|
@@ -2343,43 +2911,29 @@ function parseValueDecl(ctx, keyword, requiresInit, isGlobal, attributes) {
|
|
|
2343
2911
|
if (!token) return null;
|
|
2344
2912
|
const startPos = getStartWithAttributes(attributes, token.span[0]);
|
|
2345
2913
|
ctx.pushScope("partial");
|
|
2346
|
-
beginElem(ctx, keyword, attributes);
|
|
2347
2914
|
const typedDecl = parseTypedDecl(ctx, isGlobal);
|
|
2348
2915
|
if (!typedDecl) throwParseError(stream, `Expected identifier after '${keyword}'`);
|
|
2349
|
-
|
|
2916
|
+
let init;
|
|
2350
2917
|
if (requiresInit) {
|
|
2351
2918
|
expect(stream, "=", `${keyword} identifier`);
|
|
2352
|
-
expectExpression(ctx);
|
|
2353
|
-
} else if (stream.matchText("=")) expectExpression(ctx);
|
|
2919
|
+
init = expectExpression(ctx);
|
|
2920
|
+
} else if (stream.matchText("=")) init = expectExpression(ctx);
|
|
2354
2921
|
expect(stream, ";", `${keyword} declaration`);
|
|
2355
|
-
const endPos = stream.checkpoint();
|
|
2356
|
-
const contents = finishContents(ctx, startPos, endPos);
|
|
2357
2922
|
typedDecl.decl.ident.dependentScope = ctx.currentScope();
|
|
2358
2923
|
ctx.popScope();
|
|
2359
|
-
const elem = {
|
|
2360
|
-
kind: keyword,
|
|
2924
|
+
const elem = finishStatement(keyword, startPos, ctx, {
|
|
2361
2925
|
name: typedDecl,
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
contents
|
|
2365
|
-
};
|
|
2366
|
-
attachAttributes(elem, attributes);
|
|
2926
|
+
init
|
|
2927
|
+
}, attributes);
|
|
2367
2928
|
linkDeclIdent(typedDecl, elem);
|
|
2368
2929
|
return elem;
|
|
2369
2930
|
}
|
|
2370
|
-
/** @return true if ctx is at module level (not inside fn/block) */
|
|
2371
|
-
function isModuleScope(ctx) {
|
|
2372
|
-
let scope = ctx.currentScope();
|
|
2373
|
-
while (scope.kind === "partial" && scope.parent) scope = scope.parent;
|
|
2374
|
-
return scope.parent === null;
|
|
2375
|
-
}
|
|
2376
2931
|
/** Parse optional ': type' annotation, managing scope for type references. */
|
|
2377
2932
|
function parseOptionalType(ctx) {
|
|
2378
2933
|
if (!ctx.stream.matchText(":")) return {};
|
|
2379
2934
|
ctx.pushScope();
|
|
2380
2935
|
const typeRef = parseSimpleTypeRef(ctx);
|
|
2381
2936
|
if (!typeRef) throwParseError(ctx.stream, "Expected type after ':'");
|
|
2382
|
-
ctx.addElem(typeRef);
|
|
2383
2937
|
const typeScope = ctx.currentScope();
|
|
2384
2938
|
ctx.popScope();
|
|
2385
2939
|
return {
|
|
@@ -2399,17 +2953,19 @@ function parseGlobalVarDecl(ctx, attributes) {
|
|
|
2399
2953
|
if (!varToken) return null;
|
|
2400
2954
|
const startPos = getStartWithAttributes(attributes, varToken.span[0]);
|
|
2401
2955
|
ctx.pushScope("partial");
|
|
2402
|
-
|
|
2403
|
-
skipTemplateList(ctx);
|
|
2956
|
+
const template = parseTemplateList(ctx);
|
|
2404
2957
|
const typedDecl = parseTypedDecl(ctx);
|
|
2405
2958
|
if (!typedDecl) throwParseError(stream, "Expected identifier after 'var'");
|
|
2406
|
-
|
|
2407
|
-
if (stream.matchText("=")) expectExpression(ctx);
|
|
2959
|
+
let init;
|
|
2960
|
+
if (stream.matchText("=")) init = expectExpression(ctx);
|
|
2408
2961
|
expect(stream, ";", "var declaration");
|
|
2409
2962
|
typedDecl.decl.ident.dependentScope = ctx.currentScope();
|
|
2410
2963
|
ctx.popScope();
|
|
2411
|
-
const varElem =
|
|
2412
|
-
|
|
2964
|
+
const varElem = finishStatement("gvar", startPos, ctx, {
|
|
2965
|
+
name: typedDecl,
|
|
2966
|
+
template,
|
|
2967
|
+
init
|
|
2968
|
+
}, attributes);
|
|
2413
2969
|
linkDeclIdent(typedDecl, varElem);
|
|
2414
2970
|
return varElem;
|
|
2415
2971
|
}
|
|
@@ -2419,23 +2975,19 @@ function parseAliasDecl(ctx, attributes) {
|
|
|
2419
2975
|
const aliasToken = stream.matchText("alias");
|
|
2420
2976
|
if (!aliasToken) return null;
|
|
2421
2977
|
const startPos = getStartWithAttributes(attributes, aliasToken.span[0]);
|
|
2422
|
-
beginElem(ctx, "alias", attributes);
|
|
2423
2978
|
const declIdentElem = createDeclIdentElem(ctx, expectWord(stream, "Expected identifier after 'alias'"), true);
|
|
2424
|
-
ctx.addElem(declIdentElem);
|
|
2425
2979
|
ctx.saveIdent(declIdentElem.ident);
|
|
2426
2980
|
expect(stream, "=", "alias name");
|
|
2427
2981
|
ctx.pushScope();
|
|
2428
2982
|
const typeRef = parseSimpleTypeRef(ctx);
|
|
2429
2983
|
if (!typeRef) throwParseError(stream, "Expected type after '=' in alias declaration");
|
|
2430
|
-
ctx.addElem(typeRef);
|
|
2431
2984
|
declIdentElem.ident.dependentScope = ctx.currentScope();
|
|
2432
2985
|
ctx.popScope();
|
|
2433
2986
|
expect(stream, ";", "alias declaration");
|
|
2434
|
-
const aliasElem =
|
|
2987
|
+
const aliasElem = finishStatement("alias", startPos, ctx, {
|
|
2435
2988
|
name: declIdentElem,
|
|
2436
2989
|
typeRef
|
|
2437
|
-
});
|
|
2438
|
-
attachAttributes(aliasElem, attributes);
|
|
2990
|
+
}, attributes);
|
|
2439
2991
|
linkDeclIdentElem(declIdentElem, aliasElem);
|
|
2440
2992
|
return aliasElem;
|
|
2441
2993
|
}
|
|
@@ -2444,25 +2996,29 @@ function parseConstAssert(ctx, attributes) {
|
|
|
2444
2996
|
const assertToken = ctx.stream.matchText("const_assert");
|
|
2445
2997
|
if (!assertToken) return null;
|
|
2446
2998
|
const startPos = getStartWithAttributes(attributes, assertToken.span[0]);
|
|
2447
|
-
|
|
2448
|
-
expectExpression(ctx);
|
|
2999
|
+
const expression = expectExpression(ctx);
|
|
2449
3000
|
expect(ctx.stream, ";", "const_assert expression");
|
|
2450
|
-
|
|
2451
|
-
attachAttributes(elem, attributes);
|
|
2452
|
-
return elem;
|
|
3001
|
+
return finishStatement("assert", startPos, ctx, { expression }, attributes);
|
|
2453
3002
|
}
|
|
2454
|
-
/**
|
|
2455
|
-
|
|
3003
|
+
/**
|
|
3004
|
+
* Parse an optional var template list `<storage, read_write>`. The entries are
|
|
3005
|
+
* predeclared enumerants (address space / access mode), not user idents, so they
|
|
3006
|
+
* are captured as unbound NameElems.
|
|
3007
|
+
*/
|
|
3008
|
+
function parseTemplateList(ctx) {
|
|
2456
3009
|
const { stream } = ctx;
|
|
2457
|
-
if (!stream.nextTemplateStartToken()) return;
|
|
3010
|
+
if (!stream.nextTemplateStartToken()) return void 0;
|
|
3011
|
+
const names = [];
|
|
2458
3012
|
while (true) {
|
|
2459
3013
|
const next = stream.peek();
|
|
2460
3014
|
if (!next) throwParseError(stream, "Unclosed template in var declaration");
|
|
2461
3015
|
if (next.text.startsWith(">")) {
|
|
2462
3016
|
stream.nextTemplateEndToken();
|
|
2463
|
-
return;
|
|
3017
|
+
return names;
|
|
2464
3018
|
}
|
|
2465
|
-
stream.
|
|
3019
|
+
const word = stream.matchKind("word");
|
|
3020
|
+
if (word) names.push(makeNameElem(word));
|
|
3021
|
+
else if (!stream.matchText(",")) throwParseError(stream, "Expected ',' or '>' in var template list");
|
|
2466
3022
|
}
|
|
2467
3023
|
}
|
|
2468
3024
|
//#endregion
|
|
@@ -2484,18 +3040,20 @@ function parseVarOrLet(ctx, keyword, hasTemplate, requiresInit, attributes) {
|
|
|
2484
3040
|
const token = stream.matchText(keyword);
|
|
2485
3041
|
if (!token) return null;
|
|
2486
3042
|
const startPos = getStartWithAttributes(attributes, token.span[0]);
|
|
2487
|
-
|
|
2488
|
-
if (hasTemplate) skipTemplateList(ctx);
|
|
3043
|
+
const template = hasTemplate ? parseTemplateList(ctx) : void 0;
|
|
2489
3044
|
const typedDecl = parseTypedDecl(ctx, false);
|
|
2490
3045
|
if (!typedDecl) throwParseError(stream, `Expected identifier after '${keyword}'`);
|
|
2491
|
-
|
|
3046
|
+
let init;
|
|
2492
3047
|
if (requiresInit) {
|
|
2493
3048
|
expect(stream, "=", `${keyword} identifier (${keyword} requires initialization)`);
|
|
2494
|
-
expectExpression(ctx);
|
|
2495
|
-
} else if (stream.matchText("=")) expectExpression(ctx);
|
|
3049
|
+
init = expectExpression(ctx);
|
|
3050
|
+
} else if (stream.matchText("=")) init = expectExpression(ctx);
|
|
2496
3051
|
expect(stream, ";", `${keyword} declaration`);
|
|
2497
|
-
const elem =
|
|
2498
|
-
|
|
3052
|
+
const elem = finishStatement(keyword, startPos, ctx, {
|
|
3053
|
+
name: typedDecl,
|
|
3054
|
+
init
|
|
3055
|
+
}, attributes);
|
|
3056
|
+
if (template && elem.kind === "var") elem.template = template;
|
|
2499
3057
|
linkDeclIdent(typedDecl, elem);
|
|
2500
3058
|
return elem;
|
|
2501
3059
|
}
|
|
@@ -2524,17 +3082,42 @@ const assignmentOps = new Set([
|
|
|
2524
3082
|
function parseSimpleStatement(ctx, attributes) {
|
|
2525
3083
|
const { stream } = ctx;
|
|
2526
3084
|
const startPos = getStartWithAttributes(attributes, stream.checkpoint());
|
|
2527
|
-
return parseReturnStmt(ctx, startPos, attributes) || parseBreakStmt(ctx, startPos, attributes) ||
|
|
3085
|
+
return parseReturnStmt(ctx, startPos, attributes) || parseBreakStmt(ctx, startPos, attributes) || parseContinueStmt(ctx, startPos, attributes) || parseDiscardStmt(ctx, startPos, attributes) || parseEmptyStmt(stream, startPos) || parsePhonyAssignment(ctx, startPos, attributes) || parseExpressionStmt(ctx, startPos, attributes);
|
|
3086
|
+
}
|
|
3087
|
+
/** Match an assignment operator, capturing its value and span. */
|
|
3088
|
+
function parseAssignmentOp(stream) {
|
|
3089
|
+
const token = stream.nextIf(({ text }) => assignmentOps.has(text));
|
|
3090
|
+
if (!token) return null;
|
|
3091
|
+
return {
|
|
3092
|
+
value: token.text,
|
|
3093
|
+
span: token.span
|
|
3094
|
+
};
|
|
3095
|
+
}
|
|
3096
|
+
/** Match '++' or '--', returning its text and span (or null if absent). */
|
|
3097
|
+
function parseIncDecOp(stream) {
|
|
3098
|
+
const token = stream.nextIf(({ text }) => text === "++" || text === "--");
|
|
3099
|
+
if (!token) return null;
|
|
3100
|
+
return {
|
|
3101
|
+
op: token.text,
|
|
3102
|
+
span: token.span
|
|
3103
|
+
};
|
|
3104
|
+
}
|
|
3105
|
+
/** Grammar: ( '=' | compound_assignment_operator ) expression. Returns op + rhs. */
|
|
3106
|
+
function parseAssignmentRhs(ctx) {
|
|
3107
|
+
const op = parseAssignmentOp(ctx.stream);
|
|
3108
|
+
if (!op) return null;
|
|
3109
|
+
return {
|
|
3110
|
+
op,
|
|
3111
|
+
rhs: expectExpression(ctx, "Expected expression after assignment operator")
|
|
3112
|
+
};
|
|
2528
3113
|
}
|
|
2529
3114
|
/** Grammar: return_statement : 'return' expression? ';' */
|
|
2530
3115
|
function parseReturnStmt(ctx, startPos, attributes) {
|
|
2531
3116
|
const { stream } = ctx;
|
|
2532
3117
|
if (!stream.matchText("return")) return null;
|
|
2533
|
-
|
|
2534
|
-
const expr = parseExpression(ctx);
|
|
2535
|
-
if (expr && ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
3118
|
+
const value = parseContentExpression(ctx) ?? void 0;
|
|
2536
3119
|
expect(stream, ";", "return statement");
|
|
2537
|
-
return
|
|
3120
|
+
return finishStatement("return", startPos, ctx, { value }, attributes);
|
|
2538
3121
|
}
|
|
2539
3122
|
/**
|
|
2540
3123
|
* Grammar: break_statement : 'break' ';'
|
|
@@ -2543,70 +3126,79 @@ function parseReturnStmt(ctx, startPos, attributes) {
|
|
|
2543
3126
|
function parseBreakStmt(ctx, startPos, attributes) {
|
|
2544
3127
|
const { stream } = ctx;
|
|
2545
3128
|
if (!stream.matchText("break")) return null;
|
|
2546
|
-
|
|
2547
|
-
if (stream.matchText("if")) expectExpression(ctx, "Expected condition after 'break if'");
|
|
3129
|
+
let condition;
|
|
3130
|
+
if (stream.matchText("if")) condition = expectExpression(ctx, "Expected condition after 'break if'");
|
|
2548
3131
|
expect(stream, ";", "break statement");
|
|
2549
|
-
return
|
|
3132
|
+
return finishStatement("break", startPos, ctx, { condition }, attributes);
|
|
3133
|
+
}
|
|
3134
|
+
/** Grammar: continue_statement : 'continue' ';' */
|
|
3135
|
+
function parseContinueStmt(ctx, startPos, attributes) {
|
|
3136
|
+
const { stream } = ctx;
|
|
3137
|
+
if (!stream.matchText("continue")) return null;
|
|
3138
|
+
expect(stream, ";", "continue statement");
|
|
3139
|
+
return finishStatement("continue", startPos, ctx, {}, attributes);
|
|
2550
3140
|
}
|
|
2551
|
-
/** Grammar:
|
|
2552
|
-
function
|
|
3141
|
+
/** Grammar: 'discard' ';' */
|
|
3142
|
+
function parseDiscardStmt(ctx, startPos, attributes) {
|
|
2553
3143
|
const { stream } = ctx;
|
|
2554
|
-
if (!stream.matchText(
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
return finishBlockStatement(startPos, ctx, attributes);
|
|
3144
|
+
if (!stream.matchText("discard")) return null;
|
|
3145
|
+
expect(stream, ";", "discard statement");
|
|
3146
|
+
return finishStatement("discard", startPos, ctx, {}, attributes);
|
|
2558
3147
|
}
|
|
2559
|
-
/** Parse empty statement (just ';'). */
|
|
3148
|
+
/** Parse empty statement (just ';'). Spans the ';' so it emits no extra text. */
|
|
2560
3149
|
function parseEmptyStmt(stream, start) {
|
|
2561
3150
|
if (!stream.matchText(";")) return null;
|
|
2562
3151
|
return {
|
|
2563
|
-
kind: "
|
|
3152
|
+
kind: "empty",
|
|
2564
3153
|
start,
|
|
2565
|
-
end: stream.checkpoint()
|
|
2566
|
-
contents: []
|
|
3154
|
+
end: stream.checkpoint()
|
|
2567
3155
|
};
|
|
2568
3156
|
}
|
|
2569
3157
|
/** Grammar: assignment_statement : '_' '=' expression ';' (phony assignment) */
|
|
2570
3158
|
function parsePhonyAssignment(ctx, startPos, attributes) {
|
|
2571
3159
|
const { stream } = ctx;
|
|
2572
|
-
|
|
2573
|
-
if (!
|
|
2574
|
-
|
|
2575
|
-
|
|
3160
|
+
const underscore = stream.matchText("_");
|
|
3161
|
+
if (!underscore) return null;
|
|
3162
|
+
const lhs = {
|
|
3163
|
+
kind: "phony",
|
|
3164
|
+
span: underscore.span
|
|
3165
|
+
};
|
|
3166
|
+
const eq = stream.matchText("=");
|
|
3167
|
+
if (!eq) throwParseError(stream, "Expected '=' after '_'");
|
|
3168
|
+
const op = {
|
|
3169
|
+
value: "=",
|
|
3170
|
+
span: eq.span
|
|
3171
|
+
};
|
|
3172
|
+
const rhs = expectExpression(ctx, "Expected expression after '_ ='");
|
|
2576
3173
|
expect(stream, ";", "assignment");
|
|
2577
|
-
return
|
|
3174
|
+
return finishStatement("assign", startPos, ctx, {
|
|
3175
|
+
lhs,
|
|
3176
|
+
op,
|
|
3177
|
+
rhs
|
|
3178
|
+
}, attributes);
|
|
2578
3179
|
}
|
|
2579
|
-
/**
|
|
2580
|
-
* Parses expression statements: assignments, increments/decrements, or function calls.
|
|
2581
|
-
* Grammar: ( assignment_statement | increment_statement | decrement_statement | call_phrase ) ';'
|
|
2582
|
-
*/
|
|
3180
|
+
/** Grammar: ( assignment_statement | increment_statement | decrement_statement | call_phrase ) ';' */
|
|
2583
3181
|
function parseExpressionStmt(ctx, startPos, attributes) {
|
|
2584
3182
|
const { stream } = ctx;
|
|
2585
|
-
beginElem(ctx, "statement", attributes);
|
|
2586
3183
|
const expr = parseExpression(ctx);
|
|
2587
3184
|
if (!expr) {
|
|
2588
|
-
finishContents(ctx, startPos, startPos);
|
|
2589
3185
|
stream.reset(startPos);
|
|
2590
3186
|
return null;
|
|
2591
3187
|
}
|
|
2592
|
-
|
|
2593
|
-
if (
|
|
3188
|
+
const incDec = parseIncDecOp(stream);
|
|
3189
|
+
if (incDec) {
|
|
3190
|
+
expect(stream, ";", "expression");
|
|
3191
|
+
return finishStatement(incDec.op === "++" ? "increment" : "decrement", startPos, ctx, { target: expr }, attributes);
|
|
3192
|
+
}
|
|
3193
|
+
const assign = parseAssignmentRhs(ctx);
|
|
2594
3194
|
expect(stream, ";", "expression");
|
|
2595
|
-
return
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
function parseAssignmentRhs(ctx) {
|
|
2603
|
-
if (!parseAssignmentOperator(ctx.stream)) return false;
|
|
2604
|
-
expectExpression(ctx, "Expected expression after assignment operator");
|
|
2605
|
-
return true;
|
|
2606
|
-
}
|
|
2607
|
-
/** Grammar: increment_statement : lhs_expression '++' ; decrement_statement : lhs_expression '--' */
|
|
2608
|
-
function parseIncDecOperator(stream) {
|
|
2609
|
-
return !!stream.nextIf(({ text }) => text === "++" || text === "--");
|
|
3195
|
+
if (assign) return finishStatement("assign", startPos, ctx, {
|
|
3196
|
+
lhs: expr,
|
|
3197
|
+
op: assign.op,
|
|
3198
|
+
rhs: assign.rhs
|
|
3199
|
+
}, attributes);
|
|
3200
|
+
if (expr.kind !== "call-expression") throwParseError(stream, "Expected call, assignment, or increment");
|
|
3201
|
+
return finishStatement("call", startPos, ctx, { call: expr }, attributes);
|
|
2610
3202
|
}
|
|
2611
3203
|
//#endregion
|
|
2612
3204
|
//#region src/parse/ParseLoop.ts
|
|
@@ -2620,41 +3212,48 @@ function parseForStatement(ctx, attributes) {
|
|
|
2620
3212
|
if (startPos === null) return null;
|
|
2621
3213
|
ctx.pushScope();
|
|
2622
3214
|
expect(stream, "(", "'for'");
|
|
2623
|
-
parseForInit(ctx);
|
|
2624
|
-
const
|
|
2625
|
-
if (cond && ctx.options.preserveExpressions) ctx.addElem(cond);
|
|
3215
|
+
const init = parseForInit(ctx);
|
|
3216
|
+
const condition = parseContentExpression(ctx) ?? void 0;
|
|
2626
3217
|
expect(stream, ";", "for loop condition");
|
|
2627
|
-
parseForUpdate(ctx);
|
|
3218
|
+
const update = parseForUpdate(ctx);
|
|
2628
3219
|
expect(stream, ")", "for loop header");
|
|
2629
3220
|
const body = expectCompound(ctx, "Expected '{' after for loop header");
|
|
2630
|
-
ctx.addElem(body);
|
|
2631
3221
|
ctx.popScope();
|
|
2632
|
-
return
|
|
3222
|
+
return finishStatement("for", startPos, ctx, {
|
|
3223
|
+
init,
|
|
3224
|
+
condition,
|
|
3225
|
+
update,
|
|
3226
|
+
body
|
|
3227
|
+
}, attributes);
|
|
2633
3228
|
}
|
|
2634
3229
|
/** Grammar: while_statement : attribute* 'while' expression compound_statement */
|
|
2635
3230
|
function parseWhileStatement(ctx, attributes) {
|
|
2636
3231
|
const startPos = beginStatement(ctx, "while", attributes);
|
|
2637
3232
|
if (startPos === null) return null;
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
3233
|
+
return finishStatement("while", startPos, ctx, {
|
|
3234
|
+
condition: expectExpression(ctx, "Expected condition after 'while'"),
|
|
3235
|
+
body: expectCompound(ctx, "Expected '{' after while condition")
|
|
3236
|
+
}, attributes);
|
|
2642
3237
|
}
|
|
2643
3238
|
/** Grammar: loop_statement : attribute* 'loop' attribute* '{' statement* continuing_statement? '}' */
|
|
2644
3239
|
function parseLoopStatement(ctx, attributes) {
|
|
2645
3240
|
const startPos = beginStatement(ctx, "loop", attributes);
|
|
2646
3241
|
if (startPos === null) return null;
|
|
2647
3242
|
const body = expectCompound(ctx, "Expected '{' after 'loop'", true);
|
|
2648
|
-
ctx
|
|
2649
|
-
|
|
3243
|
+
return finishStatement("loop", startPos, ctx, {
|
|
3244
|
+
body,
|
|
3245
|
+
continuing: body.body.find((s) => s.kind === "continuing")
|
|
3246
|
+
}, attributes);
|
|
2650
3247
|
}
|
|
2651
3248
|
/** Grammar: continuing_statement : 'continuing' continuing_compound_statement */
|
|
2652
3249
|
function parseContinuingStatement(ctx, attributes) {
|
|
2653
|
-
const startPos = beginStatement(ctx, "continuing", attributes
|
|
3250
|
+
const startPos = beginStatement(ctx, "continuing", attributes);
|
|
2654
3251
|
if (startPos === null) return null;
|
|
2655
3252
|
const body = expectCompound(ctx, "Expected '{' after 'continuing'");
|
|
2656
|
-
ctx
|
|
2657
|
-
|
|
3253
|
+
return finishStatement("continuing", startPos, ctx, {
|
|
3254
|
+
body,
|
|
3255
|
+
breakIf: body.body.find((s) => s.kind === "break")?.condition
|
|
3256
|
+
}, attributes);
|
|
2658
3257
|
}
|
|
2659
3258
|
/** Grammar: for_init? ';'
|
|
2660
3259
|
* for_init : variable_or_value_statement | variable_updating_statement | func_call_statement
|
|
@@ -2662,19 +3261,60 @@ function parseContinuingStatement(ctx, attributes) {
|
|
|
2662
3261
|
function parseForInit(ctx) {
|
|
2663
3262
|
const { stream } = ctx;
|
|
2664
3263
|
const varDecl = parseLocalVarDecl(ctx);
|
|
2665
|
-
if (varDecl)
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
}
|
|
3264
|
+
if (varDecl) return varDecl;
|
|
3265
|
+
const expr = parseContentExpression(ctx);
|
|
3266
|
+
const update = expr ? finishForUpdate(ctx, expr) : void 0;
|
|
3267
|
+
expect(stream, ";", "for loop init");
|
|
3268
|
+
return update;
|
|
2671
3269
|
}
|
|
2672
|
-
/** Grammar: for_update : variable_updating_statement | func_call_statement
|
|
2673
|
-
* variable_updating_statement : assignment_statement | increment_statement | decrement_statement */
|
|
3270
|
+
/** Grammar: for_update : variable_updating_statement | func_call_statement */
|
|
2674
3271
|
function parseForUpdate(ctx) {
|
|
2675
|
-
const expr =
|
|
2676
|
-
if (expr
|
|
2677
|
-
|
|
3272
|
+
const expr = parseContentExpression(ctx);
|
|
3273
|
+
if (!expr) return void 0;
|
|
3274
|
+
return finishForUpdate(ctx, expr);
|
|
3275
|
+
}
|
|
3276
|
+
/**
|
|
3277
|
+
* Build a typed for-init/for-update node from an already-parsed lhs expression,
|
|
3278
|
+
* consuming its trailing operator (++/-- or an assignment + rhs).
|
|
3279
|
+
*/
|
|
3280
|
+
function finishForUpdate(ctx, expr) {
|
|
3281
|
+
const { stream } = ctx;
|
|
3282
|
+
const start = expr.start;
|
|
3283
|
+
const incDec = parseIncDecOp(stream);
|
|
3284
|
+
if (incDec) {
|
|
3285
|
+
const end = stream.checkpoint();
|
|
3286
|
+
if (incDec.op === "++") return {
|
|
3287
|
+
kind: "increment",
|
|
3288
|
+
target: expr,
|
|
3289
|
+
start,
|
|
3290
|
+
end
|
|
3291
|
+
};
|
|
3292
|
+
return {
|
|
3293
|
+
kind: "decrement",
|
|
3294
|
+
target: expr,
|
|
3295
|
+
start,
|
|
3296
|
+
end
|
|
3297
|
+
};
|
|
3298
|
+
}
|
|
3299
|
+
const assign = parseAssignmentRhs(ctx);
|
|
3300
|
+
if (assign) {
|
|
3301
|
+
const { op, rhs } = assign;
|
|
3302
|
+
return {
|
|
3303
|
+
kind: "assign",
|
|
3304
|
+
lhs: expr,
|
|
3305
|
+
op,
|
|
3306
|
+
rhs,
|
|
3307
|
+
start,
|
|
3308
|
+
end: stream.checkpoint()
|
|
3309
|
+
};
|
|
3310
|
+
}
|
|
3311
|
+
if (expr.kind === "call-expression") return {
|
|
3312
|
+
kind: "call",
|
|
3313
|
+
call: expr,
|
|
3314
|
+
start,
|
|
3315
|
+
end: stream.checkpoint()
|
|
3316
|
+
};
|
|
3317
|
+
throwParseError(stream, "Expected an assignment, increment, decrement, or call in for-clause");
|
|
2678
3318
|
}
|
|
2679
3319
|
//#endregion
|
|
2680
3320
|
//#region src/parse/ParseStatement.ts
|
|
@@ -2690,17 +3330,17 @@ function parseCompoundStatement(ctx, attributes, options) {
|
|
|
2690
3330
|
const brace = ctx.stream.matchText("{");
|
|
2691
3331
|
if (!brace) return null;
|
|
2692
3332
|
const startPos = getStartWithAttributes(attributes, brace.span[0]);
|
|
2693
|
-
beginElem(ctx, "statement", attributes);
|
|
2694
3333
|
const skipScope = options?.noScope || hasConditionalAttr(attributes);
|
|
2695
3334
|
if (!skipScope) ctx.pushScope();
|
|
2696
|
-
parseBlockStatements(ctx, options?.loopBody);
|
|
3335
|
+
const body = parseBlockStatements(ctx, options?.loopBody);
|
|
2697
3336
|
if (!skipScope) ctx.popScope();
|
|
2698
|
-
return
|
|
3337
|
+
return finishStatement("block", startPos, ctx, { body }, attributes);
|
|
2699
3338
|
}
|
|
2700
3339
|
/** Grammar: attribute* compound_statement (for control flow bodies) */
|
|
2701
3340
|
function expectCompound(ctx, errorMsg, loopBody) {
|
|
2702
3341
|
const attrs = parseAttributeList(ctx);
|
|
2703
|
-
const
|
|
3342
|
+
const options = loopBody ? { loopBody } : void 0;
|
|
3343
|
+
const block = parseCompoundStatement(ctx, attrsOrUndef(attrs), options);
|
|
2704
3344
|
if (!block) throwParseError(ctx.stream, errorMsg);
|
|
2705
3345
|
return block;
|
|
2706
3346
|
}
|
|
@@ -2708,16 +3348,20 @@ function expectCompound(ctx, errorMsg, loopBody) {
|
|
|
2708
3348
|
function getStartWithAttributes(attributes, keywordPos) {
|
|
2709
3349
|
return attributes?.[0]?.start ?? keywordPos;
|
|
2710
3350
|
}
|
|
2711
|
-
/** Match keyword and
|
|
2712
|
-
function beginStatement(ctx, keyword, attributes
|
|
2713
|
-
const
|
|
2714
|
-
if (!
|
|
2715
|
-
|
|
2716
|
-
beginElem(ctx, kind, attributes);
|
|
2717
|
-
return startPos;
|
|
3351
|
+
/** Match keyword and return the statement's start position (or null if no match). */
|
|
3352
|
+
function beginStatement(ctx, keyword, attributes) {
|
|
3353
|
+
const token = ctx.stream.matchText(keyword);
|
|
3354
|
+
if (!token) return null;
|
|
3355
|
+
return getStartWithAttributes(attributes, token.span[0]);
|
|
2718
3356
|
}
|
|
2719
|
-
|
|
2720
|
-
|
|
3357
|
+
/** Build a statement element from its typed fields and attach its attributes. */
|
|
3358
|
+
function finishStatement(kind, start, ctx, params, attributes) {
|
|
3359
|
+
const elem = {
|
|
3360
|
+
kind,
|
|
3361
|
+
start,
|
|
3362
|
+
end: ctx.stream.checkpoint(),
|
|
3363
|
+
...params
|
|
3364
|
+
};
|
|
2721
3365
|
attachAttributes(elem, attributes);
|
|
2722
3366
|
return elem;
|
|
2723
3367
|
}
|
|
@@ -2727,16 +3371,18 @@ function hasConditionalAttr(attributes) {
|
|
|
2727
3371
|
/** Grammar: statement* '}' (after '{' consumed). Loop bodies may end with continuing. */
|
|
2728
3372
|
function parseBlockStatements(ctx, loopBody) {
|
|
2729
3373
|
const { stream } = ctx;
|
|
3374
|
+
const body = [];
|
|
2730
3375
|
while (true) {
|
|
2731
3376
|
if (stream.matchText("}")) break;
|
|
2732
3377
|
const stmt = parseStatement(ctx);
|
|
2733
3378
|
if (!stmt) throwParseError(stream, "Expected statement or '}'");
|
|
2734
|
-
|
|
3379
|
+
body.push(stmt);
|
|
2735
3380
|
if (loopBody && stmt.kind === "continuing") {
|
|
2736
3381
|
expect(stream, "}", "continuing block");
|
|
2737
3382
|
break;
|
|
2738
3383
|
}
|
|
2739
3384
|
}
|
|
3385
|
+
return body;
|
|
2740
3386
|
}
|
|
2741
3387
|
/**
|
|
2742
3388
|
* Grammar: statement :
|
|
@@ -2757,7 +3403,6 @@ function parseStatement(ctx) {
|
|
|
2757
3403
|
}
|
|
2758
3404
|
const hasConditional = attributes.length > 0 && hasConditionalAttribute(attributes);
|
|
2759
3405
|
if (hasConditional) ctx.pushScope("partial");
|
|
2760
|
-
const attrsOrUndef = attributes.length > 0 ? attributes : void 0;
|
|
2761
3406
|
const stmt = findMap([
|
|
2762
3407
|
parseLocalVarDecl,
|
|
2763
3408
|
parseLetDecl,
|
|
@@ -2771,19 +3416,71 @@ function parseStatement(ctx) {
|
|
|
2771
3416
|
parseLoopStatement,
|
|
2772
3417
|
parseContinuingStatement,
|
|
2773
3418
|
parseSimpleStatement
|
|
2774
|
-
], (p) => p(ctx, attrsOrUndef));
|
|
2775
|
-
if (!stmt) return null;
|
|
2776
|
-
finalizeConditional$1(ctx, hasConditional, attributes);
|
|
2777
|
-
return stmt;
|
|
2778
|
-
}
|
|
2779
|
-
function finalizeConditional$1(ctx, hasConditional, attributes) {
|
|
3419
|
+
], (p) => p(ctx, attrsOrUndef(attributes)));
|
|
2780
3420
|
if (hasConditional) {
|
|
2781
3421
|
const partialScope = ctx.popScope();
|
|
2782
|
-
partialScope.condAttribute =
|
|
3422
|
+
if (stmt) partialScope.condAttribute = conditionalAttribute(attributes);
|
|
2783
3423
|
}
|
|
3424
|
+
return stmt ? stmt : null;
|
|
3425
|
+
}
|
|
3426
|
+
//#endregion
|
|
3427
|
+
//#region src/parse/ParseDirective.ts
|
|
3428
|
+
/** Grammar: global_directive : diagnostic_directive | enable_directive | requires_directive */
|
|
3429
|
+
function parseDirective(ctx) {
|
|
3430
|
+
const { stream } = ctx;
|
|
3431
|
+
const startPos = stream.checkpoint();
|
|
3432
|
+
const attrs = attrsOrUndef(parseAttributeList(ctx));
|
|
3433
|
+
const result = parseExtensionDirective(ctx, "enable", attrs) || parseExtensionDirective(ctx, "requires", attrs) || parseDiagnosticDirective(ctx, attrs);
|
|
3434
|
+
if (!result) stream.reset(startPos);
|
|
3435
|
+
return result;
|
|
3436
|
+
}
|
|
3437
|
+
/** Grammar: enable_directive | requires_directive : keyword extension_list ';' */
|
|
3438
|
+
function parseExtensionDirective(ctx, keyword, attributes) {
|
|
3439
|
+
const { stream } = ctx;
|
|
3440
|
+
const token = stream.matchText(keyword);
|
|
3441
|
+
if (!token) return null;
|
|
3442
|
+
const extensions = parseCommaList(ctx, parseDirectiveName);
|
|
3443
|
+
expect(stream, ";", `${keyword} directive`);
|
|
3444
|
+
return makeDirectiveElem({
|
|
3445
|
+
kind: keyword,
|
|
3446
|
+
extensions
|
|
3447
|
+
}, token, stream, attributes);
|
|
3448
|
+
}
|
|
3449
|
+
/**
|
|
3450
|
+
* Grammar: diagnostic_directive : 'diagnostic' diagnostic_control ';'
|
|
3451
|
+
* Grammar: diagnostic_control : '(' severity_control_name ',' diagnostic_rule_name ',' ? ')'
|
|
3452
|
+
*/
|
|
3453
|
+
function parseDiagnosticDirective(ctx, attributes) {
|
|
3454
|
+
const { stream } = ctx;
|
|
3455
|
+
const token = stream.matchText("diagnostic");
|
|
3456
|
+
if (!token) return null;
|
|
3457
|
+
expect(stream, "(", "diagnostic");
|
|
3458
|
+
const severity = makeNameElem(expectWord(stream, "Expected severity in diagnostic"));
|
|
3459
|
+
expect(stream, ",", "diagnostic severity");
|
|
3460
|
+
const ruleName = makeNameElem(expectWord(stream, "Expected rule name in diagnostic"));
|
|
3461
|
+
let subrule = null;
|
|
3462
|
+
if (stream.matchText(".")) subrule = makeNameElem(expectWord(stream, "Expected subrule name after '.'"));
|
|
3463
|
+
stream.matchText(",");
|
|
3464
|
+
expect(stream, ")", "diagnostic rule");
|
|
3465
|
+
expect(stream, ";", "diagnostic directive");
|
|
3466
|
+
return makeDirectiveElem({
|
|
3467
|
+
kind: "diagnostic",
|
|
3468
|
+
severity,
|
|
3469
|
+
rule: [ruleName, subrule]
|
|
3470
|
+
}, token, stream, attributes);
|
|
3471
|
+
}
|
|
3472
|
+
function parseDirectiveName(ctx) {
|
|
3473
|
+
return makeNameElem(expectWord(ctx.stream, "Expected identifier in name list"));
|
|
2784
3474
|
}
|
|
2785
|
-
function
|
|
2786
|
-
|
|
3475
|
+
function makeDirectiveElem(directive, token, stream, attributes) {
|
|
3476
|
+
const elem = {
|
|
3477
|
+
kind: "directive",
|
|
3478
|
+
directive,
|
|
3479
|
+
start: getStartWithAttributes(attributes, token.span[0]),
|
|
3480
|
+
end: stream.checkpoint()
|
|
3481
|
+
};
|
|
3482
|
+
attachAttributes(elem, attributes);
|
|
3483
|
+
return elem;
|
|
2787
3484
|
}
|
|
2788
3485
|
//#endregion
|
|
2789
3486
|
//#region src/parse/ParseFn.ts
|
|
@@ -2820,8 +3517,7 @@ function parseFnDecl(ctx, attributes) {
|
|
|
2820
3517
|
returnType,
|
|
2821
3518
|
returnAttributes,
|
|
2822
3519
|
start: startPos,
|
|
2823
|
-
end: stream.checkpoint()
|
|
2824
|
-
contents: buildFnContents(attributes, declIdentElem, params, returnType, body)
|
|
3520
|
+
end: stream.checkpoint()
|
|
2825
3521
|
};
|
|
2826
3522
|
attachAttributes(fnElem, attributes);
|
|
2827
3523
|
linkDeclIdentElem(declIdentElem, fnElem);
|
|
@@ -2851,37 +3547,63 @@ function parseFnReturn(ctx) {
|
|
|
2851
3547
|
if (!returnType) throwParseError(stream, "Expected type after '->'");
|
|
2852
3548
|
return {
|
|
2853
3549
|
returnType,
|
|
2854
|
-
returnAttributes: attrs
|
|
3550
|
+
returnAttributes: attrsOrUndef(attrs)
|
|
2855
3551
|
};
|
|
2856
3552
|
}
|
|
2857
|
-
/** Build contents array for function element */
|
|
2858
|
-
function buildFnContents(attributes, decl, params, returnType, body) {
|
|
2859
|
-
const base = returnType ? [
|
|
2860
|
-
decl,
|
|
2861
|
-
...params,
|
|
2862
|
-
returnType,
|
|
2863
|
-
body
|
|
2864
|
-
] : [
|
|
2865
|
-
decl,
|
|
2866
|
-
...params,
|
|
2867
|
-
body
|
|
2868
|
-
];
|
|
2869
|
-
return attributes?.length ? [...attributes, ...base] : base;
|
|
2870
|
-
}
|
|
2871
3553
|
/** Grammar: param : attribute* optionally_typed_ident */
|
|
2872
3554
|
function parseFnParam(ctx) {
|
|
2873
|
-
const
|
|
3555
|
+
const attrs = parseAttributeList(ctx);
|
|
2874
3556
|
if (ctx.stream.peek()?.kind !== "word") return null;
|
|
2875
|
-
|
|
3557
|
+
const attributes = attrsOrUndef(attrs);
|
|
2876
3558
|
const name = parseTypedDecl(ctx, false);
|
|
2877
3559
|
if (!name) throw new Error("Unexpected: peek succeeded but parseTypedDecl failed");
|
|
2878
|
-
|
|
2879
|
-
const elem = finishElem("param", getStartWithAttributes(attributes, name.start), ctx, { name });
|
|
3560
|
+
const elem = finishStatement("param", getStartWithAttributes(attributes, name.start), ctx, { name }, attributes);
|
|
2880
3561
|
linkDeclIdent(name, elem);
|
|
2881
|
-
attachAttributes(elem, attributes.length > 0 ? attributes : void 0);
|
|
2882
3562
|
return elem;
|
|
2883
3563
|
}
|
|
2884
3564
|
//#endregion
|
|
3565
|
+
//#region src/parse/ParseDoBlock.ts
|
|
3566
|
+
/**
|
|
3567
|
+
* Grammar: do_block_decl : attribute* 'do' ident '(' param_list? ')' compound_statement
|
|
3568
|
+
*
|
|
3569
|
+
* `do` is a module-level declaration when the `doBlocks` extension is on;
|
|
3570
|
+
* otherwise it stays a reserved word. The body is parsed structurally but
|
|
3571
|
+
* inside a scope that is detached from the module scope graph, so its idents
|
|
3572
|
+
* never reach bindIdents. do blocks are module-local, resolved later by AST
|
|
3573
|
+
* name match.
|
|
3574
|
+
*/
|
|
3575
|
+
function parseDoBlock(ctx, attributes) {
|
|
3576
|
+
if (!ctx.options.weslExtensions?.doBlocks) return null;
|
|
3577
|
+
const { stream } = ctx;
|
|
3578
|
+
const doToken = stream.matchText("do");
|
|
3579
|
+
if (!doToken) return null;
|
|
3580
|
+
const startPos = getStartWithAttributes(attributes, doToken.span[0]);
|
|
3581
|
+
const name = makeNameElem(expectWord(stream, "Expected identifier after 'do'"));
|
|
3582
|
+
const parentScope = ctx.currentScope();
|
|
3583
|
+
ctx.pushScope();
|
|
3584
|
+
const doScope = ctx.currentScope();
|
|
3585
|
+
const params = parseFnParams(ctx);
|
|
3586
|
+
const body = parseFunctionBody(ctx);
|
|
3587
|
+
if (!body) throwParseError(stream, "Expected do block body");
|
|
3588
|
+
ctx.popScope();
|
|
3589
|
+
detachScope(parentScope, doScope);
|
|
3590
|
+
const doBlock = {
|
|
3591
|
+
kind: "do",
|
|
3592
|
+
name,
|
|
3593
|
+
params,
|
|
3594
|
+
body,
|
|
3595
|
+
start: startPos,
|
|
3596
|
+
end: stream.checkpoint()
|
|
3597
|
+
};
|
|
3598
|
+
attachAttributes(doBlock, attributes);
|
|
3599
|
+
return doBlock;
|
|
3600
|
+
}
|
|
3601
|
+
/** Remove a child scope from its parent so bindIdents never traverses it. */
|
|
3602
|
+
function detachScope(parent, child) {
|
|
3603
|
+
const i = parent.contents.indexOf(child);
|
|
3604
|
+
if (i >= 0) parent.contents.splice(i, 1);
|
|
3605
|
+
}
|
|
3606
|
+
//#endregion
|
|
2885
3607
|
//#region src/parse/ParseImport.ts
|
|
2886
3608
|
/** WESL Grammar: translation_unit : import_statement* global_directive* global_decl* */
|
|
2887
3609
|
function parseWeslImports(ctx) {
|
|
@@ -3019,52 +3741,39 @@ function parseStructDecl(ctx, attributes) {
|
|
|
3019
3741
|
const start = getStartWithAttributes(attributes, structToken.span[0]);
|
|
3020
3742
|
const identElem = createDeclIdentElem(ctx, expectWord(stream, "Expected identifier after 'struct'"), true);
|
|
3021
3743
|
ctx.saveIdent(identElem.ident);
|
|
3022
|
-
beginElem(ctx, "struct", attributes);
|
|
3023
|
-
ctx.addElem(identElem);
|
|
3024
3744
|
expect(stream, "{", "struct name");
|
|
3025
3745
|
ctx.pushScope();
|
|
3026
|
-
const members =
|
|
3746
|
+
const members = parseCommaList(ctx, parseStructMember);
|
|
3027
3747
|
identElem.ident.dependentScope = ctx.currentScope();
|
|
3028
3748
|
ctx.popScope();
|
|
3029
3749
|
expect(stream, "}", "struct member");
|
|
3030
|
-
const elem =
|
|
3750
|
+
const elem = finishStatement("struct", start, ctx, {
|
|
3031
3751
|
name: identElem,
|
|
3032
3752
|
members
|
|
3033
|
-
});
|
|
3034
|
-
attachAttributes(elem, attributes);
|
|
3753
|
+
}, attributes);
|
|
3035
3754
|
linkDeclIdentElem(identElem, elem);
|
|
3036
3755
|
return elem;
|
|
3037
3756
|
}
|
|
3038
|
-
/** Grammar: struct_body_decl : '{' struct_member (',' struct_member)* ','? '}' */
|
|
3039
|
-
function parseStructMembers(ctx) {
|
|
3040
|
-
const members = parseCommaList(ctx, parseStructMember);
|
|
3041
|
-
for (const member of members) ctx.addElem(member);
|
|
3042
|
-
return members;
|
|
3043
|
-
}
|
|
3044
3757
|
/** Grammar: struct_member : attribute* member_ident ':' type_specifier */
|
|
3045
3758
|
function parseStructMember(ctx) {
|
|
3046
3759
|
const { stream } = ctx;
|
|
3047
3760
|
const checkpoint = stream.checkpoint();
|
|
3048
|
-
const
|
|
3761
|
+
const attrs = parseAttributeList(ctx);
|
|
3049
3762
|
const nameToken = stream.matchKind("word");
|
|
3050
3763
|
if (!nameToken) {
|
|
3051
3764
|
stream.reset(checkpoint);
|
|
3052
3765
|
return null;
|
|
3053
3766
|
}
|
|
3767
|
+
const attributes = attrsOrUndef(attrs);
|
|
3054
3768
|
const start = getStartWithAttributes(attributes, nameToken.span[0]);
|
|
3055
|
-
beginElem(ctx, "member", attributes.length ? attributes : void 0);
|
|
3056
3769
|
const name = makeNameElem(nameToken);
|
|
3057
|
-
ctx.addElem(name);
|
|
3058
3770
|
expect(stream, ":", "struct member name");
|
|
3059
3771
|
const typeRef = parseSimpleTypeRef(ctx);
|
|
3060
3772
|
if (!typeRef) throwParseError(stream, "Expected type after ':'");
|
|
3061
|
-
ctx
|
|
3062
|
-
const elem = finishElem("member", start, ctx, {
|
|
3773
|
+
return finishStatement("member", start, ctx, {
|
|
3063
3774
|
name,
|
|
3064
3775
|
typeRef
|
|
3065
|
-
});
|
|
3066
|
-
attachAttributes(elem, attributes.length ? attributes : void 0);
|
|
3067
|
-
return elem;
|
|
3776
|
+
}, attributes);
|
|
3068
3777
|
}
|
|
3069
3778
|
//#endregion
|
|
3070
3779
|
//#region src/parse/ParseModule.ts
|
|
@@ -3075,6 +3784,7 @@ const declParsers = [
|
|
|
3075
3784
|
parseAliasDecl,
|
|
3076
3785
|
parseStructDecl,
|
|
3077
3786
|
parseFnDecl,
|
|
3787
|
+
parseDoBlock,
|
|
3078
3788
|
parseConstAssert
|
|
3079
3789
|
];
|
|
3080
3790
|
/** Grammar: translation_unit : global_directive* ( global_decl | global_assert | ';' )* */
|
|
@@ -3082,19 +3792,39 @@ function parseModule(ctx) {
|
|
|
3082
3792
|
parseImports(ctx);
|
|
3083
3793
|
parseDirectives(ctx);
|
|
3084
3794
|
while (parseNextDeclaration(ctx));
|
|
3795
|
+
if (ctx.stream.peek() !== null) throwParseError(ctx.stream, "Expected a declaration or directive");
|
|
3796
|
+
}
|
|
3797
|
+
/**
|
|
3798
|
+
* Reject a module that gives a `do` block a name that clashes with a fn/global
|
|
3799
|
+
* or with another `do` block (`do` blocks share the module's declaration
|
|
3800
|
+
* namespace, and runners key blocks by name so a duplicate would silently
|
|
3801
|
+
* shadow the earlier one). This is a small module-local pass, deliberately not
|
|
3802
|
+
* part of bindIdents.
|
|
3803
|
+
*/
|
|
3804
|
+
function checkDoBlockNames(moduleElem) {
|
|
3805
|
+
const doBlocks = declsOfKind(moduleElem, "do");
|
|
3806
|
+
if (doBlocks.length === 0) return;
|
|
3807
|
+
const declNames = new Set(moduleElem.decls.map(globalDeclName));
|
|
3808
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3809
|
+
for (const block of doBlocks) {
|
|
3810
|
+
const { name, start, end } = block.name;
|
|
3811
|
+
if (declNames.has(name)) throw new ParseError(`'${name}' declared as both fn and do`, [start, end]);
|
|
3812
|
+
if (seen.has(name)) throw new ParseError(`'${name}' declared as do more than once`, [start, end]);
|
|
3813
|
+
seen.add(name);
|
|
3814
|
+
}
|
|
3085
3815
|
}
|
|
3086
3816
|
/** Parse WESL import statements at the start of the module. */
|
|
3087
3817
|
function parseImports(ctx) {
|
|
3088
3818
|
const importElems = parseWeslImports(ctx);
|
|
3089
3819
|
for (const importElem of importElems) {
|
|
3090
|
-
ctx.
|
|
3820
|
+
ctx.addModuleDecl(importElem);
|
|
3091
3821
|
ctx.state.stable.imports.push(importElem.imports);
|
|
3092
3822
|
}
|
|
3093
3823
|
}
|
|
3094
3824
|
/** Grammar: global_directive : diagnostic_directive | enable_directive | requires_directive */
|
|
3095
3825
|
function parseDirectives(ctx) {
|
|
3096
3826
|
const directives = parseMany(ctx, parseDirective);
|
|
3097
|
-
for (const elem of directives) ctx.
|
|
3827
|
+
for (const elem of directives) ctx.addModuleDecl(elem);
|
|
3098
3828
|
}
|
|
3099
3829
|
/** Parse one declaration, return true if more may exist. */
|
|
3100
3830
|
function parseNextDeclaration(ctx) {
|
|
@@ -3109,34 +3839,40 @@ function parseNextDeclaration(ctx) {
|
|
|
3109
3839
|
if (attrs.length) throwParseError(stream, "Expected declaration after attributes");
|
|
3110
3840
|
return false;
|
|
3111
3841
|
}
|
|
3842
|
+
/** @return the declared name of a module-level declaration, if it has one. */
|
|
3843
|
+
function globalDeclName(elem) {
|
|
3844
|
+
switch (elem.kind) {
|
|
3845
|
+
case "fn":
|
|
3846
|
+
case "struct":
|
|
3847
|
+
case "alias": return elem.name.ident.originalName;
|
|
3848
|
+
case "gvar":
|
|
3849
|
+
case "const":
|
|
3850
|
+
case "override": return elem.name.decl.ident.originalName;
|
|
3851
|
+
default: return;
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3112
3854
|
/** Try each declaration parser until one succeeds. */
|
|
3113
3855
|
function parseDecl(ctx, attrs) {
|
|
3114
|
-
const
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
return true;
|
|
3119
|
-
}
|
|
3120
|
-
return false;
|
|
3856
|
+
const elem = findMap(declParsers, (p) => p(ctx, attrsOrUndef(attrs)));
|
|
3857
|
+
if (!elem) return false;
|
|
3858
|
+
recordDecl(ctx, elem, attrs);
|
|
3859
|
+
return true;
|
|
3121
3860
|
}
|
|
3122
3861
|
/** Pop conditional scope and attach the conditional attribute. */
|
|
3123
3862
|
function finalizeConditional(ctx, attrs) {
|
|
3124
3863
|
const partialScope = ctx.popScope();
|
|
3125
|
-
partialScope.condAttribute =
|
|
3864
|
+
partialScope.condAttribute = conditionalAttribute(attrs);
|
|
3126
3865
|
}
|
|
3127
3866
|
/** Record a parsed declaration, extending start to include attributes. */
|
|
3128
3867
|
function recordDecl(ctx, elem, attrs) {
|
|
3129
3868
|
if (attrs.length && elem.start > attrs[0].start) elem.start = attrs[0].start;
|
|
3130
|
-
ctx.
|
|
3869
|
+
ctx.addModuleDecl(elem);
|
|
3131
3870
|
if (elem.kind === "assert") {
|
|
3132
3871
|
const { stable } = ctx.state;
|
|
3133
3872
|
stable.moduleAsserts ??= [];
|
|
3134
3873
|
stable.moduleAsserts.push(elem);
|
|
3135
3874
|
}
|
|
3136
3875
|
}
|
|
3137
|
-
function isConditionalAttribute(a) {
|
|
3138
|
-
return a.kind === "@if" || a.kind === "@elif" || a.kind === "@else";
|
|
3139
|
-
}
|
|
3140
3876
|
//#endregion
|
|
3141
3877
|
//#region src/parse/ParsingContext.ts
|
|
3142
3878
|
/** Context for parsers to build AST and manage scopes. */
|
|
@@ -3159,9 +3895,9 @@ var ParsingContext = class {
|
|
|
3159
3895
|
currentScope() {
|
|
3160
3896
|
return this.state.context.scope;
|
|
3161
3897
|
}
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3898
|
+
/** Append a top-level declaration to the module, in source order. */
|
|
3899
|
+
addModuleDecl(elem) {
|
|
3900
|
+
this.state.stable.moduleElem.decls.push(elem);
|
|
3165
3901
|
}
|
|
3166
3902
|
pushScope(kind = "scope") {
|
|
3167
3903
|
const { scope } = this.state.context;
|
|
@@ -3229,55 +3965,6 @@ const reservedWords = `NULL Self abstract active alignas alignof as asm asm_frag
|
|
|
3229
3965
|
target template this thread_local throw trait try type typedef typeid typename typeof
|
|
3230
3966
|
union unless unorm unsafe unsized use using varying virtual volatile wgsl where with writeonly yield`.split(/\s+/);
|
|
3231
3967
|
//#endregion
|
|
3232
|
-
//#region src/parse/stream/CachingStream.ts
|
|
3233
|
-
var CachingStream = class {
|
|
3234
|
-
cache = new Cache(5);
|
|
3235
|
-
inner;
|
|
3236
|
-
constructor(inner) {
|
|
3237
|
-
this.inner = inner;
|
|
3238
|
-
}
|
|
3239
|
-
checkpoint() {
|
|
3240
|
-
return this.inner.checkpoint();
|
|
3241
|
-
}
|
|
3242
|
-
reset(position) {
|
|
3243
|
-
this.inner.reset(position);
|
|
3244
|
-
}
|
|
3245
|
-
nextToken() {
|
|
3246
|
-
const startPos = this.checkpoint();
|
|
3247
|
-
const cachedValue = this.cache.get(startPos);
|
|
3248
|
-
if (cachedValue !== void 0) {
|
|
3249
|
-
this.reset(cachedValue.checkpoint);
|
|
3250
|
-
return cachedValue.token;
|
|
3251
|
-
} else {
|
|
3252
|
-
const token = this.inner.nextToken();
|
|
3253
|
-
const checkpoint = this.checkpoint();
|
|
3254
|
-
this.cache.set(startPos, {
|
|
3255
|
-
token,
|
|
3256
|
-
checkpoint
|
|
3257
|
-
});
|
|
3258
|
-
return token;
|
|
3259
|
-
}
|
|
3260
|
-
}
|
|
3261
|
-
get src() {
|
|
3262
|
-
return this.inner.src;
|
|
3263
|
-
}
|
|
3264
|
-
};
|
|
3265
|
-
/** size limited key value cache */
|
|
3266
|
-
var Cache = class extends Map {
|
|
3267
|
-
max;
|
|
3268
|
-
constructor(max) {
|
|
3269
|
-
super();
|
|
3270
|
-
this.max = max;
|
|
3271
|
-
}
|
|
3272
|
-
set(k, v) {
|
|
3273
|
-
if (this.size > this.max) {
|
|
3274
|
-
const first = this.keys().next().value;
|
|
3275
|
-
if (first) this.delete(first);
|
|
3276
|
-
}
|
|
3277
|
-
return super.set(k, v);
|
|
3278
|
-
}
|
|
3279
|
-
};
|
|
3280
|
-
//#endregion
|
|
3281
3968
|
//#region src/parse/stream/RegexHelpers.ts
|
|
3282
3969
|
function toRegexSource(nameExp) {
|
|
3283
3970
|
const [name, e] = nameExp;
|
|
@@ -3307,33 +3994,10 @@ function matchOneOf(syms) {
|
|
|
3307
3994
|
return new RegExp(escaped.join("|"));
|
|
3308
3995
|
}
|
|
3309
3996
|
//#endregion
|
|
3310
|
-
//#region src/parse/stream/
|
|
3311
|
-
/** Runs a `RegexMatchers` on an input string */
|
|
3312
|
-
var MatchersStream = class {
|
|
3313
|
-
position = 0;
|
|
3314
|
-
text;
|
|
3315
|
-
matchers;
|
|
3316
|
-
constructor(text, matchers) {
|
|
3317
|
-
this.text = text;
|
|
3318
|
-
this.matchers = matchers;
|
|
3319
|
-
}
|
|
3320
|
-
checkpoint() {
|
|
3321
|
-
return this.position;
|
|
3322
|
-
}
|
|
3323
|
-
reset(position) {
|
|
3324
|
-
this.position = position;
|
|
3325
|
-
}
|
|
3326
|
-
nextToken() {
|
|
3327
|
-
const result = this.matchers.execAt(this.text, this.position);
|
|
3328
|
-
if (result === null) return null;
|
|
3329
|
-
this.position = result.span[1];
|
|
3330
|
-
return result;
|
|
3331
|
-
}
|
|
3332
|
-
get src() {
|
|
3333
|
-
return this.text;
|
|
3334
|
-
}
|
|
3335
|
-
};
|
|
3997
|
+
//#region src/parse/stream/RegexMatchers.ts
|
|
3336
3998
|
/**
|
|
3999
|
+
* Matches tokens by kind with one combined regex.
|
|
4000
|
+
*
|
|
3337
4001
|
* The matchers passed to this object must follow certain rules:
|
|
3338
4002
|
* - They must use non-capturing groups: `(?:...)`
|
|
3339
4003
|
* - They must NOT use `^` or `$`
|
|
@@ -3344,55 +4008,230 @@ var RegexMatchers = class {
|
|
|
3344
4008
|
constructor(matchers) {
|
|
3345
4009
|
this.groups = Object.keys(matchers);
|
|
3346
4010
|
const expParts = Object.entries(matchers).map(toRegexSource).join("|");
|
|
3347
|
-
this.exp = new RegExp(expParts, "
|
|
4011
|
+
this.exp = new RegExp(expParts, "yu");
|
|
3348
4012
|
}
|
|
3349
4013
|
execAt(text, position) {
|
|
3350
4014
|
this.exp.lastIndex = position;
|
|
3351
|
-
const
|
|
3352
|
-
if (
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
text: text.slice(span[0], span[1])
|
|
3358
|
-
};
|
|
3359
|
-
} else return null;
|
|
3360
|
-
}
|
|
3361
|
-
};
|
|
3362
|
-
function findGroupDex(indices) {
|
|
3363
|
-
if (indices !== void 0) for (let i = 1; i < indices.length; i++) {
|
|
3364
|
-
const span = indices[i];
|
|
3365
|
-
if (span !== void 0) return {
|
|
4015
|
+
const matches = this.exp.exec(text);
|
|
4016
|
+
if (matches === null) return null;
|
|
4017
|
+
const matched = matches[0];
|
|
4018
|
+
const span = [position, position + matched.length];
|
|
4019
|
+
return {
|
|
4020
|
+
kind: this.groups[matchedGroupIndex(matches)],
|
|
3366
4021
|
span,
|
|
3367
|
-
|
|
4022
|
+
text: matched
|
|
3368
4023
|
};
|
|
3369
4024
|
}
|
|
4025
|
+
};
|
|
4026
|
+
/** @return index of the alternation group that matched */
|
|
4027
|
+
function matchedGroupIndex(matches) {
|
|
4028
|
+
for (let i = 1; i < matches.length; i++) if (matches[i] !== void 0) return i - 1;
|
|
4029
|
+
throw new Error("no matching group");
|
|
3370
4030
|
}
|
|
3371
4031
|
//#endregion
|
|
3372
|
-
//#region src/parse/
|
|
4032
|
+
//#region src/parse/stream/WeslLexer.ts
|
|
4033
|
+
const ident = /(?:(?:[_\p{XID_Start}][\p{XID_Continue}]+)|(?:[\p{XID_Start}]))/u;
|
|
3373
4034
|
/** Whitespaces including new lines */
|
|
3374
4035
|
const blankspaces = /[ \t\n\v\f\r\u{0085}\u{200E}\u{200F}\u{2028}\u{2029}]+/u;
|
|
3375
|
-
const
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
const
|
|
4036
|
+
const digits = new RegExp(/(?:0[fh])|(?:[1-9][0-9]*[fh])/.source + /|(?:[0-9]*\.[0-9]+(?:[eE][+-]?[0-9]+)?[fh]?)/.source + /|(?:[0-9]+\.[0-9]*(?:[eE][+-]?[0-9]+)?[fh]?)/.source + /|(?:[0-9]+[eE][+-]?[0-9]+[fh]?)/.source + /|(?:0[xX][0-9a-fA-F]*\.[0-9a-fA-F]+(?:[pP][+-]?[0-9]+[fh]?)?)/.source + /|(?:0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*(?:[pP][+-]?[0-9]+[fh]?)?)/.source + /|(?:0[xX][0-9a-fA-F]+[pP][+-]?[0-9]+[fh]?)/.source + /|(?:0[xX][0-9a-fA-F]+[iu]?)/.source + /|(?:0[iu]?)|(?:[1-9][0-9]*[iu]?)/.source);
|
|
4037
|
+
/** Unicode-aware fallback for characters the ASCII fast path can't handle.
|
|
4038
|
+
* lastIndex is set on every use, so sharing one instance is safe. */
|
|
4039
|
+
const unicodeMatcher = new RegexMatchers({
|
|
3379
4040
|
word: ident,
|
|
3380
|
-
number:
|
|
4041
|
+
number: digits,
|
|
3381
4042
|
blankspaces,
|
|
3382
4043
|
commentStart: /\/\/|\/\*/,
|
|
3383
|
-
symbol: matchOneOf(
|
|
4044
|
+
symbol: matchOneOf("& && -> @ / ! [ ] { } :: : , == = != >>= >> >= > <<= << <= < % - -- . + ++ | || ( ) ; * ~ ^ // /* */ += -= *= /= %= &= |= ^= _"),
|
|
3384
4045
|
invalid: /[^]/
|
|
3385
4046
|
});
|
|
4047
|
+
/**
|
|
4048
|
+
* Hand-written scanner for WESL/WGSL raw tokens.
|
|
4049
|
+
*
|
|
4050
|
+
* The common case is dispatched on ASCII char codes: whitespace is skipped
|
|
4051
|
+
* without producing tokens, and idents, numbers, symbols, and comment openers
|
|
4052
|
+
* are matched directly. Any character >= 0x80 (unicode idents, unicode
|
|
4053
|
+
* blankspace) falls back to the unicode-aware regex, so there is no up-front
|
|
4054
|
+
* scan and no unicode restriction.
|
|
4055
|
+
*/
|
|
4056
|
+
var WeslLexer = class {
|
|
4057
|
+
position = 0;
|
|
4058
|
+
/** sticky number matcher (stateful via lastIndex, so kept per-instance) */
|
|
4059
|
+
numberExp = new RegExp(digits.source, "y");
|
|
4060
|
+
src;
|
|
4061
|
+
constructor(src) {
|
|
4062
|
+
this.src = src;
|
|
4063
|
+
}
|
|
4064
|
+
checkpoint() {
|
|
4065
|
+
return this.position;
|
|
4066
|
+
}
|
|
4067
|
+
reset(position) {
|
|
4068
|
+
this.position = position;
|
|
4069
|
+
}
|
|
4070
|
+
nextToken() {
|
|
4071
|
+
const { src } = this;
|
|
4072
|
+
const len = src.length;
|
|
4073
|
+
let pos = this.position;
|
|
4074
|
+
while (pos < len) {
|
|
4075
|
+
const c = src.charCodeAt(pos);
|
|
4076
|
+
if (c === 32 || c >= 9 && c <= 13) pos++;
|
|
4077
|
+
else break;
|
|
4078
|
+
}
|
|
4079
|
+
if (pos >= len) {
|
|
4080
|
+
this.position = pos;
|
|
4081
|
+
return null;
|
|
4082
|
+
}
|
|
4083
|
+
const c = src.charCodeAt(pos);
|
|
4084
|
+
if (c >= 97 && c <= 122 || c >= 65 && c <= 90) return this.word(pos);
|
|
4085
|
+
if (c >= 48 && c <= 57) return this.number(pos);
|
|
4086
|
+
const n = src.charCodeAt(pos + 1);
|
|
4087
|
+
switch (c) {
|
|
4088
|
+
case 95:
|
|
4089
|
+
if (isWordChar(n)) return this.word(pos);
|
|
4090
|
+
if (n >= 128) return this.regexToken(pos);
|
|
4091
|
+
return this.symbol(pos, "_");
|
|
4092
|
+
case 47:
|
|
4093
|
+
if (n === 47) return this.token("commentStart", pos, "//");
|
|
4094
|
+
if (n === 42) return this.token("commentStart", pos, "/*");
|
|
4095
|
+
if (n === 61) return this.symbol(pos, "/=");
|
|
4096
|
+
return this.symbol(pos, "/");
|
|
4097
|
+
case 46:
|
|
4098
|
+
if (n >= 48 && n <= 57) return this.number(pos);
|
|
4099
|
+
return this.symbol(pos, ".");
|
|
4100
|
+
case 62:
|
|
4101
|
+
if (n === 62) {
|
|
4102
|
+
if (src.charCodeAt(pos + 2) === 61) return this.symbol(pos, ">>=");
|
|
4103
|
+
return this.symbol(pos, ">>");
|
|
4104
|
+
}
|
|
4105
|
+
if (n === 61) return this.symbol(pos, ">=");
|
|
4106
|
+
return this.symbol(pos, ">");
|
|
4107
|
+
case 60:
|
|
4108
|
+
if (n === 60) {
|
|
4109
|
+
if (src.charCodeAt(pos + 2) === 61) return this.symbol(pos, "<<=");
|
|
4110
|
+
return this.symbol(pos, "<<");
|
|
4111
|
+
}
|
|
4112
|
+
if (n === 61) return this.symbol(pos, "<=");
|
|
4113
|
+
return this.symbol(pos, "<");
|
|
4114
|
+
case 38:
|
|
4115
|
+
if (n === 38) return this.symbol(pos, "&&");
|
|
4116
|
+
if (n === 61) return this.symbol(pos, "&=");
|
|
4117
|
+
return this.symbol(pos, "&");
|
|
4118
|
+
case 124:
|
|
4119
|
+
if (n === 124) return this.symbol(pos, "||");
|
|
4120
|
+
if (n === 61) return this.symbol(pos, "|=");
|
|
4121
|
+
return this.symbol(pos, "|");
|
|
4122
|
+
case 45:
|
|
4123
|
+
if (n === 62) return this.symbol(pos, "->");
|
|
4124
|
+
if (n === 45) return this.symbol(pos, "--");
|
|
4125
|
+
if (n === 61) return this.symbol(pos, "-=");
|
|
4126
|
+
return this.symbol(pos, "-");
|
|
4127
|
+
case 43:
|
|
4128
|
+
if (n === 43) return this.symbol(pos, "++");
|
|
4129
|
+
if (n === 61) return this.symbol(pos, "+=");
|
|
4130
|
+
return this.symbol(pos, "+");
|
|
4131
|
+
case 58:
|
|
4132
|
+
if (n === 58) return this.symbol(pos, "::");
|
|
4133
|
+
return this.symbol(pos, ":");
|
|
4134
|
+
case 61:
|
|
4135
|
+
if (n === 61) return this.symbol(pos, "==");
|
|
4136
|
+
return this.symbol(pos, "=");
|
|
4137
|
+
case 33:
|
|
4138
|
+
if (n === 61) return this.symbol(pos, "!=");
|
|
4139
|
+
return this.symbol(pos, "!");
|
|
4140
|
+
case 42:
|
|
4141
|
+
if (n === 47) return this.symbol(pos, "*/");
|
|
4142
|
+
if (n === 61) return this.symbol(pos, "*=");
|
|
4143
|
+
return this.symbol(pos, "*");
|
|
4144
|
+
case 37:
|
|
4145
|
+
if (n === 61) return this.symbol(pos, "%=");
|
|
4146
|
+
return this.symbol(pos, "%");
|
|
4147
|
+
case 94:
|
|
4148
|
+
if (n === 61) return this.symbol(pos, "^=");
|
|
4149
|
+
return this.symbol(pos, "^");
|
|
4150
|
+
case 64: return this.symbol(pos, "@");
|
|
4151
|
+
case 91: return this.symbol(pos, "[");
|
|
4152
|
+
case 93: return this.symbol(pos, "]");
|
|
4153
|
+
case 123: return this.symbol(pos, "{");
|
|
4154
|
+
case 125: return this.symbol(pos, "}");
|
|
4155
|
+
case 40: return this.symbol(pos, "(");
|
|
4156
|
+
case 41: return this.symbol(pos, ")");
|
|
4157
|
+
case 44: return this.symbol(pos, ",");
|
|
4158
|
+
case 59: return this.symbol(pos, ";");
|
|
4159
|
+
case 126: return this.symbol(pos, "~");
|
|
4160
|
+
}
|
|
4161
|
+
if (c >= 128) return this.regexToken(pos);
|
|
4162
|
+
this.position = pos + 1;
|
|
4163
|
+
return {
|
|
4164
|
+
kind: "invalid",
|
|
4165
|
+
span: [pos, pos + 1],
|
|
4166
|
+
text: src[pos]
|
|
4167
|
+
};
|
|
4168
|
+
}
|
|
4169
|
+
token(kind, start, text) {
|
|
4170
|
+
const end = start + text.length;
|
|
4171
|
+
this.position = end;
|
|
4172
|
+
return {
|
|
4173
|
+
kind,
|
|
4174
|
+
span: [start, end],
|
|
4175
|
+
text
|
|
4176
|
+
};
|
|
4177
|
+
}
|
|
4178
|
+
symbol(start, text) {
|
|
4179
|
+
return this.token("symbol", start, text);
|
|
4180
|
+
}
|
|
4181
|
+
word(start) {
|
|
4182
|
+
const { src } = this;
|
|
4183
|
+
const len = src.length;
|
|
4184
|
+
let pos = start + 1;
|
|
4185
|
+
while (pos < len) {
|
|
4186
|
+
const c = src.charCodeAt(pos);
|
|
4187
|
+
if (isWordChar(c)) pos++;
|
|
4188
|
+
else if (c >= 128) return this.regexToken(start);
|
|
4189
|
+
else break;
|
|
4190
|
+
}
|
|
4191
|
+
this.position = pos;
|
|
4192
|
+
return {
|
|
4193
|
+
kind: "word",
|
|
4194
|
+
span: [start, pos],
|
|
4195
|
+
text: src.slice(start, pos)
|
|
4196
|
+
};
|
|
4197
|
+
}
|
|
4198
|
+
number(start) {
|
|
4199
|
+
this.numberExp.lastIndex = start;
|
|
4200
|
+
const text = this.numberExp.exec(this.src)[0];
|
|
4201
|
+
return this.token("number", start, text);
|
|
4202
|
+
}
|
|
4203
|
+
/** match one token (or blankspace run) with the unicode-aware regex */
|
|
4204
|
+
regexToken(start) {
|
|
4205
|
+
const token = unicodeMatcher.execAt(this.src, start);
|
|
4206
|
+
if (token === null) return null;
|
|
4207
|
+
this.position = token.span[1];
|
|
4208
|
+
return token;
|
|
4209
|
+
}
|
|
4210
|
+
};
|
|
4211
|
+
function isWordChar(c) {
|
|
4212
|
+
return c >= 97 && c <= 122 || c >= 65 && c <= 90 || c >= 48 && c <= 57 || c === 95;
|
|
4213
|
+
}
|
|
4214
|
+
//#endregion
|
|
4215
|
+
//#region src/parse/WeslStream.ts
|
|
4216
|
+
/** One line break, treating \r\n as a single break.
|
|
4217
|
+
* Same code points as `isLineBreak` in AttachComments.ts (charCode form). */
|
|
4218
|
+
const lineBreak = String.raw`\r\n?|[\n\v\f\u{0085}\u{2028}\u{2029}]`;
|
|
4219
|
+
const keywordOrReserved = new Set(keywords.concat(reservedWords));
|
|
3386
4220
|
/** A stream that produces WESL tokens, skipping over comments and white space */
|
|
3387
4221
|
var WeslStream = class {
|
|
3388
4222
|
stream;
|
|
3389
|
-
/** New line */
|
|
3390
|
-
eolPattern =
|
|
4223
|
+
/** New line (stateful: scanned via lastIndex, so kept per-instance). */
|
|
4224
|
+
eolPattern = new RegExp(lineBreak, "gu");
|
|
3391
4225
|
blockCommentPattern = /\/\*|\*\//g;
|
|
4226
|
+
/** Comments skipped before a real token, keyed by that token's start position. */
|
|
4227
|
+
triviaByPos = /* @__PURE__ */ new Map();
|
|
4228
|
+
/** Last peeked token, so the following nextToken() skips the rescan.
|
|
4229
|
+
* Never invalidated: tokenization is deterministic per position. */
|
|
4230
|
+
peeked = null;
|
|
3392
4231
|
src;
|
|
3393
4232
|
constructor(src) {
|
|
3394
4233
|
this.src = src;
|
|
3395
|
-
this.stream = new
|
|
4234
|
+
this.stream = new WeslLexer(src);
|
|
3396
4235
|
}
|
|
3397
4236
|
checkpoint() {
|
|
3398
4237
|
return this.stream.checkpoint();
|
|
@@ -3400,27 +4239,79 @@ var WeslStream = class {
|
|
|
3400
4239
|
reset(position) {
|
|
3401
4240
|
this.stream.reset(position);
|
|
3402
4241
|
}
|
|
4242
|
+
/** All recorded comment runs (each a contiguous group of comments between two
|
|
4243
|
+
* real tokens), in source order. Consumed by the post-parse comment pass. */
|
|
4244
|
+
commentRuns() {
|
|
4245
|
+
return [...this.triviaByPos.entries()].sort((a, b) => a[0] - b[0]).map(([, run]) => run);
|
|
4246
|
+
}
|
|
4247
|
+
recordTrivia(pos, pending) {
|
|
4248
|
+
if (pending) this.triviaByPos.set(pos, pending);
|
|
4249
|
+
}
|
|
4250
|
+
/** Next real token (comments/blankspace skipped and recorded as trivia); null at EOF. */
|
|
3403
4251
|
nextToken() {
|
|
4252
|
+
const peeked = this.usePeeked();
|
|
4253
|
+
if (peeked !== null) return peeked.token;
|
|
4254
|
+
let pending;
|
|
3404
4255
|
while (true) {
|
|
3405
4256
|
const token = this.stream.nextToken();
|
|
3406
|
-
if (token === null)
|
|
4257
|
+
if (token === null) {
|
|
4258
|
+
this.recordTrivia(this.src.length, pending);
|
|
4259
|
+
return null;
|
|
4260
|
+
}
|
|
3407
4261
|
const kind = token.kind;
|
|
3408
4262
|
if (kind === "blankspaces") continue;
|
|
3409
|
-
else if (kind === "commentStart")
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
const returnToken = token;
|
|
3413
|
-
if (keywordOrReserved.has(token.text)) returnToken.kind = "keyword";
|
|
3414
|
-
return returnToken;
|
|
4263
|
+
else if (kind === "commentStart") {
|
|
4264
|
+
pending ??= [];
|
|
4265
|
+
pending.push(this.consumeComment(token));
|
|
3415
4266
|
} else if (kind === "invalid") throw new ParseError("Invalid token " + token.text, token.span);
|
|
3416
|
-
else
|
|
4267
|
+
else {
|
|
4268
|
+
this.recordTrivia(token.span[0], pending);
|
|
4269
|
+
const result = token;
|
|
4270
|
+
if (kind === "word" && keywordOrReserved.has(token.text)) result.kind = "keyword";
|
|
4271
|
+
return result;
|
|
4272
|
+
}
|
|
4273
|
+
}
|
|
4274
|
+
}
|
|
4275
|
+
/** If the last peek() was at the current position, consume and return it. */
|
|
4276
|
+
usePeeked() {
|
|
4277
|
+
const peeked = this.peeked;
|
|
4278
|
+
if (peeked !== null && peeked.pos === this.checkpoint()) {
|
|
4279
|
+
this.reset(peeked.end);
|
|
4280
|
+
return peeked;
|
|
4281
|
+
}
|
|
4282
|
+
return null;
|
|
4283
|
+
}
|
|
4284
|
+
/** Skip a comment (rejecting embedded \0), advance the stream past it,
|
|
4285
|
+
* and return it as trivia. */
|
|
4286
|
+
consumeComment(token) {
|
|
4287
|
+
const style = token.text === "//" ? "line" : "block";
|
|
4288
|
+
const start = token.span[0];
|
|
4289
|
+
const end = this.commentEnd(token);
|
|
4290
|
+
const bodyNull = this.src.slice(start, end).indexOf("\0");
|
|
4291
|
+
if (bodyNull >= 0) {
|
|
4292
|
+
const at = start + bodyNull;
|
|
4293
|
+
throw new ParseError("Invalid token \\0", [at, at + 1]);
|
|
3417
4294
|
}
|
|
4295
|
+
this.stream.reset(end);
|
|
4296
|
+
return {
|
|
4297
|
+
style,
|
|
4298
|
+
start,
|
|
4299
|
+
end
|
|
4300
|
+
};
|
|
3418
4301
|
}
|
|
3419
4302
|
/** Peek at the next token without consuming it */
|
|
3420
4303
|
peek() {
|
|
3421
4304
|
const pos = this.checkpoint();
|
|
4305
|
+
const peeked = this.peeked;
|
|
4306
|
+
if (peeked !== null && peeked.pos === pos) return peeked.token;
|
|
3422
4307
|
const token = this.nextToken();
|
|
4308
|
+
const end = this.checkpoint();
|
|
3423
4309
|
this.reset(pos);
|
|
4310
|
+
this.peeked = {
|
|
4311
|
+
pos,
|
|
4312
|
+
token,
|
|
4313
|
+
end
|
|
4314
|
+
};
|
|
3424
4315
|
return token;
|
|
3425
4316
|
}
|
|
3426
4317
|
/** Consume token if text matches, otherwise leave position unchanged */
|
|
@@ -3464,10 +4355,15 @@ var WeslStream = class {
|
|
|
3464
4355
|
}
|
|
3465
4356
|
return tokens;
|
|
3466
4357
|
}
|
|
3467
|
-
|
|
4358
|
+
/** End position of a comment opened by a commentStart token. */
|
|
4359
|
+
commentEnd(token) {
|
|
4360
|
+
return token.text === "//" ? this.lineCommentEnd(token.span[1]) : this.skipBlockComment(token.span[1]);
|
|
4361
|
+
}
|
|
4362
|
+
/** End of a line comment: the start of the next line break (or end of file). */
|
|
4363
|
+
lineCommentEnd(position) {
|
|
3468
4364
|
this.eolPattern.lastIndex = position;
|
|
3469
|
-
|
|
3470
|
-
|
|
4365
|
+
const result = this.eolPattern.exec(this.src);
|
|
4366
|
+
return result === null ? this.src.length : result.index;
|
|
3471
4367
|
}
|
|
3472
4368
|
skipBlockComment(start) {
|
|
3473
4369
|
let position = start;
|
|
@@ -3489,39 +4385,46 @@ var WeslStream = class {
|
|
|
3489
4385
|
const startPosition = this.stream.checkpoint();
|
|
3490
4386
|
const token = this.nextToken();
|
|
3491
4387
|
this.stream.reset(startPosition);
|
|
3492
|
-
if (token === null) return null;
|
|
3493
|
-
if (token.
|
|
3494
|
-
if (token.text === "<") if (this.isTemplateStart(token.span[1])) {
|
|
3495
|
-
this.stream.reset(token.span[1]);
|
|
3496
|
-
return token;
|
|
3497
|
-
} else {
|
|
4388
|
+
if (token === null || token.kind !== "symbol" || token.text !== "<") return null;
|
|
4389
|
+
if (!this.isTemplateStart(token.span[1])) {
|
|
3498
4390
|
this.stream.reset(startPosition);
|
|
3499
4391
|
return null;
|
|
3500
4392
|
}
|
|
3501
|
-
|
|
4393
|
+
this.stream.reset(token.span[1]);
|
|
4394
|
+
return token;
|
|
3502
4395
|
}
|
|
4396
|
+
/** Match a template-closing `>`, splitting it off a `>>`/`>=`/`>>=` token when needed. */
|
|
3503
4397
|
nextTemplateEndToken() {
|
|
3504
4398
|
const startPosition = this.stream.checkpoint();
|
|
3505
4399
|
const token = this.nextToken();
|
|
3506
4400
|
this.stream.reset(startPosition);
|
|
3507
4401
|
if (token === null) return null;
|
|
3508
|
-
if (token.kind
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
} else return null;
|
|
4402
|
+
if (token.kind !== "symbol" || token.text[0] !== ">") return null;
|
|
4403
|
+
const tokenPosition = token.span[0];
|
|
4404
|
+
this.stream.reset(tokenPosition + 1);
|
|
4405
|
+
return {
|
|
4406
|
+
kind: "symbol",
|
|
4407
|
+
span: [tokenPosition, tokenPosition + 1],
|
|
4408
|
+
text: ">"
|
|
4409
|
+
};
|
|
3517
4410
|
}
|
|
4411
|
+
/** Next symbol from the raw stream, skipping comment bodies (so symbols
|
|
4412
|
+
* inside comments don't count) and non-symbol tokens; null at EOF. */
|
|
4413
|
+
nextRawSymbol() {
|
|
4414
|
+
while (true) {
|
|
4415
|
+
const token = this.stream.nextToken();
|
|
4416
|
+
if (token === null) return null;
|
|
4417
|
+
if (token.kind === "commentStart") this.stream.reset(this.commentEnd(token));
|
|
4418
|
+
else if (token.kind === "symbol") return token;
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
/** Template-list discovery: scan forward from `<` for a balanced closing `>`. */
|
|
3518
4422
|
isTemplateStart(afterToken) {
|
|
3519
4423
|
this.stream.reset(afterToken);
|
|
3520
4424
|
let pendingCounter = 1;
|
|
3521
4425
|
while (true) {
|
|
3522
|
-
const nextToken = this.
|
|
4426
|
+
const nextToken = this.nextRawSymbol();
|
|
3523
4427
|
if (nextToken === null) return false;
|
|
3524
|
-
if (nextToken.kind !== "symbol") continue;
|
|
3525
4428
|
if (nextToken.text === "<") pendingCounter += 1;
|
|
3526
4429
|
else if (nextToken.text[0] === ">") {
|
|
3527
4430
|
if (nextToken.text === ">" || nextToken.text === ">=") pendingCounter -= 1;
|
|
@@ -3539,12 +4442,11 @@ var WeslStream = class {
|
|
|
3539
4442
|
*/
|
|
3540
4443
|
skipBracketsTo(closingBracket) {
|
|
3541
4444
|
while (true) {
|
|
3542
|
-
const nextToken = this.
|
|
4445
|
+
const nextToken = this.nextRawSymbol();
|
|
3543
4446
|
if (nextToken === null) {
|
|
3544
4447
|
const after = this.stream.checkpoint();
|
|
3545
4448
|
throw new ParseError("Unclosed bracket!", [after, after]);
|
|
3546
4449
|
}
|
|
3547
|
-
if (nextToken.kind !== "symbol") continue;
|
|
3548
4450
|
if (nextToken.text === "(") this.skipBracketsTo(")");
|
|
3549
4451
|
else if (nextToken.text === "[") this.skipBracketsTo("]");
|
|
3550
4452
|
else if (nextToken.text === closingBracket) return;
|
|
@@ -3557,10 +4459,10 @@ var WeslStream = class {
|
|
|
3557
4459
|
function parseWesl(srcModule, options) {
|
|
3558
4460
|
const { ctx, state } = createParseState(srcModule, options);
|
|
3559
4461
|
try {
|
|
3560
|
-
beginElem(ctx, "module");
|
|
3561
4462
|
parseModule(ctx);
|
|
3562
|
-
const moduleElem = state.stable
|
|
3563
|
-
|
|
4463
|
+
const { moduleElem } = state.stable;
|
|
4464
|
+
attachComments(ctx, moduleElem);
|
|
4465
|
+
checkDoBlockNames(moduleElem);
|
|
3564
4466
|
return state.stable;
|
|
3565
4467
|
} catch (e) {
|
|
3566
4468
|
if (e instanceof ParseError) throw new WeslParseError({
|
|
@@ -3574,29 +4476,27 @@ function parseWesl(srcModule, options) {
|
|
|
3574
4476
|
}
|
|
3575
4477
|
}
|
|
3576
4478
|
/** Initialize parse state: token stream, root scope, and module element. */
|
|
3577
|
-
function createParseState(srcModule,
|
|
4479
|
+
function createParseState(srcModule, parseOptions) {
|
|
3578
4480
|
const stream = new WeslStream(srcModule.src);
|
|
3579
4481
|
const rootScope = emptyScope(null);
|
|
3580
4482
|
const moduleElem = {
|
|
3581
4483
|
kind: "module",
|
|
3582
|
-
|
|
4484
|
+
decls: [],
|
|
3583
4485
|
start: 0,
|
|
3584
4486
|
end: srcModule.src.length
|
|
3585
4487
|
};
|
|
3586
4488
|
const state = {
|
|
3587
|
-
context: {
|
|
3588
|
-
scope: rootScope,
|
|
3589
|
-
openElems: []
|
|
3590
|
-
},
|
|
4489
|
+
context: { scope: rootScope },
|
|
3591
4490
|
stable: {
|
|
3592
4491
|
srcModule,
|
|
3593
4492
|
moduleElem,
|
|
3594
4493
|
rootScope,
|
|
3595
|
-
imports: []
|
|
4494
|
+
imports: [],
|
|
4495
|
+
parseOptions
|
|
3596
4496
|
}
|
|
3597
4497
|
};
|
|
3598
4498
|
return {
|
|
3599
|
-
ctx: new ParsingContext(stream, state,
|
|
4499
|
+
ctx: new ParsingContext(stream, state, parseOptions),
|
|
3600
4500
|
state
|
|
3601
4501
|
};
|
|
3602
4502
|
}
|
|
@@ -3607,14 +4507,14 @@ var WeslParseError = class extends Error {
|
|
|
3607
4507
|
span;
|
|
3608
4508
|
src;
|
|
3609
4509
|
constructor(opts) {
|
|
3610
|
-
const
|
|
3611
|
-
const
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
message
|
|
3615
|
-
super(message, { cause
|
|
3616
|
-
this.span =
|
|
3617
|
-
this.src =
|
|
4510
|
+
const { cause, src } = opts;
|
|
4511
|
+
const source = src.src;
|
|
4512
|
+
const [lineNum, linePos] = offsetToLineNumber(cause.span[0], source);
|
|
4513
|
+
const highlight = errorHighlight(source, cause.span).join("\n");
|
|
4514
|
+
const message = `${src.debugFilePath}:${lineNum}:${linePos} error: ${cause.message}\n${highlight}`;
|
|
4515
|
+
super(message, { cause });
|
|
4516
|
+
this.span = cause.span;
|
|
4517
|
+
this.src = src;
|
|
3618
4518
|
}
|
|
3619
4519
|
};
|
|
3620
4520
|
/** Parse a WESL file. */
|
|
@@ -3624,8 +4524,8 @@ function parseSrcModule(srcModule, options) {
|
|
|
3624
4524
|
/** @return flattened form of import tree for binding idents. */
|
|
3625
4525
|
function flatImports(ast, conditions) {
|
|
3626
4526
|
if (ast._flatImports && !conditions) return ast._flatImports;
|
|
3627
|
-
const importElems = ast.moduleElem
|
|
3628
|
-
const flat = (conditions ? filterValidElements(importElems, conditions) : importElems).
|
|
4527
|
+
const importElems = declsOfKind(ast.moduleElem, "import");
|
|
4528
|
+
const flat = (conditions ? filterValidElements(importElems, conditions) : importElems).flatMap((elem) => flattenTreeImport(elem.imports));
|
|
3629
4529
|
if (!conditions) ast._flatImports = flat;
|
|
3630
4530
|
return flat;
|
|
3631
4531
|
}
|
|
@@ -3922,11 +4822,13 @@ var RecordResolver = class {
|
|
|
3922
4822
|
sources;
|
|
3923
4823
|
packageName;
|
|
3924
4824
|
debugWeslRoot;
|
|
4825
|
+
weslExtensions;
|
|
3925
4826
|
constructor(sources, options = {}) {
|
|
3926
|
-
const { packageName = "package", debugWeslRoot } = options;
|
|
4827
|
+
const { packageName = "package", debugWeslRoot, weslExtensions } = options;
|
|
3927
4828
|
this.sources = sources;
|
|
3928
4829
|
this.packageName = packageName;
|
|
3929
4830
|
this.debugWeslRoot = normalizeDebugRoot(debugWeslRoot);
|
|
4831
|
+
this.weslExtensions = weslExtensions;
|
|
3930
4832
|
}
|
|
3931
4833
|
resolveModule(modulePath) {
|
|
3932
4834
|
const cached = this.astCache.get(modulePath);
|
|
@@ -3937,7 +4839,7 @@ var RecordResolver = class {
|
|
|
3937
4839
|
modulePath,
|
|
3938
4840
|
debugFilePath: this.modulePathToDebugPath(modulePath),
|
|
3939
4841
|
src: source
|
|
3940
|
-
});
|
|
4842
|
+
}, { weslExtensions: this.weslExtensions });
|
|
3941
4843
|
this.astCache.set(modulePath, ast);
|
|
3942
4844
|
return ast;
|
|
3943
4845
|
}
|
|
@@ -4033,7 +4935,7 @@ function freshResolver(inner) {
|
|
|
4033
4935
|
if (cached) return cached;
|
|
4034
4936
|
const ast = inner.resolveModule(modulePath);
|
|
4035
4937
|
if (!ast) return void 0;
|
|
4036
|
-
const fresh = parseSrcModule(ast.srcModule);
|
|
4938
|
+
const fresh = parseSrcModule(ast.srcModule, ast.parseOptions);
|
|
4037
4939
|
cache.set(modulePath, fresh);
|
|
4038
4940
|
return fresh;
|
|
4039
4941
|
} };
|
|
@@ -4357,12 +5259,13 @@ var SrcMap = class SrcMap {
|
|
|
4357
5259
|
const { src, position: srcStart } = this.destToSrc(e.srcStart);
|
|
4358
5260
|
const { src: endSrc, position: srcEnd } = this.destToSrc(e.srcEnd);
|
|
4359
5261
|
if (endSrc !== src) throw new Error("NYI, need to split");
|
|
5262
|
+
const { destStart, destEnd } = e;
|
|
4360
5263
|
return {
|
|
4361
5264
|
src,
|
|
4362
5265
|
srcStart,
|
|
4363
5266
|
srcEnd,
|
|
4364
|
-
destStart
|
|
4365
|
-
destEnd
|
|
5267
|
+
destStart,
|
|
5268
|
+
destEnd
|
|
4366
5269
|
};
|
|
4367
5270
|
});
|
|
4368
5271
|
const otherSources = other.entries.filter((e) => e.src.path !== this.dest.path || e.src.text !== this.dest.text);
|
|
@@ -4383,10 +5286,6 @@ var SrcMap = class SrcMap {
|
|
|
4383
5286
|
};
|
|
4384
5287
|
}
|
|
4385
5288
|
};
|
|
4386
|
-
/** sort entries in place by src start position */
|
|
4387
|
-
function sortSrc(entries) {
|
|
4388
|
-
entries.sort((a, b) => a.srcStart - b.srcStart);
|
|
4389
|
-
}
|
|
4390
5289
|
/** Incrementally append to a string, tracking source references */
|
|
4391
5290
|
var SrcMapBuilder = class {
|
|
4392
5291
|
#fragments = [];
|
|
@@ -4440,11 +5339,6 @@ var SrcMapBuilder = class {
|
|
|
4440
5339
|
};
|
|
4441
5340
|
this.add("\n", srcStart, srcEnd);
|
|
4442
5341
|
}
|
|
4443
|
-
/** copy a string fragment from the src to the destination string */
|
|
4444
|
-
addCopy(srcStart, srcEnd) {
|
|
4445
|
-
const fragment = this.source.text.slice(srcStart, srcEnd);
|
|
4446
|
-
this.add(fragment, srcStart, srcEnd);
|
|
4447
|
-
}
|
|
4448
5342
|
/** return a SrcMap */
|
|
4449
5343
|
static build(builders) {
|
|
4450
5344
|
const map = new SrcMap({ text: builders.map((b) => b.#fragments.join("")).join("") }, builders.flatMap((b) => b.#entries));
|
|
@@ -4452,6 +5346,10 @@ var SrcMapBuilder = class {
|
|
|
4452
5346
|
return map;
|
|
4453
5347
|
}
|
|
4454
5348
|
};
|
|
5349
|
+
/** sort entries in place by src start position */
|
|
5350
|
+
function sortSrc(entries) {
|
|
5351
|
+
entries.sort((a, b) => a.srcStart - b.srcStart);
|
|
5352
|
+
}
|
|
4455
5353
|
//#endregion
|
|
4456
5354
|
//#region src/Linker.ts
|
|
4457
5355
|
/**
|
|
@@ -4468,20 +5366,15 @@ async function link(params) {
|
|
|
4468
5366
|
}
|
|
4469
5367
|
/** linker api for benchmarking */
|
|
4470
5368
|
function _linkSync(params) {
|
|
4471
|
-
const { weslSrc, libs = [], packageName, debugWeslRoot } = params;
|
|
4472
|
-
const {
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
else if (weslSrc) resolvers.push(new RecordResolver(weslSrc, {
|
|
5369
|
+
const { weslSrc, libs = [], packageName, debugWeslRoot, resolver } = params;
|
|
5370
|
+
const { weslExtensions } = params;
|
|
5371
|
+
if (!resolver && !weslSrc) throw new Error("Either resolver or weslSrc must be provided");
|
|
5372
|
+
const allResolvers = [resolver ?? new RecordResolver(weslSrc, {
|
|
4476
5373
|
packageName,
|
|
4477
|
-
debugWeslRoot
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
const libResolvers = createLibraryResolvers(libs, debugWeslRoot);
|
|
4482
|
-
resolvers.push(...libResolvers);
|
|
4483
|
-
}
|
|
4484
|
-
const finalResolver = resolvers.length === 1 ? resolvers[0] : new CompositeResolver(resolvers);
|
|
5374
|
+
debugWeslRoot,
|
|
5375
|
+
weslExtensions
|
|
5376
|
+
}), ...createLibraryResolvers(libs, debugWeslRoot)];
|
|
5377
|
+
const finalResolver = allResolvers.length === 1 ? allResolvers[0] : new CompositeResolver(allResolvers);
|
|
4485
5378
|
return linkRegistry({
|
|
4486
5379
|
...params,
|
|
4487
5380
|
resolver: finalResolver
|
|
@@ -4565,6 +5458,7 @@ function setupVirtualLibs(virtualLibs, constants) {
|
|
|
4565
5458
|
}
|
|
4566
5459
|
return libs && mapValues(libs, (fn) => ({ fn }));
|
|
4567
5460
|
}
|
|
5461
|
+
/** Run registered transform plugins over the bound AST. */
|
|
4568
5462
|
function applyTransformPlugins(rootModule, globalNames, config) {
|
|
4569
5463
|
const { moduleElem, srcModule } = rootModule;
|
|
4570
5464
|
const startAst = {
|
|
@@ -4577,311 +5471,38 @@ function applyTransformPlugins(rootModule, globalNames, config) {
|
|
|
4577
5471
|
}
|
|
4578
5472
|
/** Assemble WGSL output from prologue statements, root module, and imported declarations. */
|
|
4579
5473
|
function emitWgsl(rootModuleElem, srcModule, newDecls, newStatements, conditions = {}) {
|
|
4580
|
-
const prologueBuilders = newStatements.map((s) =>
|
|
4581
|
-
const rootBuilder =
|
|
4582
|
-
text: srcModule.src,
|
|
4583
|
-
path: srcModule.debugFilePath
|
|
4584
|
-
});
|
|
5474
|
+
const prologueBuilders = newStatements.map((s) => emitElem(s.srcModule, s.elem, conditions, { addNl: true }));
|
|
5475
|
+
const rootBuilder = builderFromModule(srcModule);
|
|
4585
5476
|
lowerAndEmit({
|
|
4586
5477
|
srcBuilder: rootBuilder,
|
|
4587
5478
|
rootElems: [rootModuleElem],
|
|
4588
5479
|
conditions,
|
|
4589
5480
|
extracting: false
|
|
4590
5481
|
});
|
|
4591
|
-
const declBuilders = newDecls.map((decl) =>
|
|
5482
|
+
const declBuilders = newDecls.map((decl) => emitElem(decl.srcModule, decl.declElem, conditions, { skipConditionalFiltering: true }));
|
|
4592
5483
|
return [
|
|
4593
5484
|
...prologueBuilders,
|
|
4594
5485
|
rootBuilder,
|
|
4595
5486
|
...declBuilders
|
|
4596
5487
|
];
|
|
4597
5488
|
}
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
const
|
|
4601
|
-
const builder = new SrcMapBuilder({
|
|
4602
|
-
text,
|
|
4603
|
-
path
|
|
4604
|
-
});
|
|
5489
|
+
/** Emit a single element (prologue statement or imported declaration) into a SrcMapBuilder. */
|
|
5490
|
+
function emitElem(srcModule, elem, conditions, opts = {}) {
|
|
5491
|
+
const builder = builderFromModule(srcModule);
|
|
4605
5492
|
lowerAndEmit({
|
|
4606
5493
|
srcBuilder: builder,
|
|
4607
5494
|
rootElems: [elem],
|
|
4608
|
-
conditions
|
|
4609
|
-
});
|
|
4610
|
-
builder.addNl();
|
|
4611
|
-
return builder;
|
|
4612
|
-
}
|
|
4613
|
-
/** Skip conditional filtering because findValidRootDecls already validated these declarations */
|
|
4614
|
-
function emitDecl(decl, conditions) {
|
|
4615
|
-
const { src: text, debugFilePath: path } = decl.srcModule;
|
|
4616
|
-
const builder = new SrcMapBuilder({
|
|
4617
|
-
text,
|
|
4618
|
-
path
|
|
4619
|
-
});
|
|
4620
|
-
lowerAndEmit({
|
|
4621
|
-
srcBuilder: builder,
|
|
4622
|
-
rootElems: [decl.declElem],
|
|
4623
5495
|
conditions,
|
|
4624
|
-
skipConditionalFiltering:
|
|
5496
|
+
skipConditionalFiltering: opts.skipConditionalFiltering
|
|
4625
5497
|
});
|
|
5498
|
+
if (opts.addNl) builder.addNl();
|
|
4626
5499
|
return builder;
|
|
4627
5500
|
}
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
if (elem.contents) elem.contents.forEach((child) => {
|
|
4633
|
-
visitAst(child, visitor);
|
|
4634
|
-
});
|
|
4635
|
-
}
|
|
4636
|
-
//#endregion
|
|
4637
|
-
//#region src/RawEmit.ts
|
|
4638
|
-
function attributeToString$1(e) {
|
|
4639
|
-
const { kind } = e.attribute;
|
|
4640
|
-
if (kind === "@attribute") {
|
|
4641
|
-
const { params } = e.attribute;
|
|
4642
|
-
if (params === void 0 || params.length === 0) return "@" + e.attribute.name;
|
|
4643
|
-
else return `@${e.attribute.name}(${params.map((param) => contentsToString(param)).join(", ")})`;
|
|
4644
|
-
} else if (kind === "@builtin") return "@builtin(" + e.attribute.param.name + ")";
|
|
4645
|
-
else if (kind === "@diagnostic") return "@diagnostic" + diagnosticControlToString(e.attribute.severity, e.attribute.rule);
|
|
4646
|
-
else if (kind === "@if") return `@if(${expressionToString(e.attribute.param.expression)})`;
|
|
4647
|
-
else if (kind === "@elif") return `@elif(${expressionToString(e.attribute.param.expression)})`;
|
|
4648
|
-
else if (kind === "@else") return "@else";
|
|
4649
|
-
else if (kind === "@interpolate") return `@interpolate(${e.attribute.params.map((v) => v.name).join(", ")})`;
|
|
4650
|
-
else assertUnreachable(kind);
|
|
4651
|
-
}
|
|
4652
|
-
function typeListToString(params) {
|
|
4653
|
-
return `<${params.map(typeParamToString).join(", ")}>`;
|
|
4654
|
-
}
|
|
4655
|
-
function typeParamToString(param) {
|
|
4656
|
-
if (param === void 0) return "?";
|
|
4657
|
-
if (param.kind === "type") return typeRefToString(param);
|
|
4658
|
-
return expressionToString(param);
|
|
4659
|
-
}
|
|
4660
|
-
function typeRefToString(t) {
|
|
4661
|
-
if (!t) return "?";
|
|
4662
|
-
const { name, templateParams } = t;
|
|
4663
|
-
const params = templateParams ? typeListToString(templateParams) : "";
|
|
4664
|
-
return `${refToString(name)}${params}`;
|
|
4665
|
-
}
|
|
4666
|
-
function refToString(ref) {
|
|
4667
|
-
if (typeof ref === "string") return ref;
|
|
4668
|
-
if (ref.std) return ref.originalName;
|
|
4669
|
-
const decl = findDecl(ref);
|
|
4670
|
-
return decl.mangledName || decl.originalName;
|
|
4671
|
-
}
|
|
4672
|
-
function contentsToString(elem) {
|
|
4673
|
-
if (elem.kind === "translate-time-expression") throw new Error("Not supported");
|
|
4674
|
-
else if (elem.kind === "expression" || elem.kind === "stuff") return elem.contents.map((c) => {
|
|
4675
|
-
const { kind } = c;
|
|
4676
|
-
if (kind === "text") return c.srcModule.src.slice(c.start, c.end);
|
|
4677
|
-
else if (kind === "ref") return refToString(c.ident);
|
|
4678
|
-
else return `?${c.kind}?`;
|
|
4679
|
-
}).join(" ");
|
|
4680
|
-
else if (elem.kind === "name") return elem.name;
|
|
4681
|
-
else assertUnreachable(elem);
|
|
4682
|
-
}
|
|
4683
|
-
//#endregion
|
|
4684
|
-
//#region src/Reflection.ts
|
|
4685
|
-
const textureStorage = matchOneOf(textureStorageTypes);
|
|
4686
|
-
matchOneOf(sampledTextureTypes);
|
|
4687
|
-
matchOneOf(multisampledTextureTypes);
|
|
4688
|
-
//#endregion
|
|
4689
|
-
//#region src/TransformBindingStructs.ts
|
|
4690
|
-
function bindingStructsPlugin() {
|
|
4691
|
-
return { transform: lowerBindingStructs };
|
|
4692
|
-
}
|
|
4693
|
-
/**
|
|
4694
|
-
* Transform binding structures into binding variables by mutating the AST.
|
|
4695
|
-
*
|
|
4696
|
-
* First we find all the binding structs:
|
|
4697
|
-
* . find all the structs in the module by filtering the moduleElem.contents
|
|
4698
|
-
* . for each struct:
|
|
4699
|
-
* . mark any structs with that contain @group or @binding annotations as 'binding structs' and save them in a list
|
|
4700
|
-
* . (later) create reverse links from structs to struct members
|
|
4701
|
-
* . (later) visit all the binding structs and traverse to referencing structs, marking the referencing structs as binding structs too
|
|
4702
|
-
* Generate synethic AST nodes for binding variables
|
|
4703
|
-
*
|
|
4704
|
-
* Find all references to binding struct members
|
|
4705
|
-
* . find the componound idents by traversing moduleElem.contents
|
|
4706
|
-
* . filter to find the compound idents that refer to 'binding structs'
|
|
4707
|
-
* . go from each ident to its declaration,
|
|
4708
|
-
* . declaration to typeRef reference
|
|
4709
|
-
* . typeRef to type declaration
|
|
4710
|
-
* . check type declaration to see if it's a binding struct
|
|
4711
|
-
* . record the intermediate declaration (e.g. a fn param b:Bindings from 'fn(b:Bindings)' )
|
|
4712
|
-
* rewrite references to binding struct members as synthetic elements
|
|
4713
|
-
*
|
|
4714
|
-
* Remove the binding structs from the AST
|
|
4715
|
-
* Remove the intermediate fn param declarations from the AST
|
|
4716
|
-
* Add the new binding variables to the AST
|
|
4717
|
-
*
|
|
4718
|
-
* @return the binding structs and the mutated AST
|
|
4719
|
-
*/
|
|
4720
|
-
function lowerBindingStructs(ast) {
|
|
4721
|
-
const clonedAst = structuredClone(ast);
|
|
4722
|
-
const { moduleElem, globalNames, notableElems } = clonedAst;
|
|
4723
|
-
const bindingStructs = markBindingStructs(moduleElem);
|
|
4724
|
-
markEntryTypes(moduleElem, bindingStructs);
|
|
4725
|
-
const newVars = bindingStructs.flatMap((s) => transformBindingStruct(s, globalNames));
|
|
4726
|
-
const bindingRefs = findRefsToBindingStructs(moduleElem);
|
|
4727
|
-
bindingRefs.forEach(({ memberRef, struct }) => {
|
|
4728
|
-
transformBindingReference(memberRef, struct);
|
|
4729
|
-
});
|
|
4730
|
-
bindingRefs.forEach(({ intermediates }) => {
|
|
4731
|
-
intermediates.forEach((e) => {
|
|
4732
|
-
e.contents = [];
|
|
4733
|
-
});
|
|
4734
|
-
});
|
|
4735
|
-
const contents = removeBindingStructs(moduleElem);
|
|
4736
|
-
moduleElem.contents = [...newVars, ...contents];
|
|
4737
|
-
notableElems.bindingStructs = bindingStructs;
|
|
4738
|
-
return {
|
|
4739
|
-
...clonedAst,
|
|
4740
|
-
moduleElem
|
|
4741
|
-
};
|
|
4742
|
-
}
|
|
4743
|
-
function markEntryTypes(moduleElem, bindingStructs) {
|
|
4744
|
-
const fnFound = fnReferencesBindingStruct(moduleElem.contents.filter((e) => e.kind === "fn"), bindingStructs);
|
|
4745
|
-
if (fnFound) {
|
|
4746
|
-
const { fn, struct } = fnFound;
|
|
4747
|
-
struct.entryFn = fn;
|
|
4748
|
-
}
|
|
4749
|
-
}
|
|
4750
|
-
function fnReferencesBindingStruct(fns, bindingStructs) {
|
|
4751
|
-
for (const fn of fns) {
|
|
4752
|
-
const { params } = fn;
|
|
4753
|
-
for (const p of params) {
|
|
4754
|
-
const referencedElem = ((p.name?.typeRef?.name)?.refersTo)?.declElem;
|
|
4755
|
-
const struct = bindingStructs.find((s) => s === referencedElem);
|
|
4756
|
-
if (struct) return {
|
|
4757
|
-
fn,
|
|
4758
|
-
struct
|
|
4759
|
-
};
|
|
4760
|
-
}
|
|
4761
|
-
}
|
|
4762
|
-
}
|
|
4763
|
-
function removeBindingStructs(moduleElem) {
|
|
4764
|
-
return moduleElem.contents.filter((elem) => elem.kind !== "struct" || !elem.bindingStruct);
|
|
4765
|
-
}
|
|
4766
|
-
/** mutate the AST, marking StructElems as bindingStructs
|
|
4767
|
-
* (if they contain ptrs with @group @binding annotations)
|
|
4768
|
-
* @return the binding structs
|
|
4769
|
-
*/
|
|
4770
|
-
function markBindingStructs(moduleElem) {
|
|
4771
|
-
const bindingStructs = moduleElem.contents.filter((elem) => elem.kind === "struct").filter(containsBinding);
|
|
4772
|
-
bindingStructs.forEach((struct) => {
|
|
4773
|
-
struct.bindingStruct = true;
|
|
4774
|
-
});
|
|
4775
|
-
return bindingStructs;
|
|
4776
|
-
}
|
|
4777
|
-
/** @return true if this struct contains a member with marked with @binding or @group */
|
|
4778
|
-
function containsBinding(struct) {
|
|
4779
|
-
return struct.members.some(({ attributes }) => bindingAttribute(attributes));
|
|
4780
|
-
}
|
|
4781
|
-
function bindingAttribute(attributes) {
|
|
4782
|
-
if (!attributes) return false;
|
|
4783
|
-
return attributes.some(({ attribute }) => attribute.kind === "@attribute" && (attribute.name === "binding" || attribute.name === "group"));
|
|
4784
|
-
}
|
|
4785
|
-
/** convert each member of the binding struct into a synthetic global variable */
|
|
4786
|
-
function transformBindingStruct(s, globalNames) {
|
|
4787
|
-
return s.members.map((member) => {
|
|
4788
|
-
const { typeRef, name: memberName } = member;
|
|
4789
|
-
const { name: typeName } = typeRef;
|
|
4790
|
-
const typeParameters = typeRef?.templateParams;
|
|
4791
|
-
const varName = minimallyMangledName(memberName.name, globalNames);
|
|
4792
|
-
member.mangledVarName = varName;
|
|
4793
|
-
globalNames.add(varName);
|
|
4794
|
-
const attributes = member.attributes?.map(attributeToString$1).join(" ") ?? "";
|
|
4795
|
-
const varTypes = lowerPtrMember(typeName, typeParameters) ?? lowerStdTypeMember(typeName, typeParameters) ?? lowerStorageTextureMember(typeName, typeParameters);
|
|
4796
|
-
if (!varTypes) {
|
|
4797
|
-
console.log("unhandled case transforming member", typeName);
|
|
4798
|
-
return syntheticVar(attributes, varName, "", "??");
|
|
4799
|
-
}
|
|
4800
|
-
const { storage: storageType, varType } = varTypes;
|
|
4801
|
-
return syntheticVar(attributes, varName, storageType, varType);
|
|
4802
|
-
});
|
|
4803
|
-
}
|
|
4804
|
-
function lowerPtrMember(typeName, typeParameters) {
|
|
4805
|
-
if (typeName.originalName === "ptr") {
|
|
4806
|
-
const origParams = typeParameters ?? [];
|
|
4807
|
-
const newParams = [origParams[0]];
|
|
4808
|
-
if (origParams[2]) newParams.push(origParams[2]);
|
|
4809
|
-
return {
|
|
4810
|
-
storage: typeListToString(newParams),
|
|
4811
|
-
varType: typeParamToString(origParams?.[1])
|
|
4812
|
-
};
|
|
4813
|
-
}
|
|
4814
|
-
}
|
|
4815
|
-
function lowerStdTypeMember(typeName, typeParameters) {
|
|
4816
|
-
if (typeof typeName !== "string") return {
|
|
4817
|
-
varType: (typeName.std ? typeName.originalName : "??") + (typeParameters ? typeListToString(typeParameters) : ""),
|
|
4818
|
-
storage: ""
|
|
4819
|
-
};
|
|
4820
|
-
}
|
|
4821
|
-
function lowerStorageTextureMember(typeName, typeParameters) {
|
|
4822
|
-
if (textureStorage.test(typeName.originalName)) return {
|
|
4823
|
-
varType: typeName + (typeParameters ? typeListToString(typeParameters) : ""),
|
|
4824
|
-
storage: ""
|
|
4825
|
-
};
|
|
4826
|
-
}
|
|
4827
|
-
function syntheticVar(attributes, varName, storageTemplate, varType) {
|
|
4828
|
-
return {
|
|
4829
|
-
kind: "synthetic",
|
|
4830
|
-
text: `${attributes} var${storageTemplate} ${varName} : ${varType};\n`
|
|
4831
|
-
};
|
|
4832
|
-
}
|
|
4833
|
-
/** find all simple member references in the module that refer to binding structs */
|
|
4834
|
-
function findRefsToBindingStructs(moduleElem) {
|
|
4835
|
-
const members = [];
|
|
4836
|
-
visitAst(moduleElem, (elem) => {
|
|
4837
|
-
if (elem.kind === "memberRef") members.push(elem);
|
|
5501
|
+
function builderFromModule(srcModule) {
|
|
5502
|
+
return new SrcMapBuilder({
|
|
5503
|
+
text: srcModule.src,
|
|
5504
|
+
path: srcModule.debugFilePath
|
|
4838
5505
|
});
|
|
4839
|
-
return filterMap(members, refersToBindingStruct);
|
|
4840
|
-
}
|
|
4841
|
-
/** @return true if this memberRef refers to a binding struct */
|
|
4842
|
-
function refersToBindingStruct(memberRef) {
|
|
4843
|
-
const found = traceToStruct(memberRef.name.ident);
|
|
4844
|
-
if (found?.struct.bindingStruct) return {
|
|
4845
|
-
memberRef,
|
|
4846
|
-
...found
|
|
4847
|
-
};
|
|
4848
|
-
}
|
|
4849
|
-
/** If this identifier ultimately refers to a struct type, return the struct declaration */
|
|
4850
|
-
function traceToStruct(ident) {
|
|
4851
|
-
const declElem = findDecl(ident).declElem;
|
|
4852
|
-
if (declElem && declElem.kind === "param") {
|
|
4853
|
-
const name = declElem.name.typeRef?.name;
|
|
4854
|
-
if (typeof name !== "string") {
|
|
4855
|
-
if (name?.std) return;
|
|
4856
|
-
const structElem = findDecl(name).declElem;
|
|
4857
|
-
if (structElem?.kind === "struct") return {
|
|
4858
|
-
struct: structElem,
|
|
4859
|
-
intermediates: [declElem]
|
|
4860
|
-
};
|
|
4861
|
-
return;
|
|
4862
|
-
}
|
|
4863
|
-
}
|
|
4864
|
-
}
|
|
4865
|
-
/** Mutate the member reference elem to instead contain synthetic elem text.
|
|
4866
|
-
* The new text is the mangled var name of the struct member that the memberRef refers to. */
|
|
4867
|
-
function transformBindingReference(memberRef, struct) {
|
|
4868
|
-
const refName = memberRef.member.name;
|
|
4869
|
-
const structMember = struct.members.find((m) => m.name.name === refName);
|
|
4870
|
-
if (!structMember || !structMember.mangledVarName) {
|
|
4871
|
-
console.log(`missing mangledVarName for ${refName}`);
|
|
4872
|
-
return {
|
|
4873
|
-
kind: "synthetic",
|
|
4874
|
-
text: refName
|
|
4875
|
-
};
|
|
4876
|
-
}
|
|
4877
|
-
const { extraComponents } = memberRef;
|
|
4878
|
-
const extraText = extraComponents ? contentsToString(extraComponents) : "";
|
|
4879
|
-
const synthElem = {
|
|
4880
|
-
kind: "synthetic",
|
|
4881
|
-
text: structMember.mangledVarName + extraText
|
|
4882
|
-
};
|
|
4883
|
-
memberRef.contents = [synthElem];
|
|
4884
|
-
return synthElem;
|
|
4885
5506
|
}
|
|
4886
5507
|
//#endregion
|
|
4887
5508
|
//#region src/WeslDevice.ts
|
|
@@ -4924,6 +5545,7 @@ function makeWeslDevice(device) {
|
|
|
4924
5545
|
lineNumber: message.lineNum,
|
|
4925
5546
|
lineColumn: message.linePos,
|
|
4926
5547
|
length: message.length,
|
|
5548
|
+
offset: message.offset,
|
|
4927
5549
|
error: /* @__PURE__ */ new Error(message.type + ": " + message.message)
|
|
4928
5550
|
});
|
|
4929
5551
|
else console.error(ev.error.message);
|
|
@@ -4951,4 +5573,4 @@ function makeWeslDevice(device) {
|
|
|
4951
5573
|
return device;
|
|
4952
5574
|
}
|
|
4953
5575
|
//#endregion
|
|
4954
|
-
export { BundleResolver, CompositeResolver, LinkedWesl, ParseError, RecordResolver, SrcMap, SrcMapBuilder, TrackingResolver, WeslParseError, WeslStream, _linkSync, astToString, attributeToString, bindAndTransform, bindIdents, bindIdentsRecursive,
|
|
5576
|
+
export { BundleResolver, CompositeResolver, LinkedWesl, ParseError, RecordResolver, SrcMap, SrcMapBuilder, TrackingResolver, WeslParseError, WeslStream, _linkSync, astToString, attributeToString, bindAndTransform, bindIdents, bindIdentsRecursive, childElems, childIdent, childScope, containsScope, debug, declsOfKind, discoverModules, emptyScope, errorHighlight, fileToModulePath, filterMap, filterValidElements, findAllRootDecls, findMap, findUnboundIdents, findUnboundRefs, findValidRootDecls, flatImports, freshResolver, groupBy, grouped, identElemLog, identToString, last, lengthPrefixMangle, link, linkRegistry, liveDeclsToString, log, makeLiveDecls, makeWeslDevice, mapForward, mapValues, mergeScope, minimalMangle, minimallyMangledName, modulePartsToRelativePath, moduleToRelativePath, multiKeySet, multisampledTextureTypes, nextIdentId, noSuffix, normalize, normalizeDebugRoot, normalizeModuleName, npmNameVariations, offsetToLineNumber, overlapTail, parseSrcModule, partition, publicDecl, replaceWords, requestWeslDevice, resetScopeIds, resolveModulePath, sampledTextureTypes, sanitizePackageName, scan, scopeToString, scopeToStringLong, srcLog, stdEnumerant, stdEnumerants, stdFn, stdFns, stdType, stdTypes, textureStorageTypes, underscoreMangle, validation, visitAst, wgslStandardAttributes, withLoggerAsync };
|