wesl 0.7.27 → 0.7.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +272 -126
- package/dist/index.js +1742 -1102
- package/package.json +1 -1
- package/src/AbstractElems.ts +263 -81
- package/src/Linker.ts +14 -2
- package/src/LinkerUtil.ts +141 -7
- package/src/LowerAndEmit.ts +660 -304
- package/src/ModuleResolver.ts +15 -4
- package/src/ParseWESL.ts +21 -33
- package/src/SrcMap.ts +7 -19
- package/src/debug/ASTtoString.ts +92 -76
- package/src/index.ts +1 -1
- package/src/parse/AttachComments.ts +289 -0
- package/src/parse/ExpressionUtil.ts +3 -3
- package/src/parse/ParseAttribute.ts +49 -71
- package/src/parse/ParseCall.ts +3 -4
- package/src/parse/ParseControlFlow.ts +100 -56
- package/src/parse/ParseDirective.ts +9 -8
- package/src/parse/ParseDoBlock.ts +63 -0
- package/src/parse/ParseExpression.ts +11 -10
- package/src/parse/ParseFn.ts +11 -33
- package/src/parse/ParseGlobalVar.ts +36 -27
- package/src/parse/ParseIdent.ts +4 -5
- package/src/parse/ParseLocalVar.ts +16 -12
- package/src/parse/ParseLoop.ts +76 -39
- package/src/parse/ParseModule.ts +65 -19
- package/src/parse/ParseSimpleStatement.ts +112 -66
- package/src/parse/ParseStatement.ts +39 -66
- package/src/parse/ParseStruct.ts +8 -22
- package/src/parse/ParseType.ts +10 -13
- package/src/parse/ParseUtil.ts +26 -12
- package/src/parse/ParseValueDeclaration.ts +18 -31
- package/src/parse/ParseWesl.ts +11 -14
- package/src/parse/ParsingContext.ts +11 -9
- package/src/parse/WeslStream.ts +138 -121
- package/src/parse/stream/RegexMatchers.ts +45 -0
- package/src/parse/stream/WeslLexer.ts +249 -0
- package/src/test/BevyLink.test.ts +17 -10
- package/src/test/ConditionalElif.test.ts +4 -2
- package/src/test/DeclCommentEmit.test.ts +122 -0
- package/src/test/DoBlock.test.ts +164 -0
- package/src/test/FilterValidElements.test.ts +4 -6
- package/src/test/Linker.test.ts +23 -7
- package/src/test/Mangling.test.ts +8 -4
- package/src/test/ParseComments.test.ts +234 -6
- package/src/test/ParseConditionsV2.test.ts +47 -175
- package/src/test/ParseElifV2.test.ts +12 -33
- package/src/test/ParseErrorV2.test.ts +0 -0
- package/src/test/ParseWeslV2.test.ts +247 -626
- package/src/test/StatementEmit.test.ts +143 -0
- package/src/test/StripWesl.ts +24 -3
- package/src/test/TestLink.ts +19 -3
- package/src/test/TestUtil.ts +18 -8
- package/src/test/Tokenizer.test.ts +96 -0
- package/src/test/__snapshots__/ParseWeslV2.test.ts.snap +10 -42
- package/src/RawEmit.ts +0 -103
- package/src/Reflection.ts +0 -336
- package/src/TransformBindingStructs.ts +0 -320
- package/src/parse/ContentsHelpers.ts +0 -70
- package/src/parse/stream/CachingStream.ts +0 -48
- package/src/parse/stream/MatchersStream.ts +0 -85
package/dist/index.js
CHANGED
|
@@ -499,6 +499,120 @@ function childIdent(child) {
|
|
|
499
499
|
return !childScope(child);
|
|
500
500
|
}
|
|
501
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
|
|
502
616
|
//#region src/StandardTypes.ts
|
|
503
617
|
const stdFns = `bitcast all any select arrayLength
|
|
504
618
|
abs acos acosh asin asinh atan atanh atan2 ceil clamp cos cosh
|
|
@@ -600,6 +714,22 @@ function stdEnumerant(name) {
|
|
|
600
714
|
}
|
|
601
715
|
//#endregion
|
|
602
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
|
+
]);
|
|
603
733
|
/** Traverse the AST, starting from root elements, emitting WGSL for each. */
|
|
604
734
|
function lowerAndEmit(params) {
|
|
605
735
|
const { srcBuilder, rootElems, conditions } = params;
|
|
@@ -607,17 +737,53 @@ function lowerAndEmit(params) {
|
|
|
607
737
|
const emitContext = {
|
|
608
738
|
conditions,
|
|
609
739
|
srcBuilder,
|
|
610
|
-
extracting
|
|
740
|
+
extracting,
|
|
741
|
+
indent: 0
|
|
611
742
|
};
|
|
612
743
|
const validElements = skipConditionalFiltering ? rootElems : filterValidElements(rootElems, conditions);
|
|
613
744
|
for (const e of validElements) lowerAndEmitElem(e, emitContext);
|
|
614
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
|
+
}
|
|
615
783
|
function lowerAndEmitElem(e, ctx) {
|
|
616
784
|
switch (e.kind) {
|
|
617
785
|
case "import": return;
|
|
618
|
-
case "
|
|
619
|
-
emitText(e, ctx);
|
|
620
|
-
return;
|
|
786
|
+
case "do": return;
|
|
621
787
|
case "name":
|
|
622
788
|
emitName(e, ctx);
|
|
623
789
|
return;
|
|
@@ -640,24 +806,45 @@ function lowerAndEmitElem(e, ctx) {
|
|
|
640
806
|
emitExpression(e, ctx);
|
|
641
807
|
return;
|
|
642
808
|
case "param":
|
|
809
|
+
emitAttributes(e.attributes, ctx);
|
|
810
|
+
emitTypedDecl(e.name, ctx);
|
|
811
|
+
return;
|
|
643
812
|
case "typeDecl":
|
|
813
|
+
emitTypedDecl(e, ctx);
|
|
814
|
+
return;
|
|
644
815
|
case "member":
|
|
645
|
-
|
|
816
|
+
emitMember(e, ctx);
|
|
817
|
+
return;
|
|
646
818
|
case "expression":
|
|
647
|
-
|
|
819
|
+
emitExpression(e.expression, ctx);
|
|
820
|
+
return;
|
|
648
821
|
case "switch-clause":
|
|
649
|
-
|
|
822
|
+
emitSwitchClause(e, ctx);
|
|
650
823
|
return;
|
|
651
|
-
case "
|
|
652
|
-
|
|
824
|
+
case "type":
|
|
825
|
+
emitTypeRef(e, ctx);
|
|
653
826
|
return;
|
|
654
827
|
case "module":
|
|
655
828
|
emitModule(e, ctx);
|
|
656
829
|
return;
|
|
657
830
|
case "var":
|
|
658
831
|
case "let":
|
|
659
|
-
case "
|
|
832
|
+
case "block":
|
|
833
|
+
case "if":
|
|
834
|
+
case "for":
|
|
835
|
+
case "while":
|
|
836
|
+
case "loop":
|
|
660
837
|
case "continuing":
|
|
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":
|
|
661
848
|
emitStatement(e, ctx);
|
|
662
849
|
return;
|
|
663
850
|
case "override":
|
|
@@ -669,52 +856,131 @@ function lowerAndEmitElem(e, ctx) {
|
|
|
669
856
|
return;
|
|
670
857
|
case "fn":
|
|
671
858
|
emitRootElemNl(ctx);
|
|
859
|
+
emitRootLeading(e, ctx);
|
|
672
860
|
emitFn(e, ctx);
|
|
861
|
+
emitTrailingComments(e, ctx);
|
|
673
862
|
return;
|
|
674
863
|
case "struct":
|
|
675
864
|
emitRootElemNl(ctx);
|
|
865
|
+
emitRootLeading(e, ctx);
|
|
676
866
|
emitStruct(e, ctx);
|
|
867
|
+
emitTrailingComments(e, ctx);
|
|
677
868
|
return;
|
|
678
869
|
case "attribute":
|
|
679
870
|
emitAttribute(e, ctx);
|
|
680
871
|
return;
|
|
681
872
|
case "directive":
|
|
873
|
+
ctx.srcBuilder.addNl();
|
|
874
|
+
emitRootLeading(e, ctx);
|
|
682
875
|
emitDirective(e, ctx);
|
|
876
|
+
emitTrailingComments(e, ctx);
|
|
683
877
|
return;
|
|
684
878
|
default: assertUnreachable(e);
|
|
685
879
|
}
|
|
686
880
|
}
|
|
687
|
-
function
|
|
688
|
-
|
|
881
|
+
function emitName(e, ctx) {
|
|
882
|
+
ctx.srcBuilder.add(e.name, e.start, e.end);
|
|
689
883
|
}
|
|
690
|
-
function
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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);
|
|
695
909
|
}
|
|
696
|
-
|
|
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
|
+
});
|
|
697
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);
|
|
953
|
+
}
|
|
954
|
+
function emitModule(e, ctx) {
|
|
955
|
+
const validElements = filterValidElements(e.decls, ctx.conditions);
|
|
956
|
+
for (const child of validElements) lowerAndEmitElem(child, ctx);
|
|
698
957
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
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);
|
|
702
964
|
}
|
|
703
965
|
function emitRootDecl(e, ctx) {
|
|
704
966
|
emitRootElemNl(ctx);
|
|
705
|
-
|
|
706
|
-
|
|
967
|
+
emitRootLeading(e, ctx);
|
|
968
|
+
emitValueDecl(e, ctx);
|
|
969
|
+
emitTrailingComments(e, ctx);
|
|
707
970
|
}
|
|
708
971
|
/** Emit newlines between root elements. */
|
|
709
972
|
function emitRootElemNl(ctx) {
|
|
710
973
|
ctx.srcBuilder.addNl();
|
|
711
974
|
ctx.srcBuilder.addNl();
|
|
712
975
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
function
|
|
717
|
-
|
|
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
|
+
}
|
|
718
984
|
}
|
|
719
985
|
/** Emit function explicitly to control commas between conditional parameters. */
|
|
720
986
|
function emitFn(e, ctx) {
|
|
@@ -722,27 +988,35 @@ function emitFn(e, ctx) {
|
|
|
722
988
|
const { conditions, srcBuilder: builder } = ctx;
|
|
723
989
|
emitAttributes(attributes, ctx);
|
|
724
990
|
builder.add("fn ", name.start - 3, name.start);
|
|
991
|
+
emitInlineLeading(name, ctx);
|
|
725
992
|
emitDeclIdent(name, ctx);
|
|
993
|
+
emitInlineTrailing(name, ctx);
|
|
726
994
|
builder.appendNext("(");
|
|
727
995
|
const validParams = filterValidElements(params, conditions);
|
|
728
996
|
validParams.forEach((p, i) => {
|
|
729
|
-
|
|
730
|
-
|
|
997
|
+
emitInlineLeading(p, ctx);
|
|
998
|
+
emitAttributes(p.attributes, ctx);
|
|
999
|
+
emitTypedDecl(p.name, ctx);
|
|
1000
|
+
emitInlineTrailing(p, ctx);
|
|
731
1001
|
if (i < validParams.length - 1) builder.appendNext(", ");
|
|
732
1002
|
});
|
|
733
1003
|
builder.appendNext(") ");
|
|
734
1004
|
if (returnType) {
|
|
735
1005
|
builder.appendNext("-> ");
|
|
736
1006
|
emitAttributes(returnAttributes, ctx);
|
|
737
|
-
|
|
1007
|
+
emitInlineLeading(returnType, ctx);
|
|
1008
|
+
emitTypeRef(returnType, ctx);
|
|
738
1009
|
builder.appendNext(" ");
|
|
739
1010
|
}
|
|
740
|
-
|
|
1011
|
+
emitBlock(body, ctx);
|
|
741
1012
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
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
|
+
}
|
|
746
1020
|
}
|
|
747
1021
|
/** Emit structs explicitly to control commas between conditional members. */
|
|
748
1022
|
function emitStruct(e, ctx) {
|
|
@@ -756,125 +1030,22 @@ function emitStruct(e, ctx) {
|
|
|
756
1030
|
}
|
|
757
1031
|
emitAttributes(attributes, ctx);
|
|
758
1032
|
srcBuilder.add("struct ", start, name.start);
|
|
1033
|
+
emitInlineLeading(name, ctx);
|
|
759
1034
|
emitDeclIdent(name, ctx);
|
|
760
|
-
|
|
1035
|
+
emitInlineTrailing(name, ctx);
|
|
1036
|
+
if (validLength === 1 && !hasComments(validMembers[0])) {
|
|
761
1037
|
srcBuilder.appendNext(" { ");
|
|
762
|
-
|
|
1038
|
+
emitMember(validMembers[0], ctx);
|
|
763
1039
|
srcBuilder.appendNext(" }");
|
|
764
1040
|
srcBuilder.addNl();
|
|
765
1041
|
} else {
|
|
766
1042
|
srcBuilder.appendNext(" {");
|
|
767
1043
|
srcBuilder.addNl();
|
|
768
|
-
|
|
769
|
-
srcBuilder.appendNext(" ");
|
|
770
|
-
emitContentsNoWs(m, ctx);
|
|
771
|
-
srcBuilder.appendNext(",");
|
|
772
|
-
srcBuilder.addNl();
|
|
773
|
-
});
|
|
1044
|
+
for (const m of validMembers) emitMemberLine(m, ctx);
|
|
774
1045
|
srcBuilder.appendNext("}");
|
|
775
1046
|
srcBuilder.addNl();
|
|
776
1047
|
}
|
|
777
1048
|
}
|
|
778
|
-
function warnEmptyStruct(e) {
|
|
779
|
-
const { name, members } = e;
|
|
780
|
-
const condStr = members.length ? "(with current conditions)" : "";
|
|
781
|
-
failIdentElem(name, `struct '${name.ident.originalName}' has no members ${condStr}`);
|
|
782
|
-
}
|
|
783
|
-
function emitSynthetic(e, ctx) {
|
|
784
|
-
const { text } = e;
|
|
785
|
-
ctx.srcBuilder.addSynthetic(text, text, 0, text.length);
|
|
786
|
-
}
|
|
787
|
-
function emitContents(elem, ctx) {
|
|
788
|
-
const validElements = filterValidElements(elem.contents, ctx.conditions);
|
|
789
|
-
for (const e of validElements) lowerAndEmitElem(e, ctx);
|
|
790
|
-
}
|
|
791
|
-
/** Emit contents with leading/trailing whitespace trimming (V2 parser). */
|
|
792
|
-
function emitContentsWithTrimming(elem, ctx) {
|
|
793
|
-
const validElements = filterValidElements(elem.contents, ctx.conditions);
|
|
794
|
-
const firstEmit = validElements.findIndex((e) => !isConditionalAttr(e));
|
|
795
|
-
const lastEmit = validElements.findLastIndex((e) => !isConditionalAttr(e));
|
|
796
|
-
validElements.forEach((elem, i) => {
|
|
797
|
-
if (elem.kind === "text") {
|
|
798
|
-
let text = elem.srcModule.src.slice(elem.start, elem.end);
|
|
799
|
-
if (i === firstEmit) text = text.trimStart();
|
|
800
|
-
if (i === lastEmit) text = text.trimEnd();
|
|
801
|
-
if (text) ctx.srcBuilder.add(text, elem.start, elem.end);
|
|
802
|
-
} else lowerAndEmitElem(elem, ctx);
|
|
803
|
-
});
|
|
804
|
-
}
|
|
805
|
-
function isConditionalAttr(e) {
|
|
806
|
-
if (e.kind !== "attribute") return false;
|
|
807
|
-
const { kind } = e.attribute;
|
|
808
|
-
return kind === "@if" || kind === "@elif" || kind === "@else";
|
|
809
|
-
}
|
|
810
|
-
/** Emit contents without whitespace. */
|
|
811
|
-
function emitContentsNoWs(elem, ctx) {
|
|
812
|
-
filterValidElements(elem.contents, ctx.conditions).forEach((e) => {
|
|
813
|
-
if (e.kind === "text") {
|
|
814
|
-
const { srcModule, start, end } = e;
|
|
815
|
-
if (srcModule.src.slice(start, end).trim() === "") return;
|
|
816
|
-
}
|
|
817
|
-
lowerAndEmitElem(e, ctx);
|
|
818
|
-
});
|
|
819
|
-
}
|
|
820
|
-
function emitRefIdent(e, ctx) {
|
|
821
|
-
if (e.ident.std) ctx.srcBuilder.add(e.ident.originalName, e.start, e.end);
|
|
822
|
-
else {
|
|
823
|
-
const mangledName = displayName(findDecl(e.ident));
|
|
824
|
-
ctx.srcBuilder.add(mangledName, e.start, e.end);
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
function emitDeclIdent(e, ctx) {
|
|
828
|
-
const mangledName = displayName(e.ident);
|
|
829
|
-
ctx.srcBuilder.add(mangledName, e.start, e.end);
|
|
830
|
-
}
|
|
831
|
-
function emitExpression(e, ctx) {
|
|
832
|
-
const { kind } = e;
|
|
833
|
-
if (kind === "literal") {
|
|
834
|
-
ctx.srcBuilder.add(e.value, e.start, e.end);
|
|
835
|
-
return;
|
|
836
|
-
}
|
|
837
|
-
if (kind === "ref") {
|
|
838
|
-
emitRefIdent(e, ctx);
|
|
839
|
-
return;
|
|
840
|
-
}
|
|
841
|
-
if (kind === "type") {
|
|
842
|
-
emitContents(e, ctx);
|
|
843
|
-
return;
|
|
844
|
-
}
|
|
845
|
-
if (kind === "binary-expression") {
|
|
846
|
-
emitExpression(e.left, ctx);
|
|
847
|
-
ctx.srcBuilder.add(` ${e.operator.value} `, e.operator.span[0], e.operator.span[1]);
|
|
848
|
-
emitExpression(e.right, ctx);
|
|
849
|
-
return;
|
|
850
|
-
}
|
|
851
|
-
if (kind === "unary-expression") {
|
|
852
|
-
ctx.srcBuilder.add(e.operator.value, e.operator.span[0], e.operator.span[1]);
|
|
853
|
-
emitExpression(e.expression, ctx);
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
if (kind === "parenthesized-expression") {
|
|
857
|
-
emitExpression(e.expression, ctx);
|
|
858
|
-
return;
|
|
859
|
-
}
|
|
860
|
-
if (kind === "call-expression") {
|
|
861
|
-
emitExpression(e.function, ctx);
|
|
862
|
-
if (e.templateArgs) for (const targ of e.templateArgs) lowerAndEmitElem(targ, ctx);
|
|
863
|
-
for (const arg of e.arguments) emitExpression(arg, ctx);
|
|
864
|
-
return;
|
|
865
|
-
}
|
|
866
|
-
if (kind === "component-expression") {
|
|
867
|
-
emitExpression(e.base, ctx);
|
|
868
|
-
emitExpression(e.access, ctx);
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
|
-
if (kind === "component-member-expression") {
|
|
872
|
-
emitExpression(e.base, ctx);
|
|
873
|
-
if (e.access.kind === "name") ctx.srcBuilder.add(e.access.name, e.access.start, e.access.end);
|
|
874
|
-
return;
|
|
875
|
-
}
|
|
876
|
-
assertUnreachable(kind);
|
|
877
|
-
}
|
|
878
1049
|
function emitAttribute(e, ctx) {
|
|
879
1050
|
const { kind } = e.attribute;
|
|
880
1051
|
if (kind === "@if" || kind === "@elif" || kind === "@else") return false;
|
|
@@ -884,11 +1055,13 @@ function emitAttribute(e, ctx) {
|
|
|
884
1055
|
return true;
|
|
885
1056
|
}
|
|
886
1057
|
if (kind === "@builtin") {
|
|
887
|
-
|
|
1058
|
+
const builtinStr = `@builtin(${e.attribute.param.name})`;
|
|
1059
|
+
ctx.srcBuilder.add(builtinStr, e.start, e.end);
|
|
888
1060
|
return true;
|
|
889
1061
|
}
|
|
890
1062
|
if (kind === "@diagnostic") {
|
|
891
|
-
const
|
|
1063
|
+
const { severity, rule } = e.attribute;
|
|
1064
|
+
const diagStr = `@diagnostic${diagnosticControlToString(severity, rule)}`;
|
|
892
1065
|
ctx.srcBuilder.add(diagStr, e.start, e.end);
|
|
893
1066
|
return true;
|
|
894
1067
|
}
|
|
@@ -899,46 +1072,6 @@ function emitAttribute(e, ctx) {
|
|
|
899
1072
|
}
|
|
900
1073
|
assertUnreachable(kind);
|
|
901
1074
|
}
|
|
902
|
-
function emitStandardAttribute(e, ctx) {
|
|
903
|
-
if (e.attribute.kind !== "@attribute") return;
|
|
904
|
-
const { params } = e.attribute;
|
|
905
|
-
if (!params || params.length === 0) {
|
|
906
|
-
ctx.srcBuilder.add("@" + e.attribute.name, e.start, e.end);
|
|
907
|
-
return;
|
|
908
|
-
}
|
|
909
|
-
ctx.srcBuilder.add("@" + e.attribute.name + "(", e.start, params[0].start);
|
|
910
|
-
for (let i = 0; i < params.length; i++) {
|
|
911
|
-
emitContents(params[i], ctx);
|
|
912
|
-
if (i < params.length - 1) ctx.srcBuilder.add(",", params[i].end, params[i + 1].start);
|
|
913
|
-
}
|
|
914
|
-
ctx.srcBuilder.add(")", params[params.length - 1].end, e.end);
|
|
915
|
-
}
|
|
916
|
-
function diagnosticControlToString(severity, rule) {
|
|
917
|
-
const ruleStr = rule[0].name + (rule[1] !== null ? "." + rule[1].name : "");
|
|
918
|
-
return `(${severity.name}, ${ruleStr})`;
|
|
919
|
-
}
|
|
920
|
-
function expressionToString(elem) {
|
|
921
|
-
const { kind } = elem;
|
|
922
|
-
switch (kind) {
|
|
923
|
-
case "binary-expression": {
|
|
924
|
-
const left = expressionToString(elem.left);
|
|
925
|
-
const right = expressionToString(elem.right);
|
|
926
|
-
return `${left} ${elem.operator.value} ${right}`;
|
|
927
|
-
}
|
|
928
|
-
case "unary-expression": return `${elem.operator.value}${expressionToString(elem.expression)}`;
|
|
929
|
-
case "ref": return elem.ident.originalName;
|
|
930
|
-
case "literal": return elem.value;
|
|
931
|
-
case "parenthesized-expression": return `(${expressionToString(elem.expression)})`;
|
|
932
|
-
case "component-expression": return `${expressionToString(elem.base)}[${elem.access}]`;
|
|
933
|
-
case "component-member-expression": return `${expressionToString(elem.base)}.${elem.access}`;
|
|
934
|
-
case "call-expression": {
|
|
935
|
-
const fn = elem.function;
|
|
936
|
-
return `${fn.kind === "ref" ? fn.ident.originalName : fn.name.originalName}${elem.templateArgs ? `<...>` : ""}(${elem.arguments.map(expressionToString).join(", ")})`;
|
|
937
|
-
}
|
|
938
|
-
case "type": return elem.name.originalName;
|
|
939
|
-
default: assertUnreachable(kind);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
1075
|
function emitDirective(e, ctx) {
|
|
943
1076
|
const { directive } = e;
|
|
944
1077
|
const { kind } = directive;
|
|
@@ -960,14 +1093,351 @@ function displayName(declIdent) {
|
|
|
960
1093
|
}
|
|
961
1094
|
return declIdent.mangledName || declIdent.originalName;
|
|
962
1095
|
}
|
|
963
|
-
/**
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
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);
|
|
971
1441
|
}
|
|
972
1442
|
//#endregion
|
|
973
1443
|
//#region src/debug/ImportToString.ts
|
|
@@ -1052,19 +1522,25 @@ function astToString(elem, indent = 0) {
|
|
|
1052
1522
|
const str = new LineWrapper(indent, maxLineLength);
|
|
1053
1523
|
str.add(kind);
|
|
1054
1524
|
addElemFields(elem, str);
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
if (
|
|
1525
|
+
addCommentFields(elem, str);
|
|
1526
|
+
const children = childElems(elem);
|
|
1527
|
+
if (children.length) {
|
|
1058
1528
|
str.nl();
|
|
1529
|
+
const childStrings = children.map((e) => astToString(e, indent + 2));
|
|
1059
1530
|
str.addBlock(childStrings.join("\n"), false);
|
|
1060
1531
|
}
|
|
1061
1532
|
return str.result;
|
|
1062
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
|
+
}
|
|
1063
1540
|
function addElemFields(elem, str) {
|
|
1064
1541
|
const { kind } = elem;
|
|
1065
|
-
if (kind === "assert" || kind === "
|
|
1066
|
-
if (kind === "
|
|
1067
|
-
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") {
|
|
1068
1544
|
addTypedDeclIdent(elem.name, str);
|
|
1069
1545
|
listAttributeElems(elem.attributes, str);
|
|
1070
1546
|
} else if (kind === "struct") str.add(" " + elem.name.ident.originalName);
|
|
@@ -1072,22 +1548,14 @@ function addElemFields(elem, str) {
|
|
|
1072
1548
|
listAttributeElems(elem.attributes, str);
|
|
1073
1549
|
str.add(` ${elem.name.name}: ${typeRefElemToString(elem.typeRef)}`);
|
|
1074
1550
|
} else if (kind === "name") str.add(" " + elem.name);
|
|
1075
|
-
else if (kind === "
|
|
1076
|
-
|
|
1077
|
-
str.add(` ${elem.name.ident.originalName}.${elem.member.name}${extra}`);
|
|
1078
|
-
} else if (kind === "fn") addFnFields(elem, str);
|
|
1551
|
+
else if (kind === "fn") addFnFields(elem, str);
|
|
1552
|
+
else if (kind === "do") addDoFields(elem, str);
|
|
1079
1553
|
else if (kind === "alias") {
|
|
1080
1554
|
const prefix = elem.name.ident.kind === "decl" ? "%" : "";
|
|
1081
1555
|
str.add(` ${prefix}${elem.name.ident.originalName}=${typeRefElemToString(elem.typeRef)}`);
|
|
1082
1556
|
} else if (kind === "attribute") addAttributeFields(elem.attribute, str);
|
|
1083
|
-
else if (kind === "
|
|
1084
|
-
|
|
1085
|
-
str.add(" " + contents);
|
|
1086
|
-
} else if (kind === "type") {
|
|
1087
|
-
const nameStr = typeof elem.name === "string" ? elem.name : elem.name.originalName;
|
|
1088
|
-
const params = elem.templateParams?.map(templateParamToString).join(", ");
|
|
1089
|
-
str.add(params ? ` ${nameStr}<${params}>` : ` ${nameStr}`);
|
|
1090
|
-
} 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}'`);
|
|
1091
1559
|
else if (kind === "import") {
|
|
1092
1560
|
str.add(" " + importToString(elem.imports));
|
|
1093
1561
|
listAttributeElems(elem.attributes, str);
|
|
@@ -1095,7 +1563,7 @@ function addElemFields(elem, str) {
|
|
|
1095
1563
|
else if (kind === "typeDecl") addTypedDeclIdent(elem, str);
|
|
1096
1564
|
else if (kind === "decl") str.add(" %" + elem.ident.originalName);
|
|
1097
1565
|
else if (kind === "directive") addDirective(elem, str);
|
|
1098
|
-
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);
|
|
1099
1567
|
else if (kind === "literal") str.add(` literal(${elem.value})`);
|
|
1100
1568
|
else if (kind === "binary-expression") str.add(` binop(${elem.operator.value})`);
|
|
1101
1569
|
else if (kind === "unary-expression") str.add(` unop(${elem.operator.value})`);
|
|
@@ -1105,6 +1573,14 @@ function addElemFields(elem, str) {
|
|
|
1105
1573
|
else if (kind === "component-member-expression") str.add(" .");
|
|
1106
1574
|
else assertUnreachable(kind);
|
|
1107
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
|
+
}
|
|
1108
1584
|
function addAttributeFields(attr, str) {
|
|
1109
1585
|
const { kind } = attr;
|
|
1110
1586
|
if (kind === "@attribute") {
|
|
@@ -1118,35 +1594,33 @@ function addAttributeFields(attr, str) {
|
|
|
1118
1594
|
else if (kind === "@interpolate") str.add(` @interpolate(${attr.params.map((v) => v.name).join(", ")})`);
|
|
1119
1595
|
else assertUnreachable(kind);
|
|
1120
1596
|
}
|
|
1121
|
-
/** @return string representation of an attribute (for test/debug) */
|
|
1122
|
-
function attributeToString(attr) {
|
|
1123
|
-
const str = new LineWrapper(0, maxLineLength);
|
|
1124
|
-
addAttributeFields(attr, str);
|
|
1125
|
-
return str.result;
|
|
1126
|
-
}
|
|
1127
1597
|
function addTypedDeclIdent(elem, str) {
|
|
1128
1598
|
const { decl, typeRef } = elem;
|
|
1129
1599
|
str.add(" %" + decl.ident.originalName);
|
|
1130
1600
|
if (typeRef) str.add(" : " + typeRefElemToString(typeRef));
|
|
1131
1601
|
}
|
|
1132
|
-
function addFnFields(elem, str) {
|
|
1133
|
-
const { name, params, returnType, attributes } = elem;
|
|
1134
|
-
const paramStrs = params.map((p) => {
|
|
1135
|
-
const { originalName } = p.name.decl.ident;
|
|
1136
|
-
return `${originalName}: ${typeRefElemToString(p.name.typeRef)}`;
|
|
1137
|
-
});
|
|
1138
|
-
str.add(` ${name.ident.originalName}(${paramStrs.join(", ")})`);
|
|
1139
|
-
listAttributeElems(attributes, str);
|
|
1140
|
-
if (returnType) str.add(" -> " + typeRefElemToString(returnType));
|
|
1141
|
-
}
|
|
1142
1602
|
/** show attribute names in short form to verify collection */
|
|
1143
1603
|
function listAttributeElems(attrs, str) {
|
|
1144
1604
|
attrs?.forEach((a) => {
|
|
1145
1605
|
str.add(" " + attributeName(a.attribute));
|
|
1146
1606
|
});
|
|
1147
1607
|
}
|
|
1148
|
-
function
|
|
1149
|
-
|
|
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);
|
|
1150
1624
|
}
|
|
1151
1625
|
function addDirective(elem, str) {
|
|
1152
1626
|
const { directive, attributes } = elem;
|
|
@@ -1159,30 +1633,29 @@ function addDirective(elem, str) {
|
|
|
1159
1633
|
else assertUnreachable(kind);
|
|
1160
1634
|
listAttributeElems(attributes, str);
|
|
1161
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
|
+
}
|
|
1162
1642
|
function unknownExpressionToString(elem) {
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
}).join(" ");
|
|
1643
|
+
return astToString(elem.expression);
|
|
1644
|
+
}
|
|
1645
|
+
function attributeName(attr) {
|
|
1646
|
+
return attr.kind === "@attribute" ? "@" + attr.name : attr.kind;
|
|
1168
1647
|
}
|
|
1169
1648
|
function templateParamToString(p) {
|
|
1170
1649
|
if (typeof p === "string") return p;
|
|
1171
1650
|
if (p.kind === "type") return typeRefElemToString(p);
|
|
1172
|
-
return
|
|
1651
|
+
return expressionToString(p);
|
|
1173
1652
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
}
|
|
1180
|
-
function debugContentsToString(elem) {
|
|
1181
|
-
return elem.contents.map((c) => {
|
|
1182
|
-
if (c.kind === "text") return c.srcModule.src.slice(c.start, c.end);
|
|
1183
|
-
if (c.kind === "ref") return c.ident.originalName;
|
|
1184
|
-
return `?${c.kind}?`;
|
|
1185
|
-
}).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(", ");
|
|
1186
1659
|
}
|
|
1187
1660
|
//#endregion
|
|
1188
1661
|
//#region src/debug/ScopeToString.ts
|
|
@@ -1365,53 +1838,184 @@ var ParseError = class extends Error {
|
|
|
1365
1838
|
}
|
|
1366
1839
|
};
|
|
1367
1840
|
//#endregion
|
|
1368
|
-
//#region src/parse/
|
|
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
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
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,
|
|
1397
1927
|
start,
|
|
1398
1928
|
end,
|
|
1399
1929
|
srcModule
|
|
1400
1930
|
};
|
|
1931
|
+
if (lineBreaksBefore(srcModule.src, start) >= 2) comment.blankBefore = true;
|
|
1932
|
+
return comment;
|
|
1401
1933
|
}
|
|
1402
|
-
/**
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
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;
|
|
1415
2019
|
}
|
|
1416
2020
|
//#endregion
|
|
1417
2021
|
//#region src/parse/ExpressionUtil.ts
|
|
@@ -1425,9 +2029,11 @@ function makeLiteral(token) {
|
|
|
1425
2029
|
};
|
|
1426
2030
|
}
|
|
1427
2031
|
function makeUnaryOperator(token) {
|
|
2032
|
+
const [start, end] = token.span;
|
|
1428
2033
|
return {
|
|
1429
2034
|
value: token.text,
|
|
1430
|
-
|
|
2035
|
+
start,
|
|
2036
|
+
end
|
|
1431
2037
|
};
|
|
1432
2038
|
}
|
|
1433
2039
|
function makeBinaryOperator(token) {
|
|
@@ -1437,12 +2043,11 @@ function makeBinaryOperator(token) {
|
|
|
1437
2043
|
};
|
|
1438
2044
|
}
|
|
1439
2045
|
function makeUnaryExpression(operator, expr) {
|
|
1440
|
-
const [start] = operator.span;
|
|
1441
2046
|
return {
|
|
1442
2047
|
kind: "unary-expression",
|
|
1443
2048
|
operator,
|
|
1444
2049
|
expression: expr,
|
|
1445
|
-
start,
|
|
2050
|
+
start: operator.start,
|
|
1446
2051
|
end: expr.end
|
|
1447
2052
|
};
|
|
1448
2053
|
}
|
|
@@ -1503,13 +2108,16 @@ function expectWord(stream, errorMsg) {
|
|
|
1503
2108
|
stream.nextToken();
|
|
1504
2109
|
return token;
|
|
1505
2110
|
}
|
|
1506
|
-
/** Parse
|
|
2111
|
+
/** Parse a required expression, throwing if absent. */
|
|
1507
2112
|
function expectExpression(ctx, errorMsg = "Expected expression") {
|
|
1508
2113
|
const expr = parseExpression(ctx);
|
|
1509
2114
|
if (!expr) throwParseError(ctx.stream, errorMsg);
|
|
1510
|
-
if (ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
1511
2115
|
return expr;
|
|
1512
2116
|
}
|
|
2117
|
+
/** Parse an optional expression. */
|
|
2118
|
+
function parseContentExpression(ctx, opts) {
|
|
2119
|
+
return parseExpression(ctx, opts);
|
|
2120
|
+
}
|
|
1513
2121
|
/** Throw a ParseError at the current/next token position. */
|
|
1514
2122
|
function throwParseError(stream, message) {
|
|
1515
2123
|
const weslStream = stream;
|
|
@@ -1565,18 +2173,27 @@ function makeRefIdentElem(ctx, refIdent, start, end) {
|
|
|
1565
2173
|
refIdent.refIdentElem = elem;
|
|
1566
2174
|
return elem;
|
|
1567
2175
|
}
|
|
1568
|
-
|
|
2176
|
+
/** @return true if the attribute is a conditional (@if, @elif, @else) */
|
|
2177
|
+
function isConditionalAttribute(attr) {
|
|
1569
2178
|
const { kind } = attr;
|
|
1570
2179
|
return kind === "@if" || kind === "@elif" || kind === "@else";
|
|
1571
2180
|
}
|
|
1572
2181
|
/** @return true if any attribute is a conditional (@if, @elif, @else) */
|
|
1573
2182
|
function hasConditionalAttribute(attributes) {
|
|
1574
|
-
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;
|
|
1575
2188
|
}
|
|
1576
2189
|
/** Attach non-empty attributes array to element. */
|
|
1577
2190
|
function attachAttributes(elem, attributes) {
|
|
1578
2191
|
if (attributes?.length) elem.attributes = attributes;
|
|
1579
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
|
+
}
|
|
1580
2197
|
/** Link a DeclIdentElem's ident to its parent declaration. */
|
|
1581
2198
|
function linkDeclIdentElem(declIdentElem, declElem) {
|
|
1582
2199
|
declIdentElem.ident.declElem = declElem;
|
|
@@ -1719,10 +2336,7 @@ function parseIdent(ctx, conditionRef) {
|
|
|
1719
2336
|
const ident = ctx.createRefIdent(parts.join("::"));
|
|
1720
2337
|
if (conditionRef) ident.conditionRef = true;
|
|
1721
2338
|
const refIdentElem = makeRefIdentElem(ctx, ident, start, end);
|
|
1722
|
-
if (!conditionRef)
|
|
1723
|
-
ctx.saveIdent(ident);
|
|
1724
|
-
ctx.addElem(refIdentElem);
|
|
1725
|
-
}
|
|
2339
|
+
if (!conditionRef) ctx.saveIdent(ident);
|
|
1726
2340
|
return refIdentElem;
|
|
1727
2341
|
}
|
|
1728
2342
|
/** Check if token is valid as a path segment (word or allowed keyword). */
|
|
@@ -1744,19 +2358,20 @@ function parseSimpleTypeRef(ctx) {
|
|
|
1744
2358
|
if (!path) return null;
|
|
1745
2359
|
const { parts, start, end: nameEnd } = path;
|
|
1746
2360
|
const refIdent = ctx.createRefIdent(parts.join("::"));
|
|
1747
|
-
|
|
1748
|
-
const refIdentElem = makeRefIdentElem(ctx, refIdent, start, nameEnd);
|
|
2361
|
+
makeRefIdentElem(ctx, refIdent, start, nameEnd);
|
|
1749
2362
|
ctx.saveIdent(refIdent);
|
|
1750
|
-
|
|
1751
|
-
|
|
2363
|
+
return {
|
|
2364
|
+
kind: "type",
|
|
1752
2365
|
name: refIdent,
|
|
1753
|
-
templateParams: ctx.stream.nextTemplateStartToken() ? parseTemplateParams(ctx) : void 0
|
|
1754
|
-
|
|
2366
|
+
templateParams: ctx.stream.nextTemplateStartToken() ? parseTemplateParams(ctx) : void 0,
|
|
2367
|
+
start,
|
|
2368
|
+
end: ctx.stream.checkpoint()
|
|
2369
|
+
};
|
|
1755
2370
|
}
|
|
1756
2371
|
/** Parse comma-separated template parameters until closing '>'. */
|
|
1757
2372
|
function parseTemplateParams(ctx) {
|
|
1758
2373
|
const { stream } = ctx;
|
|
1759
|
-
if (consumeTemplateEnd(stream))
|
|
2374
|
+
if (consumeTemplateEnd(stream)) throwParseError(stream, "Empty template parameter list '<>'");
|
|
1760
2375
|
const params = [parseTemplateParam(ctx)];
|
|
1761
2376
|
while (stream.matchText(",")) params.push(parseTemplateParam(ctx));
|
|
1762
2377
|
if (!consumeTemplateEnd(stream)) throwParseError(stream, "Expected '>' or ',' after template parameter");
|
|
@@ -1771,8 +2386,8 @@ function consumeTemplateEnd(stream) {
|
|
|
1771
2386
|
/** Grammar: template_arg_expression : expression */
|
|
1772
2387
|
function parseTemplateParam(ctx) {
|
|
1773
2388
|
const expr = parseExpression(ctx, { inTemplate: true });
|
|
1774
|
-
if (expr)
|
|
1775
|
-
|
|
2389
|
+
if (!expr) throwParseError(ctx.stream, "Expected expression in template parameters");
|
|
2390
|
+
return expr;
|
|
1776
2391
|
}
|
|
1777
2392
|
//#endregion
|
|
1778
2393
|
//#region src/parse/ParseCall.ts
|
|
@@ -1783,7 +2398,7 @@ function parseTemplateParam(ctx) {
|
|
|
1783
2398
|
function parseCallSuffix(ctx, current, parseExpr) {
|
|
1784
2399
|
if (current.kind !== "ref" && current.kind !== "type") return null;
|
|
1785
2400
|
const { stream } = ctx;
|
|
1786
|
-
const templateArgs = current.kind === "
|
|
2401
|
+
const templateArgs = current.kind === "ref" ? parseCallTemplateArgs(ctx) : null;
|
|
1787
2402
|
if (!stream.matchText("(")) return null;
|
|
1788
2403
|
const args = [];
|
|
1789
2404
|
while (true) {
|
|
@@ -1948,8 +2563,7 @@ function parseTemplateElaboratedIdent(ctx, conditionRef) {
|
|
|
1948
2563
|
name: refIdent.ident,
|
|
1949
2564
|
templateParams,
|
|
1950
2565
|
start: refIdent.start,
|
|
1951
|
-
end: ctx.stream.checkpoint()
|
|
1952
|
-
contents: []
|
|
2566
|
+
end: ctx.stream.checkpoint()
|
|
1953
2567
|
};
|
|
1954
2568
|
}
|
|
1955
2569
|
/** Parse postfix operators: member access, indexing, function calls. */
|
|
@@ -1976,7 +2590,9 @@ function parseMemberAccess(ctx, base) {
|
|
|
1976
2590
|
function parseIndexAccess(ctx, base) {
|
|
1977
2591
|
const { stream } = ctx;
|
|
1978
2592
|
if (!stream.matchText("[")) return null;
|
|
1979
|
-
|
|
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]);
|
|
1980
2596
|
}
|
|
1981
2597
|
//#endregion
|
|
1982
2598
|
//#region src/parse/ParseAttribute.ts
|
|
@@ -1997,6 +2613,20 @@ function parseElseAttribute(ctx) {
|
|
|
1997
2613
|
function parseElifAttribute(ctx) {
|
|
1998
2614
|
return parseConditionalAttribute(ctx, "elif", makeElifAttribute);
|
|
1999
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
|
+
}
|
|
2000
2630
|
/**
|
|
2001
2631
|
* Grammar: attribute :
|
|
2002
2632
|
* '@' ident_pattern_token argument_expression_list ?
|
|
@@ -2007,15 +2637,8 @@ function parseElifAttribute(ctx) {
|
|
|
2007
2637
|
* WESL extensions: @if, @elif, @else
|
|
2008
2638
|
*/
|
|
2009
2639
|
function parseAttribute(ctx) {
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
if (!stream.matchText("@")) return null;
|
|
2013
|
-
stream.reset(startPos);
|
|
2014
|
-
const weslAttr = parseWeslConditional(ctx);
|
|
2015
|
-
if (weslAttr) return weslAttr;
|
|
2016
|
-
const stdAttr = parseStandardAttribute(ctx);
|
|
2017
|
-
if (stdAttr) return stdAttr;
|
|
2018
|
-
return null;
|
|
2640
|
+
if (ctx.stream.peek()?.text !== "@") return null;
|
|
2641
|
+
return parseWeslConditional(ctx) ?? parseStandardAttribute(ctx);
|
|
2019
2642
|
}
|
|
2020
2643
|
/** Parse `@if(expr)` or `@elif(expr)` conditional attributes. */
|
|
2021
2644
|
function parseConditionalAttribute(ctx, keyword, makeAttr) {
|
|
@@ -2024,13 +2647,15 @@ function parseConditionalAttribute(ctx, keyword, makeAttr) {
|
|
|
2024
2647
|
if (!stream.matchSequence("@", keyword)) return null;
|
|
2025
2648
|
expect(stream, "(", `@${keyword}`);
|
|
2026
2649
|
const expr = parseExpression(ctx, true);
|
|
2027
|
-
if (!expr)
|
|
2650
|
+
if (!expr) throwParseError(stream, `Expected expression after @${keyword}(`);
|
|
2028
2651
|
stream.matchText(",");
|
|
2029
2652
|
expect(stream, ")", `@${keyword} expression`);
|
|
2030
|
-
return makeAttr(
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2653
|
+
return makeAttr({
|
|
2654
|
+
kind: "translate-time-expression",
|
|
2655
|
+
expression: expr,
|
|
2656
|
+
start: startPos,
|
|
2657
|
+
end: stream.checkpoint()
|
|
2658
|
+
});
|
|
2034
2659
|
}
|
|
2035
2660
|
function makeIfAttribute(param) {
|
|
2036
2661
|
return {
|
|
@@ -2047,19 +2672,13 @@ function makeElifAttribute(param) {
|
|
|
2047
2672
|
param
|
|
2048
2673
|
};
|
|
2049
2674
|
}
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
if (ifAttr) return attributeElem(ifAttr, startPos, stream.checkpoint());
|
|
2058
|
-
const elifAttr = parseElifAttribute(ctx);
|
|
2059
|
-
if (elifAttr) return attributeElem(elifAttr, startPos, stream.checkpoint());
|
|
2060
|
-
const elseAttr = parseElseAttribute(ctx);
|
|
2061
|
-
if (elseAttr) return attributeElem(elseAttr, startPos, stream.checkpoint());
|
|
2062
|
-
return null;
|
|
2675
|
+
function attributeElem(attribute, start, end) {
|
|
2676
|
+
return {
|
|
2677
|
+
kind: "attribute",
|
|
2678
|
+
attribute,
|
|
2679
|
+
start,
|
|
2680
|
+
end
|
|
2681
|
+
};
|
|
2063
2682
|
}
|
|
2064
2683
|
/** Parse a standard attribute (not @if/@elif/@else) */
|
|
2065
2684
|
function parseStandardAttribute(ctx) {
|
|
@@ -2069,7 +2688,7 @@ function parseStandardAttribute(ctx) {
|
|
|
2069
2688
|
if (!atToken) return null;
|
|
2070
2689
|
const startPos = atToken.span[0];
|
|
2071
2690
|
const nameToken = stream.peek();
|
|
2072
|
-
if (
|
|
2691
|
+
if (nameToken?.kind !== "word" && nameToken?.kind !== "keyword") {
|
|
2073
2692
|
stream.reset(resetPos);
|
|
2074
2693
|
return null;
|
|
2075
2694
|
}
|
|
@@ -2092,22 +2711,6 @@ function parseStandardAttribute(ctx) {
|
|
|
2092
2711
|
params
|
|
2093
2712
|
}, startPos, stream.checkpoint());
|
|
2094
2713
|
}
|
|
2095
|
-
function makeTranslateTimeExpressionElem(args) {
|
|
2096
|
-
return {
|
|
2097
|
-
kind: "translate-time-expression",
|
|
2098
|
-
expression: args.value,
|
|
2099
|
-
span: args.span
|
|
2100
|
-
};
|
|
2101
|
-
}
|
|
2102
|
-
function attributeElem(attribute, start, end) {
|
|
2103
|
-
return {
|
|
2104
|
-
kind: "attribute",
|
|
2105
|
-
attribute,
|
|
2106
|
-
start,
|
|
2107
|
-
end,
|
|
2108
|
-
contents: []
|
|
2109
|
-
};
|
|
2110
|
-
}
|
|
2111
2714
|
function parseBuiltinAttribute(ctx, startPos) {
|
|
2112
2715
|
const { stream } = ctx;
|
|
2113
2716
|
expect(stream, "(", "@builtin");
|
|
@@ -2128,9 +2731,6 @@ function parseInterpolateAttribute(ctx, startPos) {
|
|
|
2128
2731
|
params
|
|
2129
2732
|
}, startPos, stream.checkpoint());
|
|
2130
2733
|
}
|
|
2131
|
-
function parseNameElem(ctx) {
|
|
2132
|
-
return makeNameElem(expectWord(ctx.stream, "Expected identifier"));
|
|
2133
|
-
}
|
|
2134
2734
|
/** @diagnostic(severity, rule) or @diagnostic(severity, namespace.rule) */
|
|
2135
2735
|
function parseDiagnosticAttribute(ctx, startPos) {
|
|
2136
2736
|
const { stream } = ctx;
|
|
@@ -2152,78 +2752,20 @@ function parseDiagnosticAttribute(ctx, startPos) {
|
|
|
2152
2752
|
function parseAttributeParams(ctx) {
|
|
2153
2753
|
return parseCommaList(ctx, parseAttrParam);
|
|
2154
2754
|
}
|
|
2755
|
+
function parseNameElem(ctx) {
|
|
2756
|
+
return makeNameElem(expectWord(ctx.stream, "Expected identifier"));
|
|
2757
|
+
}
|
|
2155
2758
|
function parseAttrParam(ctx) {
|
|
2156
2759
|
const { stream } = ctx;
|
|
2157
2760
|
const start = stream.checkpoint();
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
const end = stream.checkpoint();
|
|
2761
|
+
const expression = parseExpression(ctx);
|
|
2762
|
+
if (!expression) throwParseError(stream, "Expected attribute parameter");
|
|
2161
2763
|
return {
|
|
2162
2764
|
kind: "expression",
|
|
2765
|
+
expression,
|
|
2163
2766
|
start,
|
|
2164
|
-
end,
|
|
2165
|
-
contents: finishContents(ctx, start, end)
|
|
2166
|
-
};
|
|
2167
|
-
}
|
|
2168
|
-
//#endregion
|
|
2169
|
-
//#region src/parse/ParseDirective.ts
|
|
2170
|
-
/** Grammar: global_directive : diagnostic_directive | enable_directive | requires_directive */
|
|
2171
|
-
function parseDirective(ctx) {
|
|
2172
|
-
const { stream } = ctx;
|
|
2173
|
-
const startPos = stream.checkpoint();
|
|
2174
|
-
const attributes = parseAttributeList(ctx);
|
|
2175
|
-
const attrs = attributes.length > 0 ? attributes : void 0;
|
|
2176
|
-
const result = parseExtensionDirective(ctx, "enable", attrs) || parseExtensionDirective(ctx, "requires", attrs) || parseDiagnosticDirective(ctx, attrs);
|
|
2177
|
-
if (!result) stream.reset(startPos);
|
|
2178
|
-
return result;
|
|
2179
|
-
}
|
|
2180
|
-
/** Grammar: enable_directive | requires_directive : keyword extension_list ';' */
|
|
2181
|
-
function parseExtensionDirective(ctx, keyword, attributes) {
|
|
2182
|
-
const { stream } = ctx;
|
|
2183
|
-
const token = stream.matchText(keyword);
|
|
2184
|
-
if (!token) return null;
|
|
2185
|
-
const extensions = parseCommaList(ctx, parseDirectiveName);
|
|
2186
|
-
expect(stream, ";", `${keyword} directive`);
|
|
2187
|
-
return makeDirectiveElem({
|
|
2188
|
-
kind: keyword,
|
|
2189
|
-
extensions
|
|
2190
|
-
}, token, stream, attributes);
|
|
2191
|
-
}
|
|
2192
|
-
function parseDirectiveName(ctx) {
|
|
2193
|
-
return makeNameElem(expectWord(ctx.stream, "Expected identifier in name list"));
|
|
2194
|
-
}
|
|
2195
|
-
/**
|
|
2196
|
-
* Grammar: diagnostic_directive : 'diagnostic' diagnostic_control ';'
|
|
2197
|
-
* Grammar: diagnostic_control : '(' severity_control_name ',' diagnostic_rule_name ',' ? ')'
|
|
2198
|
-
*/
|
|
2199
|
-
function parseDiagnosticDirective(ctx, attributes) {
|
|
2200
|
-
const { stream } = ctx;
|
|
2201
|
-
const token = stream.matchText("diagnostic");
|
|
2202
|
-
if (!token) return null;
|
|
2203
|
-
expect(stream, "(", "diagnostic");
|
|
2204
|
-
const severity = makeNameElem(expectWord(stream, "Expected severity in diagnostic"));
|
|
2205
|
-
expect(stream, ",", "diagnostic severity");
|
|
2206
|
-
const ruleName = makeNameElem(expectWord(stream, "Expected rule name in diagnostic"));
|
|
2207
|
-
let subrule = null;
|
|
2208
|
-
if (stream.matchText(".")) subrule = makeNameElem(expectWord(stream, "Expected subrule name after '.'"));
|
|
2209
|
-
stream.matchText(",");
|
|
2210
|
-
expect(stream, ")", "diagnostic rule");
|
|
2211
|
-
expect(stream, ";", "diagnostic directive");
|
|
2212
|
-
return makeDirectiveElem({
|
|
2213
|
-
kind: "diagnostic",
|
|
2214
|
-
severity,
|
|
2215
|
-
rule: [ruleName, subrule]
|
|
2216
|
-
}, token, stream, attributes);
|
|
2217
|
-
}
|
|
2218
|
-
function makeDirectiveElem(directive, token, stream, attributes) {
|
|
2219
|
-
const elem = {
|
|
2220
|
-
kind: "directive",
|
|
2221
|
-
directive,
|
|
2222
|
-
start: attributes?.[0]?.start ?? token.span[0],
|
|
2223
2767
|
end: stream.checkpoint()
|
|
2224
2768
|
};
|
|
2225
|
-
attachAttributes(elem, attributes);
|
|
2226
|
-
return elem;
|
|
2227
2769
|
}
|
|
2228
2770
|
//#endregion
|
|
2229
2771
|
//#region src/parse/ParseControlFlow.ts
|
|
@@ -2234,37 +2776,50 @@ function makeDirectiveElem(directive, token, stream, attributes) {
|
|
|
2234
2776
|
function parseIfStatement(ctx, attributes) {
|
|
2235
2777
|
const startPos = beginStatement(ctx, "if", attributes);
|
|
2236
2778
|
if (startPos === null) return null;
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
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);
|
|
2242
2784
|
}
|
|
2243
2785
|
/** Grammar: switch_statement : attribute* 'switch' expression switch_body */
|
|
2244
2786
|
function parseSwitchStatement(ctx, attributes) {
|
|
2245
2787
|
const startPos = beginStatement(ctx, "switch", attributes);
|
|
2246
2788
|
if (startPos === null) return null;
|
|
2247
|
-
expectExpression(ctx, "Expected expression after 'switch'");
|
|
2248
|
-
expectSwitchClauses(ctx);
|
|
2249
|
-
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);
|
|
2250
2796
|
}
|
|
2251
2797
|
/**
|
|
2252
2798
|
* Grammar: else_if_clause : 'else' 'if' expression compound_statement
|
|
2253
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.
|
|
2254
2803
|
*/
|
|
2255
2804
|
function parseElseChain(ctx) {
|
|
2256
2805
|
const { stream } = ctx;
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
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
|
+
};
|
|
2267
2821
|
}
|
|
2822
|
+
return expectCompound(ctx, "Expected '{' after else");
|
|
2268
2823
|
}
|
|
2269
2824
|
/**
|
|
2270
2825
|
* Grammar: switch_body : attribute* '{' switch_clause+ '}'
|
|
@@ -2274,27 +2829,43 @@ function parseElseChain(ctx) {
|
|
|
2274
2829
|
*/
|
|
2275
2830
|
function expectSwitchClauses(ctx) {
|
|
2276
2831
|
const { stream } = ctx;
|
|
2277
|
-
parseAttributeList(ctx);
|
|
2832
|
+
const bodyAttrs = parseAttributeList(ctx);
|
|
2278
2833
|
expect(stream, "{", "switch expression");
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
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'");
|
|
2291
2857
|
}
|
|
2858
|
+
return finishStatement("switch-clause", clauseStart, ctx, {
|
|
2859
|
+
selectors,
|
|
2860
|
+
body
|
|
2861
|
+
}, attrs);
|
|
2292
2862
|
}
|
|
2293
2863
|
/** Grammar: case_selectors : case_selector (',' case_selector)* ','? */
|
|
2294
2864
|
function parseCaseSelectors(ctx) {
|
|
2295
2865
|
const { stream } = ctx;
|
|
2296
|
-
expectExpression(ctx, "Expected expression after 'case'");
|
|
2297
|
-
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;
|
|
2298
2869
|
}
|
|
2299
2870
|
/**
|
|
2300
2871
|
* Grammar: case_clause : 'case' case_selectors ':'? compound_statement
|
|
@@ -2302,16 +2873,15 @@ function parseCaseSelectors(ctx) {
|
|
|
2302
2873
|
*/
|
|
2303
2874
|
function parseCaseBody(ctx, errorMsg) {
|
|
2304
2875
|
ctx.stream.matchText(":");
|
|
2305
|
-
const
|
|
2306
|
-
const body = parseCompoundStatement(ctx, bodyAttrs.length > 0 ? bodyAttrs : void 0);
|
|
2876
|
+
const body = parseCompoundStatement(ctx, attrsOrUndef(parseAttributeList(ctx)));
|
|
2307
2877
|
if (!body) throwParseError(ctx.stream, errorMsg);
|
|
2308
|
-
|
|
2878
|
+
return body;
|
|
2309
2879
|
}
|
|
2310
2880
|
//#endregion
|
|
2311
2881
|
//#region src/parse/ParseValueDeclaration.ts
|
|
2312
2882
|
/** Grammar: 'const' optionally_typed_ident '=' expression (global or local) */
|
|
2313
2883
|
function parseConstDecl(ctx, attributes) {
|
|
2314
|
-
return parseValueDecl(ctx, "const", true, isModuleScope(
|
|
2884
|
+
return parseValueDecl(ctx, "const", true, ctx.isModuleScope(), attributes);
|
|
2315
2885
|
}
|
|
2316
2886
|
/** Grammar: 'override' optionally_typed_ident ( '=' expression )? */
|
|
2317
2887
|
function parseOverrideDecl(ctx, attributes) {
|
|
@@ -2322,20 +2892,16 @@ function parseTypedDecl(ctx, isGlobal = true) {
|
|
|
2322
2892
|
const nameToken = ctx.stream.matchKind("word");
|
|
2323
2893
|
if (!nameToken) return null;
|
|
2324
2894
|
const start = nameToken.span[0];
|
|
2325
|
-
beginElem(ctx, "typeDecl");
|
|
2326
2895
|
const decl = createDeclIdentElem(ctx, nameToken, isGlobal);
|
|
2327
|
-
ctx.addElem(decl);
|
|
2328
2896
|
ctx.saveIdent(decl.ident);
|
|
2329
2897
|
const { typeRef, typeScope } = parseOptionalType(ctx);
|
|
2330
|
-
const end = ctx.stream.checkpoint();
|
|
2331
2898
|
return {
|
|
2332
2899
|
kind: "typeDecl",
|
|
2333
2900
|
decl,
|
|
2334
2901
|
typeRef,
|
|
2335
2902
|
typeScope,
|
|
2336
2903
|
start,
|
|
2337
|
-
end
|
|
2338
|
-
contents: finishContents(ctx, start, end)
|
|
2904
|
+
end: ctx.stream.checkpoint()
|
|
2339
2905
|
};
|
|
2340
2906
|
}
|
|
2341
2907
|
/** Shared parser for const/override declarations. */
|
|
@@ -2345,43 +2911,29 @@ function parseValueDecl(ctx, keyword, requiresInit, isGlobal, attributes) {
|
|
|
2345
2911
|
if (!token) return null;
|
|
2346
2912
|
const startPos = getStartWithAttributes(attributes, token.span[0]);
|
|
2347
2913
|
ctx.pushScope("partial");
|
|
2348
|
-
beginElem(ctx, keyword, attributes);
|
|
2349
2914
|
const typedDecl = parseTypedDecl(ctx, isGlobal);
|
|
2350
2915
|
if (!typedDecl) throwParseError(stream, `Expected identifier after '${keyword}'`);
|
|
2351
|
-
|
|
2916
|
+
let init;
|
|
2352
2917
|
if (requiresInit) {
|
|
2353
2918
|
expect(stream, "=", `${keyword} identifier`);
|
|
2354
|
-
expectExpression(ctx);
|
|
2355
|
-
} else if (stream.matchText("=")) expectExpression(ctx);
|
|
2919
|
+
init = expectExpression(ctx);
|
|
2920
|
+
} else if (stream.matchText("=")) init = expectExpression(ctx);
|
|
2356
2921
|
expect(stream, ";", `${keyword} declaration`);
|
|
2357
|
-
const endPos = stream.checkpoint();
|
|
2358
|
-
const contents = finishContents(ctx, startPos, endPos);
|
|
2359
2922
|
typedDecl.decl.ident.dependentScope = ctx.currentScope();
|
|
2360
2923
|
ctx.popScope();
|
|
2361
|
-
const elem = {
|
|
2362
|
-
kind: keyword,
|
|
2924
|
+
const elem = finishStatement(keyword, startPos, ctx, {
|
|
2363
2925
|
name: typedDecl,
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
contents
|
|
2367
|
-
};
|
|
2368
|
-
attachAttributes(elem, attributes);
|
|
2926
|
+
init
|
|
2927
|
+
}, attributes);
|
|
2369
2928
|
linkDeclIdent(typedDecl, elem);
|
|
2370
2929
|
return elem;
|
|
2371
2930
|
}
|
|
2372
|
-
/** @return true if ctx is at module level (not inside fn/block) */
|
|
2373
|
-
function isModuleScope(ctx) {
|
|
2374
|
-
let scope = ctx.currentScope();
|
|
2375
|
-
while (scope.kind === "partial" && scope.parent) scope = scope.parent;
|
|
2376
|
-
return scope.parent === null;
|
|
2377
|
-
}
|
|
2378
2931
|
/** Parse optional ': type' annotation, managing scope for type references. */
|
|
2379
2932
|
function parseOptionalType(ctx) {
|
|
2380
2933
|
if (!ctx.stream.matchText(":")) return {};
|
|
2381
2934
|
ctx.pushScope();
|
|
2382
2935
|
const typeRef = parseSimpleTypeRef(ctx);
|
|
2383
2936
|
if (!typeRef) throwParseError(ctx.stream, "Expected type after ':'");
|
|
2384
|
-
ctx.addElem(typeRef);
|
|
2385
2937
|
const typeScope = ctx.currentScope();
|
|
2386
2938
|
ctx.popScope();
|
|
2387
2939
|
return {
|
|
@@ -2401,17 +2953,19 @@ function parseGlobalVarDecl(ctx, attributes) {
|
|
|
2401
2953
|
if (!varToken) return null;
|
|
2402
2954
|
const startPos = getStartWithAttributes(attributes, varToken.span[0]);
|
|
2403
2955
|
ctx.pushScope("partial");
|
|
2404
|
-
|
|
2405
|
-
skipTemplateList(ctx);
|
|
2956
|
+
const template = parseTemplateList(ctx);
|
|
2406
2957
|
const typedDecl = parseTypedDecl(ctx);
|
|
2407
2958
|
if (!typedDecl) throwParseError(stream, "Expected identifier after 'var'");
|
|
2408
|
-
|
|
2409
|
-
if (stream.matchText("=")) expectExpression(ctx);
|
|
2959
|
+
let init;
|
|
2960
|
+
if (stream.matchText("=")) init = expectExpression(ctx);
|
|
2410
2961
|
expect(stream, ";", "var declaration");
|
|
2411
2962
|
typedDecl.decl.ident.dependentScope = ctx.currentScope();
|
|
2412
2963
|
ctx.popScope();
|
|
2413
|
-
const varElem =
|
|
2414
|
-
|
|
2964
|
+
const varElem = finishStatement("gvar", startPos, ctx, {
|
|
2965
|
+
name: typedDecl,
|
|
2966
|
+
template,
|
|
2967
|
+
init
|
|
2968
|
+
}, attributes);
|
|
2415
2969
|
linkDeclIdent(typedDecl, varElem);
|
|
2416
2970
|
return varElem;
|
|
2417
2971
|
}
|
|
@@ -2421,23 +2975,19 @@ function parseAliasDecl(ctx, attributes) {
|
|
|
2421
2975
|
const aliasToken = stream.matchText("alias");
|
|
2422
2976
|
if (!aliasToken) return null;
|
|
2423
2977
|
const startPos = getStartWithAttributes(attributes, aliasToken.span[0]);
|
|
2424
|
-
beginElem(ctx, "alias", attributes);
|
|
2425
2978
|
const declIdentElem = createDeclIdentElem(ctx, expectWord(stream, "Expected identifier after 'alias'"), true);
|
|
2426
|
-
ctx.addElem(declIdentElem);
|
|
2427
2979
|
ctx.saveIdent(declIdentElem.ident);
|
|
2428
2980
|
expect(stream, "=", "alias name");
|
|
2429
2981
|
ctx.pushScope();
|
|
2430
2982
|
const typeRef = parseSimpleTypeRef(ctx);
|
|
2431
2983
|
if (!typeRef) throwParseError(stream, "Expected type after '=' in alias declaration");
|
|
2432
|
-
ctx.addElem(typeRef);
|
|
2433
2984
|
declIdentElem.ident.dependentScope = ctx.currentScope();
|
|
2434
2985
|
ctx.popScope();
|
|
2435
2986
|
expect(stream, ";", "alias declaration");
|
|
2436
|
-
const aliasElem =
|
|
2987
|
+
const aliasElem = finishStatement("alias", startPos, ctx, {
|
|
2437
2988
|
name: declIdentElem,
|
|
2438
2989
|
typeRef
|
|
2439
|
-
});
|
|
2440
|
-
attachAttributes(aliasElem, attributes);
|
|
2990
|
+
}, attributes);
|
|
2441
2991
|
linkDeclIdentElem(declIdentElem, aliasElem);
|
|
2442
2992
|
return aliasElem;
|
|
2443
2993
|
}
|
|
@@ -2446,25 +2996,29 @@ function parseConstAssert(ctx, attributes) {
|
|
|
2446
2996
|
const assertToken = ctx.stream.matchText("const_assert");
|
|
2447
2997
|
if (!assertToken) return null;
|
|
2448
2998
|
const startPos = getStartWithAttributes(attributes, assertToken.span[0]);
|
|
2449
|
-
|
|
2450
|
-
expectExpression(ctx);
|
|
2999
|
+
const expression = expectExpression(ctx);
|
|
2451
3000
|
expect(ctx.stream, ";", "const_assert expression");
|
|
2452
|
-
|
|
2453
|
-
attachAttributes(elem, attributes);
|
|
2454
|
-
return elem;
|
|
3001
|
+
return finishStatement("assert", startPos, ctx, { expression }, attributes);
|
|
2455
3002
|
}
|
|
2456
|
-
/**
|
|
2457
|
-
|
|
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) {
|
|
2458
3009
|
const { stream } = ctx;
|
|
2459
|
-
if (!stream.nextTemplateStartToken()) return;
|
|
3010
|
+
if (!stream.nextTemplateStartToken()) return void 0;
|
|
3011
|
+
const names = [];
|
|
2460
3012
|
while (true) {
|
|
2461
3013
|
const next = stream.peek();
|
|
2462
3014
|
if (!next) throwParseError(stream, "Unclosed template in var declaration");
|
|
2463
3015
|
if (next.text.startsWith(">")) {
|
|
2464
3016
|
stream.nextTemplateEndToken();
|
|
2465
|
-
return;
|
|
3017
|
+
return names;
|
|
2466
3018
|
}
|
|
2467
|
-
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");
|
|
2468
3022
|
}
|
|
2469
3023
|
}
|
|
2470
3024
|
//#endregion
|
|
@@ -2486,18 +3040,20 @@ function parseVarOrLet(ctx, keyword, hasTemplate, requiresInit, attributes) {
|
|
|
2486
3040
|
const token = stream.matchText(keyword);
|
|
2487
3041
|
if (!token) return null;
|
|
2488
3042
|
const startPos = getStartWithAttributes(attributes, token.span[0]);
|
|
2489
|
-
|
|
2490
|
-
if (hasTemplate) skipTemplateList(ctx);
|
|
3043
|
+
const template = hasTemplate ? parseTemplateList(ctx) : void 0;
|
|
2491
3044
|
const typedDecl = parseTypedDecl(ctx, false);
|
|
2492
3045
|
if (!typedDecl) throwParseError(stream, `Expected identifier after '${keyword}'`);
|
|
2493
|
-
|
|
3046
|
+
let init;
|
|
2494
3047
|
if (requiresInit) {
|
|
2495
3048
|
expect(stream, "=", `${keyword} identifier (${keyword} requires initialization)`);
|
|
2496
|
-
expectExpression(ctx);
|
|
2497
|
-
} else if (stream.matchText("=")) expectExpression(ctx);
|
|
3049
|
+
init = expectExpression(ctx);
|
|
3050
|
+
} else if (stream.matchText("=")) init = expectExpression(ctx);
|
|
2498
3051
|
expect(stream, ";", `${keyword} declaration`);
|
|
2499
|
-
const elem =
|
|
2500
|
-
|
|
3052
|
+
const elem = finishStatement(keyword, startPos, ctx, {
|
|
3053
|
+
name: typedDecl,
|
|
3054
|
+
init
|
|
3055
|
+
}, attributes);
|
|
3056
|
+
if (template && elem.kind === "var") elem.template = template;
|
|
2501
3057
|
linkDeclIdent(typedDecl, elem);
|
|
2502
3058
|
return elem;
|
|
2503
3059
|
}
|
|
@@ -2526,17 +3082,42 @@ const assignmentOps = new Set([
|
|
|
2526
3082
|
function parseSimpleStatement(ctx, attributes) {
|
|
2527
3083
|
const { stream } = ctx;
|
|
2528
3084
|
const startPos = getStartWithAttributes(attributes, stream.checkpoint());
|
|
2529
|
-
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
|
+
};
|
|
2530
3113
|
}
|
|
2531
3114
|
/** Grammar: return_statement : 'return' expression? ';' */
|
|
2532
3115
|
function parseReturnStmt(ctx, startPos, attributes) {
|
|
2533
3116
|
const { stream } = ctx;
|
|
2534
3117
|
if (!stream.matchText("return")) return null;
|
|
2535
|
-
|
|
2536
|
-
const expr = parseExpression(ctx);
|
|
2537
|
-
if (expr && ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
3118
|
+
const value = parseContentExpression(ctx) ?? void 0;
|
|
2538
3119
|
expect(stream, ";", "return statement");
|
|
2539
|
-
return
|
|
3120
|
+
return finishStatement("return", startPos, ctx, { value }, attributes);
|
|
2540
3121
|
}
|
|
2541
3122
|
/**
|
|
2542
3123
|
* Grammar: break_statement : 'break' ';'
|
|
@@ -2545,70 +3126,79 @@ function parseReturnStmt(ctx, startPos, attributes) {
|
|
|
2545
3126
|
function parseBreakStmt(ctx, startPos, attributes) {
|
|
2546
3127
|
const { stream } = ctx;
|
|
2547
3128
|
if (!stream.matchText("break")) return null;
|
|
2548
|
-
|
|
2549
|
-
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'");
|
|
2550
3131
|
expect(stream, ";", "break statement");
|
|
2551
|
-
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);
|
|
2552
3140
|
}
|
|
2553
|
-
/** Grammar:
|
|
2554
|
-
function
|
|
3141
|
+
/** Grammar: 'discard' ';' */
|
|
3142
|
+
function parseDiscardStmt(ctx, startPos, attributes) {
|
|
2555
3143
|
const { stream } = ctx;
|
|
2556
|
-
if (!stream.matchText(
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
return finishBlockStatement(startPos, ctx, attributes);
|
|
3144
|
+
if (!stream.matchText("discard")) return null;
|
|
3145
|
+
expect(stream, ";", "discard statement");
|
|
3146
|
+
return finishStatement("discard", startPos, ctx, {}, attributes);
|
|
2560
3147
|
}
|
|
2561
|
-
/** Parse empty statement (just ';'). */
|
|
3148
|
+
/** Parse empty statement (just ';'). Spans the ';' so it emits no extra text. */
|
|
2562
3149
|
function parseEmptyStmt(stream, start) {
|
|
2563
3150
|
if (!stream.matchText(";")) return null;
|
|
2564
3151
|
return {
|
|
2565
|
-
kind: "
|
|
3152
|
+
kind: "empty",
|
|
2566
3153
|
start,
|
|
2567
|
-
end: stream.checkpoint()
|
|
2568
|
-
contents: []
|
|
3154
|
+
end: stream.checkpoint()
|
|
2569
3155
|
};
|
|
2570
3156
|
}
|
|
2571
3157
|
/** Grammar: assignment_statement : '_' '=' expression ';' (phony assignment) */
|
|
2572
3158
|
function parsePhonyAssignment(ctx, startPos, attributes) {
|
|
2573
3159
|
const { stream } = ctx;
|
|
2574
|
-
|
|
2575
|
-
if (!
|
|
2576
|
-
|
|
2577
|
-
|
|
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 '_ ='");
|
|
2578
3173
|
expect(stream, ";", "assignment");
|
|
2579
|
-
return
|
|
3174
|
+
return finishStatement("assign", startPos, ctx, {
|
|
3175
|
+
lhs,
|
|
3176
|
+
op,
|
|
3177
|
+
rhs
|
|
3178
|
+
}, attributes);
|
|
2580
3179
|
}
|
|
2581
|
-
/**
|
|
2582
|
-
* Parses expression statements: assignments, increments/decrements, or function calls.
|
|
2583
|
-
* Grammar: ( assignment_statement | increment_statement | decrement_statement | call_phrase ) ';'
|
|
2584
|
-
*/
|
|
3180
|
+
/** Grammar: ( assignment_statement | increment_statement | decrement_statement | call_phrase ) ';' */
|
|
2585
3181
|
function parseExpressionStmt(ctx, startPos, attributes) {
|
|
2586
3182
|
const { stream } = ctx;
|
|
2587
|
-
beginElem(ctx, "statement", attributes);
|
|
2588
3183
|
const expr = parseExpression(ctx);
|
|
2589
3184
|
if (!expr) {
|
|
2590
|
-
finishContents(ctx, startPos, startPos);
|
|
2591
3185
|
stream.reset(startPos);
|
|
2592
3186
|
return null;
|
|
2593
3187
|
}
|
|
2594
|
-
|
|
2595
|
-
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);
|
|
2596
3194
|
expect(stream, ";", "expression");
|
|
2597
|
-
return
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
function parseAssignmentRhs(ctx) {
|
|
2605
|
-
if (!parseAssignmentOperator(ctx.stream)) return false;
|
|
2606
|
-
expectExpression(ctx, "Expected expression after assignment operator");
|
|
2607
|
-
return true;
|
|
2608
|
-
}
|
|
2609
|
-
/** Grammar: increment_statement : lhs_expression '++' ; decrement_statement : lhs_expression '--' */
|
|
2610
|
-
function parseIncDecOperator(stream) {
|
|
2611
|
-
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);
|
|
2612
3202
|
}
|
|
2613
3203
|
//#endregion
|
|
2614
3204
|
//#region src/parse/ParseLoop.ts
|
|
@@ -2622,41 +3212,48 @@ function parseForStatement(ctx, attributes) {
|
|
|
2622
3212
|
if (startPos === null) return null;
|
|
2623
3213
|
ctx.pushScope();
|
|
2624
3214
|
expect(stream, "(", "'for'");
|
|
2625
|
-
parseForInit(ctx);
|
|
2626
|
-
const
|
|
2627
|
-
if (cond && ctx.options.preserveExpressions) ctx.addElem(cond);
|
|
3215
|
+
const init = parseForInit(ctx);
|
|
3216
|
+
const condition = parseContentExpression(ctx) ?? void 0;
|
|
2628
3217
|
expect(stream, ";", "for loop condition");
|
|
2629
|
-
parseForUpdate(ctx);
|
|
3218
|
+
const update = parseForUpdate(ctx);
|
|
2630
3219
|
expect(stream, ")", "for loop header");
|
|
2631
3220
|
const body = expectCompound(ctx, "Expected '{' after for loop header");
|
|
2632
|
-
ctx.addElem(body);
|
|
2633
3221
|
ctx.popScope();
|
|
2634
|
-
return
|
|
3222
|
+
return finishStatement("for", startPos, ctx, {
|
|
3223
|
+
init,
|
|
3224
|
+
condition,
|
|
3225
|
+
update,
|
|
3226
|
+
body
|
|
3227
|
+
}, attributes);
|
|
2635
3228
|
}
|
|
2636
3229
|
/** Grammar: while_statement : attribute* 'while' expression compound_statement */
|
|
2637
3230
|
function parseWhileStatement(ctx, attributes) {
|
|
2638
3231
|
const startPos = beginStatement(ctx, "while", attributes);
|
|
2639
3232
|
if (startPos === null) return null;
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
3233
|
+
return finishStatement("while", startPos, ctx, {
|
|
3234
|
+
condition: expectExpression(ctx, "Expected condition after 'while'"),
|
|
3235
|
+
body: expectCompound(ctx, "Expected '{' after while condition")
|
|
3236
|
+
}, attributes);
|
|
2644
3237
|
}
|
|
2645
3238
|
/** Grammar: loop_statement : attribute* 'loop' attribute* '{' statement* continuing_statement? '}' */
|
|
2646
3239
|
function parseLoopStatement(ctx, attributes) {
|
|
2647
3240
|
const startPos = beginStatement(ctx, "loop", attributes);
|
|
2648
3241
|
if (startPos === null) return null;
|
|
2649
3242
|
const body = expectCompound(ctx, "Expected '{' after 'loop'", true);
|
|
2650
|
-
ctx
|
|
2651
|
-
|
|
3243
|
+
return finishStatement("loop", startPos, ctx, {
|
|
3244
|
+
body,
|
|
3245
|
+
continuing: body.body.find((s) => s.kind === "continuing")
|
|
3246
|
+
}, attributes);
|
|
2652
3247
|
}
|
|
2653
3248
|
/** Grammar: continuing_statement : 'continuing' continuing_compound_statement */
|
|
2654
3249
|
function parseContinuingStatement(ctx, attributes) {
|
|
2655
|
-
const startPos = beginStatement(ctx, "continuing", attributes
|
|
3250
|
+
const startPos = beginStatement(ctx, "continuing", attributes);
|
|
2656
3251
|
if (startPos === null) return null;
|
|
2657
3252
|
const body = expectCompound(ctx, "Expected '{' after 'continuing'");
|
|
2658
|
-
ctx
|
|
2659
|
-
|
|
3253
|
+
return finishStatement("continuing", startPos, ctx, {
|
|
3254
|
+
body,
|
|
3255
|
+
breakIf: body.body.find((s) => s.kind === "break")?.condition
|
|
3256
|
+
}, attributes);
|
|
2660
3257
|
}
|
|
2661
3258
|
/** Grammar: for_init? ';'
|
|
2662
3259
|
* for_init : variable_or_value_statement | variable_updating_statement | func_call_statement
|
|
@@ -2664,19 +3261,60 @@ function parseContinuingStatement(ctx, attributes) {
|
|
|
2664
3261
|
function parseForInit(ctx) {
|
|
2665
3262
|
const { stream } = ctx;
|
|
2666
3263
|
const varDecl = parseLocalVarDecl(ctx);
|
|
2667
|
-
if (varDecl)
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
}
|
|
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;
|
|
2673
3269
|
}
|
|
2674
|
-
/** Grammar: for_update : variable_updating_statement | func_call_statement
|
|
2675
|
-
* variable_updating_statement : assignment_statement | increment_statement | decrement_statement */
|
|
3270
|
+
/** Grammar: for_update : variable_updating_statement | func_call_statement */
|
|
2676
3271
|
function parseForUpdate(ctx) {
|
|
2677
|
-
const expr =
|
|
2678
|
-
if (expr
|
|
2679
|
-
|
|
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");
|
|
2680
3318
|
}
|
|
2681
3319
|
//#endregion
|
|
2682
3320
|
//#region src/parse/ParseStatement.ts
|
|
@@ -2692,17 +3330,17 @@ function parseCompoundStatement(ctx, attributes, options) {
|
|
|
2692
3330
|
const brace = ctx.stream.matchText("{");
|
|
2693
3331
|
if (!brace) return null;
|
|
2694
3332
|
const startPos = getStartWithAttributes(attributes, brace.span[0]);
|
|
2695
|
-
beginElem(ctx, "statement", attributes);
|
|
2696
3333
|
const skipScope = options?.noScope || hasConditionalAttr(attributes);
|
|
2697
3334
|
if (!skipScope) ctx.pushScope();
|
|
2698
|
-
parseBlockStatements(ctx, options?.loopBody);
|
|
3335
|
+
const body = parseBlockStatements(ctx, options?.loopBody);
|
|
2699
3336
|
if (!skipScope) ctx.popScope();
|
|
2700
|
-
return
|
|
3337
|
+
return finishStatement("block", startPos, ctx, { body }, attributes);
|
|
2701
3338
|
}
|
|
2702
3339
|
/** Grammar: attribute* compound_statement (for control flow bodies) */
|
|
2703
3340
|
function expectCompound(ctx, errorMsg, loopBody) {
|
|
2704
3341
|
const attrs = parseAttributeList(ctx);
|
|
2705
|
-
const
|
|
3342
|
+
const options = loopBody ? { loopBody } : void 0;
|
|
3343
|
+
const block = parseCompoundStatement(ctx, attrsOrUndef(attrs), options);
|
|
2706
3344
|
if (!block) throwParseError(ctx.stream, errorMsg);
|
|
2707
3345
|
return block;
|
|
2708
3346
|
}
|
|
@@ -2710,16 +3348,20 @@ function expectCompound(ctx, errorMsg, loopBody) {
|
|
|
2710
3348
|
function getStartWithAttributes(attributes, keywordPos) {
|
|
2711
3349
|
return attributes?.[0]?.start ?? keywordPos;
|
|
2712
3350
|
}
|
|
2713
|
-
/** Match keyword and
|
|
2714
|
-
function beginStatement(ctx, keyword, attributes
|
|
2715
|
-
const
|
|
2716
|
-
if (!
|
|
2717
|
-
|
|
2718
|
-
beginElem(ctx, kind, attributes);
|
|
2719
|
-
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]);
|
|
2720
3356
|
}
|
|
2721
|
-
|
|
2722
|
-
|
|
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
|
+
};
|
|
2723
3365
|
attachAttributes(elem, attributes);
|
|
2724
3366
|
return elem;
|
|
2725
3367
|
}
|
|
@@ -2729,16 +3371,18 @@ function hasConditionalAttr(attributes) {
|
|
|
2729
3371
|
/** Grammar: statement* '}' (after '{' consumed). Loop bodies may end with continuing. */
|
|
2730
3372
|
function parseBlockStatements(ctx, loopBody) {
|
|
2731
3373
|
const { stream } = ctx;
|
|
3374
|
+
const body = [];
|
|
2732
3375
|
while (true) {
|
|
2733
3376
|
if (stream.matchText("}")) break;
|
|
2734
3377
|
const stmt = parseStatement(ctx);
|
|
2735
3378
|
if (!stmt) throwParseError(stream, "Expected statement or '}'");
|
|
2736
|
-
|
|
3379
|
+
body.push(stmt);
|
|
2737
3380
|
if (loopBody && stmt.kind === "continuing") {
|
|
2738
3381
|
expect(stream, "}", "continuing block");
|
|
2739
3382
|
break;
|
|
2740
3383
|
}
|
|
2741
3384
|
}
|
|
3385
|
+
return body;
|
|
2742
3386
|
}
|
|
2743
3387
|
/**
|
|
2744
3388
|
* Grammar: statement :
|
|
@@ -2759,7 +3403,6 @@ function parseStatement(ctx) {
|
|
|
2759
3403
|
}
|
|
2760
3404
|
const hasConditional = attributes.length > 0 && hasConditionalAttribute(attributes);
|
|
2761
3405
|
if (hasConditional) ctx.pushScope("partial");
|
|
2762
|
-
const attrsOrUndef = attributes.length > 0 ? attributes : void 0;
|
|
2763
3406
|
const stmt = findMap([
|
|
2764
3407
|
parseLocalVarDecl,
|
|
2765
3408
|
parseLetDecl,
|
|
@@ -2773,19 +3416,71 @@ function parseStatement(ctx) {
|
|
|
2773
3416
|
parseLoopStatement,
|
|
2774
3417
|
parseContinuingStatement,
|
|
2775
3418
|
parseSimpleStatement
|
|
2776
|
-
], (p) => p(ctx, attrsOrUndef));
|
|
2777
|
-
if (
|
|
2778
|
-
|
|
2779
|
-
|
|
3419
|
+
], (p) => p(ctx, attrsOrUndef(attributes)));
|
|
3420
|
+
if (hasConditional) {
|
|
3421
|
+
const partialScope = ctx.popScope();
|
|
3422
|
+
if (stmt) partialScope.condAttribute = conditionalAttribute(attributes);
|
|
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);
|
|
2780
3471
|
}
|
|
2781
|
-
function
|
|
2782
|
-
|
|
2783
|
-
const partialScope = ctx.popScope();
|
|
2784
|
-
partialScope.condAttribute = getConditionalAttribute(attributes);
|
|
2785
|
-
}
|
|
3472
|
+
function parseDirectiveName(ctx) {
|
|
3473
|
+
return makeNameElem(expectWord(ctx.stream, "Expected identifier in name list"));
|
|
2786
3474
|
}
|
|
2787
|
-
function
|
|
2788
|
-
|
|
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;
|
|
2789
3484
|
}
|
|
2790
3485
|
//#endregion
|
|
2791
3486
|
//#region src/parse/ParseFn.ts
|
|
@@ -2822,8 +3517,7 @@ function parseFnDecl(ctx, attributes) {
|
|
|
2822
3517
|
returnType,
|
|
2823
3518
|
returnAttributes,
|
|
2824
3519
|
start: startPos,
|
|
2825
|
-
end: stream.checkpoint()
|
|
2826
|
-
contents: buildFnContents(attributes, declIdentElem, params, returnType, body)
|
|
3520
|
+
end: stream.checkpoint()
|
|
2827
3521
|
};
|
|
2828
3522
|
attachAttributes(fnElem, attributes);
|
|
2829
3523
|
linkDeclIdentElem(declIdentElem, fnElem);
|
|
@@ -2853,37 +3547,63 @@ function parseFnReturn(ctx) {
|
|
|
2853
3547
|
if (!returnType) throwParseError(stream, "Expected type after '->'");
|
|
2854
3548
|
return {
|
|
2855
3549
|
returnType,
|
|
2856
|
-
returnAttributes: attrs
|
|
3550
|
+
returnAttributes: attrsOrUndef(attrs)
|
|
2857
3551
|
};
|
|
2858
3552
|
}
|
|
2859
|
-
/** Build contents array for function element */
|
|
2860
|
-
function buildFnContents(attributes, decl, params, returnType, body) {
|
|
2861
|
-
const base = returnType ? [
|
|
2862
|
-
decl,
|
|
2863
|
-
...params,
|
|
2864
|
-
returnType,
|
|
2865
|
-
body
|
|
2866
|
-
] : [
|
|
2867
|
-
decl,
|
|
2868
|
-
...params,
|
|
2869
|
-
body
|
|
2870
|
-
];
|
|
2871
|
-
return attributes?.length ? [...attributes, ...base] : base;
|
|
2872
|
-
}
|
|
2873
3553
|
/** Grammar: param : attribute* optionally_typed_ident */
|
|
2874
3554
|
function parseFnParam(ctx) {
|
|
2875
|
-
const
|
|
3555
|
+
const attrs = parseAttributeList(ctx);
|
|
2876
3556
|
if (ctx.stream.peek()?.kind !== "word") return null;
|
|
2877
|
-
|
|
3557
|
+
const attributes = attrsOrUndef(attrs);
|
|
2878
3558
|
const name = parseTypedDecl(ctx, false);
|
|
2879
3559
|
if (!name) throw new Error("Unexpected: peek succeeded but parseTypedDecl failed");
|
|
2880
|
-
|
|
2881
|
-
const elem = finishElem("param", getStartWithAttributes(attributes, name.start), ctx, { name });
|
|
3560
|
+
const elem = finishStatement("param", getStartWithAttributes(attributes, name.start), ctx, { name }, attributes);
|
|
2882
3561
|
linkDeclIdent(name, elem);
|
|
2883
|
-
attachAttributes(elem, attributes.length > 0 ? attributes : void 0);
|
|
2884
3562
|
return elem;
|
|
2885
3563
|
}
|
|
2886
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
|
|
2887
3607
|
//#region src/parse/ParseImport.ts
|
|
2888
3608
|
/** WESL Grammar: translation_unit : import_statement* global_directive* global_decl* */
|
|
2889
3609
|
function parseWeslImports(ctx) {
|
|
@@ -3021,52 +3741,39 @@ function parseStructDecl(ctx, attributes) {
|
|
|
3021
3741
|
const start = getStartWithAttributes(attributes, structToken.span[0]);
|
|
3022
3742
|
const identElem = createDeclIdentElem(ctx, expectWord(stream, "Expected identifier after 'struct'"), true);
|
|
3023
3743
|
ctx.saveIdent(identElem.ident);
|
|
3024
|
-
beginElem(ctx, "struct", attributes);
|
|
3025
|
-
ctx.addElem(identElem);
|
|
3026
3744
|
expect(stream, "{", "struct name");
|
|
3027
3745
|
ctx.pushScope();
|
|
3028
|
-
const members =
|
|
3746
|
+
const members = parseCommaList(ctx, parseStructMember);
|
|
3029
3747
|
identElem.ident.dependentScope = ctx.currentScope();
|
|
3030
3748
|
ctx.popScope();
|
|
3031
3749
|
expect(stream, "}", "struct member");
|
|
3032
|
-
const elem =
|
|
3750
|
+
const elem = finishStatement("struct", start, ctx, {
|
|
3033
3751
|
name: identElem,
|
|
3034
3752
|
members
|
|
3035
|
-
});
|
|
3036
|
-
attachAttributes(elem, attributes);
|
|
3753
|
+
}, attributes);
|
|
3037
3754
|
linkDeclIdentElem(identElem, elem);
|
|
3038
3755
|
return elem;
|
|
3039
3756
|
}
|
|
3040
|
-
/** Grammar: struct_body_decl : '{' struct_member (',' struct_member)* ','? '}' */
|
|
3041
|
-
function parseStructMembers(ctx) {
|
|
3042
|
-
const members = parseCommaList(ctx, parseStructMember);
|
|
3043
|
-
for (const member of members) ctx.addElem(member);
|
|
3044
|
-
return members;
|
|
3045
|
-
}
|
|
3046
3757
|
/** Grammar: struct_member : attribute* member_ident ':' type_specifier */
|
|
3047
3758
|
function parseStructMember(ctx) {
|
|
3048
3759
|
const { stream } = ctx;
|
|
3049
3760
|
const checkpoint = stream.checkpoint();
|
|
3050
|
-
const
|
|
3761
|
+
const attrs = parseAttributeList(ctx);
|
|
3051
3762
|
const nameToken = stream.matchKind("word");
|
|
3052
3763
|
if (!nameToken) {
|
|
3053
3764
|
stream.reset(checkpoint);
|
|
3054
3765
|
return null;
|
|
3055
3766
|
}
|
|
3767
|
+
const attributes = attrsOrUndef(attrs);
|
|
3056
3768
|
const start = getStartWithAttributes(attributes, nameToken.span[0]);
|
|
3057
|
-
beginElem(ctx, "member", attributes.length ? attributes : void 0);
|
|
3058
3769
|
const name = makeNameElem(nameToken);
|
|
3059
|
-
ctx.addElem(name);
|
|
3060
3770
|
expect(stream, ":", "struct member name");
|
|
3061
3771
|
const typeRef = parseSimpleTypeRef(ctx);
|
|
3062
3772
|
if (!typeRef) throwParseError(stream, "Expected type after ':'");
|
|
3063
|
-
ctx
|
|
3064
|
-
const elem = finishElem("member", start, ctx, {
|
|
3773
|
+
return finishStatement("member", start, ctx, {
|
|
3065
3774
|
name,
|
|
3066
3775
|
typeRef
|
|
3067
|
-
});
|
|
3068
|
-
attachAttributes(elem, attributes.length ? attributes : void 0);
|
|
3069
|
-
return elem;
|
|
3776
|
+
}, attributes);
|
|
3070
3777
|
}
|
|
3071
3778
|
//#endregion
|
|
3072
3779
|
//#region src/parse/ParseModule.ts
|
|
@@ -3077,6 +3784,7 @@ const declParsers = [
|
|
|
3077
3784
|
parseAliasDecl,
|
|
3078
3785
|
parseStructDecl,
|
|
3079
3786
|
parseFnDecl,
|
|
3787
|
+
parseDoBlock,
|
|
3080
3788
|
parseConstAssert
|
|
3081
3789
|
];
|
|
3082
3790
|
/** Grammar: translation_unit : global_directive* ( global_decl | global_assert | ';' )* */
|
|
@@ -3084,19 +3792,39 @@ function parseModule(ctx) {
|
|
|
3084
3792
|
parseImports(ctx);
|
|
3085
3793
|
parseDirectives(ctx);
|
|
3086
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
|
+
}
|
|
3087
3815
|
}
|
|
3088
3816
|
/** Parse WESL import statements at the start of the module. */
|
|
3089
3817
|
function parseImports(ctx) {
|
|
3090
3818
|
const importElems = parseWeslImports(ctx);
|
|
3091
3819
|
for (const importElem of importElems) {
|
|
3092
|
-
ctx.
|
|
3820
|
+
ctx.addModuleDecl(importElem);
|
|
3093
3821
|
ctx.state.stable.imports.push(importElem.imports);
|
|
3094
3822
|
}
|
|
3095
3823
|
}
|
|
3096
3824
|
/** Grammar: global_directive : diagnostic_directive | enable_directive | requires_directive */
|
|
3097
3825
|
function parseDirectives(ctx) {
|
|
3098
3826
|
const directives = parseMany(ctx, parseDirective);
|
|
3099
|
-
for (const elem of directives) ctx.
|
|
3827
|
+
for (const elem of directives) ctx.addModuleDecl(elem);
|
|
3100
3828
|
}
|
|
3101
3829
|
/** Parse one declaration, return true if more may exist. */
|
|
3102
3830
|
function parseNextDeclaration(ctx) {
|
|
@@ -3111,34 +3839,40 @@ function parseNextDeclaration(ctx) {
|
|
|
3111
3839
|
if (attrs.length) throwParseError(stream, "Expected declaration after attributes");
|
|
3112
3840
|
return false;
|
|
3113
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
|
+
}
|
|
3114
3854
|
/** Try each declaration parser until one succeeds. */
|
|
3115
3855
|
function parseDecl(ctx, attrs) {
|
|
3116
|
-
const
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
return true;
|
|
3121
|
-
}
|
|
3122
|
-
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;
|
|
3123
3860
|
}
|
|
3124
3861
|
/** Pop conditional scope and attach the conditional attribute. */
|
|
3125
3862
|
function finalizeConditional(ctx, attrs) {
|
|
3126
3863
|
const partialScope = ctx.popScope();
|
|
3127
|
-
partialScope.condAttribute =
|
|
3864
|
+
partialScope.condAttribute = conditionalAttribute(attrs);
|
|
3128
3865
|
}
|
|
3129
3866
|
/** Record a parsed declaration, extending start to include attributes. */
|
|
3130
3867
|
function recordDecl(ctx, elem, attrs) {
|
|
3131
3868
|
if (attrs.length && elem.start > attrs[0].start) elem.start = attrs[0].start;
|
|
3132
|
-
ctx.
|
|
3869
|
+
ctx.addModuleDecl(elem);
|
|
3133
3870
|
if (elem.kind === "assert") {
|
|
3134
3871
|
const { stable } = ctx.state;
|
|
3135
3872
|
stable.moduleAsserts ??= [];
|
|
3136
3873
|
stable.moduleAsserts.push(elem);
|
|
3137
3874
|
}
|
|
3138
3875
|
}
|
|
3139
|
-
function isConditionalAttribute(a) {
|
|
3140
|
-
return a.kind === "@if" || a.kind === "@elif" || a.kind === "@else";
|
|
3141
|
-
}
|
|
3142
3876
|
//#endregion
|
|
3143
3877
|
//#region src/parse/ParsingContext.ts
|
|
3144
3878
|
/** Context for parsers to build AST and manage scopes. */
|
|
@@ -3161,9 +3895,9 @@ var ParsingContext = class {
|
|
|
3161
3895
|
currentScope() {
|
|
3162
3896
|
return this.state.context.scope;
|
|
3163
3897
|
}
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3898
|
+
/** Append a top-level declaration to the module, in source order. */
|
|
3899
|
+
addModuleDecl(elem) {
|
|
3900
|
+
this.state.stable.moduleElem.decls.push(elem);
|
|
3167
3901
|
}
|
|
3168
3902
|
pushScope(kind = "scope") {
|
|
3169
3903
|
const { scope } = this.state.context;
|
|
@@ -3231,55 +3965,6 @@ const reservedWords = `NULL Self abstract active alignas alignof as asm asm_frag
|
|
|
3231
3965
|
target template this thread_local throw trait try type typedef typeid typename typeof
|
|
3232
3966
|
union unless unorm unsafe unsized use using varying virtual volatile wgsl where with writeonly yield`.split(/\s+/);
|
|
3233
3967
|
//#endregion
|
|
3234
|
-
//#region src/parse/stream/CachingStream.ts
|
|
3235
|
-
var CachingStream = class {
|
|
3236
|
-
cache = new Cache(5);
|
|
3237
|
-
inner;
|
|
3238
|
-
constructor(inner) {
|
|
3239
|
-
this.inner = inner;
|
|
3240
|
-
}
|
|
3241
|
-
checkpoint() {
|
|
3242
|
-
return this.inner.checkpoint();
|
|
3243
|
-
}
|
|
3244
|
-
reset(position) {
|
|
3245
|
-
this.inner.reset(position);
|
|
3246
|
-
}
|
|
3247
|
-
nextToken() {
|
|
3248
|
-
const startPos = this.checkpoint();
|
|
3249
|
-
const cachedValue = this.cache.get(startPos);
|
|
3250
|
-
if (cachedValue !== void 0) {
|
|
3251
|
-
this.reset(cachedValue.checkpoint);
|
|
3252
|
-
return cachedValue.token;
|
|
3253
|
-
} else {
|
|
3254
|
-
const token = this.inner.nextToken();
|
|
3255
|
-
const checkpoint = this.checkpoint();
|
|
3256
|
-
this.cache.set(startPos, {
|
|
3257
|
-
token,
|
|
3258
|
-
checkpoint
|
|
3259
|
-
});
|
|
3260
|
-
return token;
|
|
3261
|
-
}
|
|
3262
|
-
}
|
|
3263
|
-
get src() {
|
|
3264
|
-
return this.inner.src;
|
|
3265
|
-
}
|
|
3266
|
-
};
|
|
3267
|
-
/** size limited key value cache */
|
|
3268
|
-
var Cache = class extends Map {
|
|
3269
|
-
max;
|
|
3270
|
-
constructor(max) {
|
|
3271
|
-
super();
|
|
3272
|
-
this.max = max;
|
|
3273
|
-
}
|
|
3274
|
-
set(k, v) {
|
|
3275
|
-
if (this.size > this.max) {
|
|
3276
|
-
const first = this.keys().next().value;
|
|
3277
|
-
if (first) this.delete(first);
|
|
3278
|
-
}
|
|
3279
|
-
return super.set(k, v);
|
|
3280
|
-
}
|
|
3281
|
-
};
|
|
3282
|
-
//#endregion
|
|
3283
3968
|
//#region src/parse/stream/RegexHelpers.ts
|
|
3284
3969
|
function toRegexSource(nameExp) {
|
|
3285
3970
|
const [name, e] = nameExp;
|
|
@@ -3309,33 +3994,10 @@ function matchOneOf(syms) {
|
|
|
3309
3994
|
return new RegExp(escaped.join("|"));
|
|
3310
3995
|
}
|
|
3311
3996
|
//#endregion
|
|
3312
|
-
//#region src/parse/stream/
|
|
3313
|
-
/** Runs a `RegexMatchers` on an input string */
|
|
3314
|
-
var MatchersStream = class {
|
|
3315
|
-
position = 0;
|
|
3316
|
-
text;
|
|
3317
|
-
matchers;
|
|
3318
|
-
constructor(text, matchers) {
|
|
3319
|
-
this.text = text;
|
|
3320
|
-
this.matchers = matchers;
|
|
3321
|
-
}
|
|
3322
|
-
checkpoint() {
|
|
3323
|
-
return this.position;
|
|
3324
|
-
}
|
|
3325
|
-
reset(position) {
|
|
3326
|
-
this.position = position;
|
|
3327
|
-
}
|
|
3328
|
-
nextToken() {
|
|
3329
|
-
const result = this.matchers.execAt(this.text, this.position);
|
|
3330
|
-
if (result === null) return null;
|
|
3331
|
-
this.position = result.span[1];
|
|
3332
|
-
return result;
|
|
3333
|
-
}
|
|
3334
|
-
get src() {
|
|
3335
|
-
return this.text;
|
|
3336
|
-
}
|
|
3337
|
-
};
|
|
3997
|
+
//#region src/parse/stream/RegexMatchers.ts
|
|
3338
3998
|
/**
|
|
3999
|
+
* Matches tokens by kind with one combined regex.
|
|
4000
|
+
*
|
|
3339
4001
|
* The matchers passed to this object must follow certain rules:
|
|
3340
4002
|
* - They must use non-capturing groups: `(?:...)`
|
|
3341
4003
|
* - They must NOT use `^` or `$`
|
|
@@ -3346,55 +4008,230 @@ var RegexMatchers = class {
|
|
|
3346
4008
|
constructor(matchers) {
|
|
3347
4009
|
this.groups = Object.keys(matchers);
|
|
3348
4010
|
const expParts = Object.entries(matchers).map(toRegexSource).join("|");
|
|
3349
|
-
this.exp = new RegExp(expParts, "
|
|
4011
|
+
this.exp = new RegExp(expParts, "yu");
|
|
3350
4012
|
}
|
|
3351
4013
|
execAt(text, position) {
|
|
3352
4014
|
this.exp.lastIndex = position;
|
|
3353
|
-
const
|
|
3354
|
-
if (
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
text: text.slice(span[0], span[1])
|
|
3360
|
-
};
|
|
3361
|
-
} else return null;
|
|
3362
|
-
}
|
|
3363
|
-
};
|
|
3364
|
-
function findGroupDex(indices) {
|
|
3365
|
-
if (indices !== void 0) for (let i = 1; i < indices.length; i++) {
|
|
3366
|
-
const span = indices[i];
|
|
3367
|
-
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)],
|
|
3368
4021
|
span,
|
|
3369
|
-
|
|
4022
|
+
text: matched
|
|
3370
4023
|
};
|
|
3371
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");
|
|
3372
4030
|
}
|
|
3373
4031
|
//#endregion
|
|
3374
|
-
//#region src/parse/
|
|
4032
|
+
//#region src/parse/stream/WeslLexer.ts
|
|
4033
|
+
const ident = /(?:(?:[_\p{XID_Start}][\p{XID_Continue}]+)|(?:[\p{XID_Start}]))/u;
|
|
3375
4034
|
/** Whitespaces including new lines */
|
|
3376
4035
|
const blankspaces = /[ \t\n\v\f\r\u{0085}\u{200E}\u{200F}\u{2028}\u{2029}]+/u;
|
|
3377
|
-
const
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
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({
|
|
3381
4040
|
word: ident,
|
|
3382
|
-
number:
|
|
4041
|
+
number: digits,
|
|
3383
4042
|
blankspaces,
|
|
3384
4043
|
commentStart: /\/\/|\/\*/,
|
|
3385
|
-
symbol: matchOneOf(
|
|
4044
|
+
symbol: matchOneOf("& && -> @ / ! [ ] { } :: : , == = != >>= >> >= > <<= << <= < % - -- . + ++ | || ( ) ; * ~ ^ // /* */ += -= *= /= %= &= |= ^= _"),
|
|
3386
4045
|
invalid: /[^]/
|
|
3387
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));
|
|
3388
4220
|
/** A stream that produces WESL tokens, skipping over comments and white space */
|
|
3389
4221
|
var WeslStream = class {
|
|
3390
4222
|
stream;
|
|
3391
|
-
/** New line */
|
|
3392
|
-
eolPattern =
|
|
4223
|
+
/** New line (stateful: scanned via lastIndex, so kept per-instance). */
|
|
4224
|
+
eolPattern = new RegExp(lineBreak, "gu");
|
|
3393
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;
|
|
3394
4231
|
src;
|
|
3395
4232
|
constructor(src) {
|
|
3396
4233
|
this.src = src;
|
|
3397
|
-
this.stream = new
|
|
4234
|
+
this.stream = new WeslLexer(src);
|
|
3398
4235
|
}
|
|
3399
4236
|
checkpoint() {
|
|
3400
4237
|
return this.stream.checkpoint();
|
|
@@ -3402,27 +4239,79 @@ var WeslStream = class {
|
|
|
3402
4239
|
reset(position) {
|
|
3403
4240
|
this.stream.reset(position);
|
|
3404
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. */
|
|
3405
4251
|
nextToken() {
|
|
4252
|
+
const peeked = this.usePeeked();
|
|
4253
|
+
if (peeked !== null) return peeked.token;
|
|
4254
|
+
let pending;
|
|
3406
4255
|
while (true) {
|
|
3407
4256
|
const token = this.stream.nextToken();
|
|
3408
|
-
if (token === null)
|
|
4257
|
+
if (token === null) {
|
|
4258
|
+
this.recordTrivia(this.src.length, pending);
|
|
4259
|
+
return null;
|
|
4260
|
+
}
|
|
3409
4261
|
const kind = token.kind;
|
|
3410
4262
|
if (kind === "blankspaces") continue;
|
|
3411
|
-
else if (kind === "commentStart")
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
const returnToken = token;
|
|
3415
|
-
if (keywordOrReserved.has(token.text)) returnToken.kind = "keyword";
|
|
3416
|
-
return returnToken;
|
|
4263
|
+
else if (kind === "commentStart") {
|
|
4264
|
+
pending ??= [];
|
|
4265
|
+
pending.push(this.consumeComment(token));
|
|
3417
4266
|
} else if (kind === "invalid") throw new ParseError("Invalid token " + token.text, token.span);
|
|
3418
|
-
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;
|
|
3419
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]);
|
|
4294
|
+
}
|
|
4295
|
+
this.stream.reset(end);
|
|
4296
|
+
return {
|
|
4297
|
+
style,
|
|
4298
|
+
start,
|
|
4299
|
+
end
|
|
4300
|
+
};
|
|
3420
4301
|
}
|
|
3421
4302
|
/** Peek at the next token without consuming it */
|
|
3422
4303
|
peek() {
|
|
3423
4304
|
const pos = this.checkpoint();
|
|
4305
|
+
const peeked = this.peeked;
|
|
4306
|
+
if (peeked !== null && peeked.pos === pos) return peeked.token;
|
|
3424
4307
|
const token = this.nextToken();
|
|
4308
|
+
const end = this.checkpoint();
|
|
3425
4309
|
this.reset(pos);
|
|
4310
|
+
this.peeked = {
|
|
4311
|
+
pos,
|
|
4312
|
+
token,
|
|
4313
|
+
end
|
|
4314
|
+
};
|
|
3426
4315
|
return token;
|
|
3427
4316
|
}
|
|
3428
4317
|
/** Consume token if text matches, otherwise leave position unchanged */
|
|
@@ -3466,10 +4355,15 @@ var WeslStream = class {
|
|
|
3466
4355
|
}
|
|
3467
4356
|
return tokens;
|
|
3468
4357
|
}
|
|
3469
|
-
|
|
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) {
|
|
3470
4364
|
this.eolPattern.lastIndex = position;
|
|
3471
|
-
|
|
3472
|
-
|
|
4365
|
+
const result = this.eolPattern.exec(this.src);
|
|
4366
|
+
return result === null ? this.src.length : result.index;
|
|
3473
4367
|
}
|
|
3474
4368
|
skipBlockComment(start) {
|
|
3475
4369
|
let position = start;
|
|
@@ -3491,39 +4385,46 @@ var WeslStream = class {
|
|
|
3491
4385
|
const startPosition = this.stream.checkpoint();
|
|
3492
4386
|
const token = this.nextToken();
|
|
3493
4387
|
this.stream.reset(startPosition);
|
|
3494
|
-
if (token === null) return null;
|
|
3495
|
-
if (token.
|
|
3496
|
-
if (token.text === "<") if (this.isTemplateStart(token.span[1])) {
|
|
3497
|
-
this.stream.reset(token.span[1]);
|
|
3498
|
-
return token;
|
|
3499
|
-
} else {
|
|
4388
|
+
if (token === null || token.kind !== "symbol" || token.text !== "<") return null;
|
|
4389
|
+
if (!this.isTemplateStart(token.span[1])) {
|
|
3500
4390
|
this.stream.reset(startPosition);
|
|
3501
4391
|
return null;
|
|
3502
4392
|
}
|
|
3503
|
-
|
|
4393
|
+
this.stream.reset(token.span[1]);
|
|
4394
|
+
return token;
|
|
3504
4395
|
}
|
|
4396
|
+
/** Match a template-closing `>`, splitting it off a `>>`/`>=`/`>>=` token when needed. */
|
|
3505
4397
|
nextTemplateEndToken() {
|
|
3506
4398
|
const startPosition = this.stream.checkpoint();
|
|
3507
4399
|
const token = this.nextToken();
|
|
3508
4400
|
this.stream.reset(startPosition);
|
|
3509
4401
|
if (token === null) return null;
|
|
3510
|
-
if (token.kind
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
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
|
+
};
|
|
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
|
+
}
|
|
3519
4420
|
}
|
|
4421
|
+
/** Template-list discovery: scan forward from `<` for a balanced closing `>`. */
|
|
3520
4422
|
isTemplateStart(afterToken) {
|
|
3521
4423
|
this.stream.reset(afterToken);
|
|
3522
4424
|
let pendingCounter = 1;
|
|
3523
4425
|
while (true) {
|
|
3524
|
-
const nextToken = this.
|
|
4426
|
+
const nextToken = this.nextRawSymbol();
|
|
3525
4427
|
if (nextToken === null) return false;
|
|
3526
|
-
if (nextToken.kind !== "symbol") continue;
|
|
3527
4428
|
if (nextToken.text === "<") pendingCounter += 1;
|
|
3528
4429
|
else if (nextToken.text[0] === ">") {
|
|
3529
4430
|
if (nextToken.text === ">" || nextToken.text === ">=") pendingCounter -= 1;
|
|
@@ -3541,12 +4442,11 @@ var WeslStream = class {
|
|
|
3541
4442
|
*/
|
|
3542
4443
|
skipBracketsTo(closingBracket) {
|
|
3543
4444
|
while (true) {
|
|
3544
|
-
const nextToken = this.
|
|
4445
|
+
const nextToken = this.nextRawSymbol();
|
|
3545
4446
|
if (nextToken === null) {
|
|
3546
4447
|
const after = this.stream.checkpoint();
|
|
3547
4448
|
throw new ParseError("Unclosed bracket!", [after, after]);
|
|
3548
4449
|
}
|
|
3549
|
-
if (nextToken.kind !== "symbol") continue;
|
|
3550
4450
|
if (nextToken.text === "(") this.skipBracketsTo(")");
|
|
3551
4451
|
else if (nextToken.text === "[") this.skipBracketsTo("]");
|
|
3552
4452
|
else if (nextToken.text === closingBracket) return;
|
|
@@ -3559,10 +4459,10 @@ var WeslStream = class {
|
|
|
3559
4459
|
function parseWesl(srcModule, options) {
|
|
3560
4460
|
const { ctx, state } = createParseState(srcModule, options);
|
|
3561
4461
|
try {
|
|
3562
|
-
beginElem(ctx, "module");
|
|
3563
4462
|
parseModule(ctx);
|
|
3564
|
-
const moduleElem = state.stable
|
|
3565
|
-
|
|
4463
|
+
const { moduleElem } = state.stable;
|
|
4464
|
+
attachComments(ctx, moduleElem);
|
|
4465
|
+
checkDoBlockNames(moduleElem);
|
|
3566
4466
|
return state.stable;
|
|
3567
4467
|
} catch (e) {
|
|
3568
4468
|
if (e instanceof ParseError) throw new WeslParseError({
|
|
@@ -3576,29 +4476,27 @@ function parseWesl(srcModule, options) {
|
|
|
3576
4476
|
}
|
|
3577
4477
|
}
|
|
3578
4478
|
/** Initialize parse state: token stream, root scope, and module element. */
|
|
3579
|
-
function createParseState(srcModule,
|
|
4479
|
+
function createParseState(srcModule, parseOptions) {
|
|
3580
4480
|
const stream = new WeslStream(srcModule.src);
|
|
3581
4481
|
const rootScope = emptyScope(null);
|
|
3582
4482
|
const moduleElem = {
|
|
3583
4483
|
kind: "module",
|
|
3584
|
-
|
|
4484
|
+
decls: [],
|
|
3585
4485
|
start: 0,
|
|
3586
4486
|
end: srcModule.src.length
|
|
3587
4487
|
};
|
|
3588
4488
|
const state = {
|
|
3589
|
-
context: {
|
|
3590
|
-
scope: rootScope,
|
|
3591
|
-
openElems: []
|
|
3592
|
-
},
|
|
4489
|
+
context: { scope: rootScope },
|
|
3593
4490
|
stable: {
|
|
3594
4491
|
srcModule,
|
|
3595
4492
|
moduleElem,
|
|
3596
4493
|
rootScope,
|
|
3597
|
-
imports: []
|
|
4494
|
+
imports: [],
|
|
4495
|
+
parseOptions
|
|
3598
4496
|
}
|
|
3599
4497
|
};
|
|
3600
4498
|
return {
|
|
3601
|
-
ctx: new ParsingContext(stream, state,
|
|
4499
|
+
ctx: new ParsingContext(stream, state, parseOptions),
|
|
3602
4500
|
state
|
|
3603
4501
|
};
|
|
3604
4502
|
}
|
|
@@ -3609,14 +4507,14 @@ var WeslParseError = class extends Error {
|
|
|
3609
4507
|
span;
|
|
3610
4508
|
src;
|
|
3611
4509
|
constructor(opts) {
|
|
3612
|
-
const
|
|
3613
|
-
const
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
message
|
|
3617
|
-
super(message, { cause
|
|
3618
|
-
this.span =
|
|
3619
|
-
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;
|
|
3620
4518
|
}
|
|
3621
4519
|
};
|
|
3622
4520
|
/** Parse a WESL file. */
|
|
@@ -3626,8 +4524,8 @@ function parseSrcModule(srcModule, options) {
|
|
|
3626
4524
|
/** @return flattened form of import tree for binding idents. */
|
|
3627
4525
|
function flatImports(ast, conditions) {
|
|
3628
4526
|
if (ast._flatImports && !conditions) return ast._flatImports;
|
|
3629
|
-
const importElems = ast.moduleElem
|
|
3630
|
-
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));
|
|
3631
4529
|
if (!conditions) ast._flatImports = flat;
|
|
3632
4530
|
return flat;
|
|
3633
4531
|
}
|
|
@@ -3924,11 +4822,13 @@ var RecordResolver = class {
|
|
|
3924
4822
|
sources;
|
|
3925
4823
|
packageName;
|
|
3926
4824
|
debugWeslRoot;
|
|
4825
|
+
weslExtensions;
|
|
3927
4826
|
constructor(sources, options = {}) {
|
|
3928
|
-
const { packageName = "package", debugWeslRoot } = options;
|
|
4827
|
+
const { packageName = "package", debugWeslRoot, weslExtensions } = options;
|
|
3929
4828
|
this.sources = sources;
|
|
3930
4829
|
this.packageName = packageName;
|
|
3931
4830
|
this.debugWeslRoot = normalizeDebugRoot(debugWeslRoot);
|
|
4831
|
+
this.weslExtensions = weslExtensions;
|
|
3932
4832
|
}
|
|
3933
4833
|
resolveModule(modulePath) {
|
|
3934
4834
|
const cached = this.astCache.get(modulePath);
|
|
@@ -3939,7 +4839,7 @@ var RecordResolver = class {
|
|
|
3939
4839
|
modulePath,
|
|
3940
4840
|
debugFilePath: this.modulePathToDebugPath(modulePath),
|
|
3941
4841
|
src: source
|
|
3942
|
-
});
|
|
4842
|
+
}, { weslExtensions: this.weslExtensions });
|
|
3943
4843
|
this.astCache.set(modulePath, ast);
|
|
3944
4844
|
return ast;
|
|
3945
4845
|
}
|
|
@@ -4035,7 +4935,7 @@ function freshResolver(inner) {
|
|
|
4035
4935
|
if (cached) return cached;
|
|
4036
4936
|
const ast = inner.resolveModule(modulePath);
|
|
4037
4937
|
if (!ast) return void 0;
|
|
4038
|
-
const fresh = parseSrcModule(ast.srcModule);
|
|
4938
|
+
const fresh = parseSrcModule(ast.srcModule, ast.parseOptions);
|
|
4039
4939
|
cache.set(modulePath, fresh);
|
|
4040
4940
|
return fresh;
|
|
4041
4941
|
} };
|
|
@@ -4359,12 +5259,13 @@ var SrcMap = class SrcMap {
|
|
|
4359
5259
|
const { src, position: srcStart } = this.destToSrc(e.srcStart);
|
|
4360
5260
|
const { src: endSrc, position: srcEnd } = this.destToSrc(e.srcEnd);
|
|
4361
5261
|
if (endSrc !== src) throw new Error("NYI, need to split");
|
|
5262
|
+
const { destStart, destEnd } = e;
|
|
4362
5263
|
return {
|
|
4363
5264
|
src,
|
|
4364
5265
|
srcStart,
|
|
4365
5266
|
srcEnd,
|
|
4366
|
-
destStart
|
|
4367
|
-
destEnd
|
|
5267
|
+
destStart,
|
|
5268
|
+
destEnd
|
|
4368
5269
|
};
|
|
4369
5270
|
});
|
|
4370
5271
|
const otherSources = other.entries.filter((e) => e.src.path !== this.dest.path || e.src.text !== this.dest.text);
|
|
@@ -4385,10 +5286,6 @@ var SrcMap = class SrcMap {
|
|
|
4385
5286
|
};
|
|
4386
5287
|
}
|
|
4387
5288
|
};
|
|
4388
|
-
/** sort entries in place by src start position */
|
|
4389
|
-
function sortSrc(entries) {
|
|
4390
|
-
entries.sort((a, b) => a.srcStart - b.srcStart);
|
|
4391
|
-
}
|
|
4392
5289
|
/** Incrementally append to a string, tracking source references */
|
|
4393
5290
|
var SrcMapBuilder = class {
|
|
4394
5291
|
#fragments = [];
|
|
@@ -4442,11 +5339,6 @@ var SrcMapBuilder = class {
|
|
|
4442
5339
|
};
|
|
4443
5340
|
this.add("\n", srcStart, srcEnd);
|
|
4444
5341
|
}
|
|
4445
|
-
/** copy a string fragment from the src to the destination string */
|
|
4446
|
-
addCopy(srcStart, srcEnd) {
|
|
4447
|
-
const fragment = this.source.text.slice(srcStart, srcEnd);
|
|
4448
|
-
this.add(fragment, srcStart, srcEnd);
|
|
4449
|
-
}
|
|
4450
5342
|
/** return a SrcMap */
|
|
4451
5343
|
static build(builders) {
|
|
4452
5344
|
const map = new SrcMap({ text: builders.map((b) => b.#fragments.join("")).join("") }, builders.flatMap((b) => b.#entries));
|
|
@@ -4454,6 +5346,10 @@ var SrcMapBuilder = class {
|
|
|
4454
5346
|
return map;
|
|
4455
5347
|
}
|
|
4456
5348
|
};
|
|
5349
|
+
/** sort entries in place by src start position */
|
|
5350
|
+
function sortSrc(entries) {
|
|
5351
|
+
entries.sort((a, b) => a.srcStart - b.srcStart);
|
|
5352
|
+
}
|
|
4457
5353
|
//#endregion
|
|
4458
5354
|
//#region src/Linker.ts
|
|
4459
5355
|
/**
|
|
@@ -4471,10 +5367,12 @@ async function link(params) {
|
|
|
4471
5367
|
/** linker api for benchmarking */
|
|
4472
5368
|
function _linkSync(params) {
|
|
4473
5369
|
const { weslSrc, libs = [], packageName, debugWeslRoot, resolver } = params;
|
|
5370
|
+
const { weslExtensions } = params;
|
|
4474
5371
|
if (!resolver && !weslSrc) throw new Error("Either resolver or weslSrc must be provided");
|
|
4475
5372
|
const allResolvers = [resolver ?? new RecordResolver(weslSrc, {
|
|
4476
5373
|
packageName,
|
|
4477
|
-
debugWeslRoot
|
|
5374
|
+
debugWeslRoot,
|
|
5375
|
+
weslExtensions
|
|
4478
5376
|
}), ...createLibraryResolvers(libs, debugWeslRoot)];
|
|
4479
5377
|
const finalResolver = allResolvers.length === 1 ? allResolvers[0] : new CompositeResolver(allResolvers);
|
|
4480
5378
|
return linkRegistry({
|
|
@@ -4607,264 +5505,6 @@ function builderFromModule(srcModule) {
|
|
|
4607
5505
|
});
|
|
4608
5506
|
}
|
|
4609
5507
|
//#endregion
|
|
4610
|
-
//#region src/LinkerUtil.ts
|
|
4611
|
-
function visitAst(elem, visitor) {
|
|
4612
|
-
visitor(elem);
|
|
4613
|
-
if (elem.contents) elem.contents.forEach((child) => {
|
|
4614
|
-
visitAst(child, visitor);
|
|
4615
|
-
});
|
|
4616
|
-
}
|
|
4617
|
-
//#endregion
|
|
4618
|
-
//#region src/RawEmit.ts
|
|
4619
|
-
function attributeToString$1(e) {
|
|
4620
|
-
const { kind } = e.attribute;
|
|
4621
|
-
if (kind === "@attribute") {
|
|
4622
|
-
const { params } = e.attribute;
|
|
4623
|
-
if (params === void 0 || params.length === 0) return "@" + e.attribute.name;
|
|
4624
|
-
else return `@${e.attribute.name}(${params.map((param) => contentsToString(param)).join(", ")})`;
|
|
4625
|
-
} else if (kind === "@builtin") return "@builtin(" + e.attribute.param.name + ")";
|
|
4626
|
-
else if (kind === "@diagnostic") return "@diagnostic" + diagnosticControlToString(e.attribute.severity, e.attribute.rule);
|
|
4627
|
-
else if (kind === "@if") return `@if(${expressionToString(e.attribute.param.expression)})`;
|
|
4628
|
-
else if (kind === "@elif") return `@elif(${expressionToString(e.attribute.param.expression)})`;
|
|
4629
|
-
else if (kind === "@else") return "@else";
|
|
4630
|
-
else if (kind === "@interpolate") return `@interpolate(${e.attribute.params.map((v) => v.name).join(", ")})`;
|
|
4631
|
-
else assertUnreachable(kind);
|
|
4632
|
-
}
|
|
4633
|
-
function typeListToString(params) {
|
|
4634
|
-
return `<${params.map(typeParamToString).join(", ")}>`;
|
|
4635
|
-
}
|
|
4636
|
-
function typeParamToString(param) {
|
|
4637
|
-
if (param === void 0) return "?";
|
|
4638
|
-
if (param.kind === "type") return typeRefToString(param);
|
|
4639
|
-
return expressionToString(param);
|
|
4640
|
-
}
|
|
4641
|
-
function typeRefToString(t) {
|
|
4642
|
-
if (!t) return "?";
|
|
4643
|
-
const { name, templateParams } = t;
|
|
4644
|
-
const params = templateParams ? typeListToString(templateParams) : "";
|
|
4645
|
-
return `${refToString(name)}${params}`;
|
|
4646
|
-
}
|
|
4647
|
-
function refToString(ref) {
|
|
4648
|
-
if (typeof ref === "string") return ref;
|
|
4649
|
-
if (ref.std) return ref.originalName;
|
|
4650
|
-
const decl = findDecl(ref);
|
|
4651
|
-
return decl.mangledName || decl.originalName;
|
|
4652
|
-
}
|
|
4653
|
-
function contentsToString(elem) {
|
|
4654
|
-
if (elem.kind === "translate-time-expression") throw new Error("Not supported");
|
|
4655
|
-
else if (elem.kind === "expression" || elem.kind === "stuff") return elem.contents.map((c) => {
|
|
4656
|
-
const { kind } = c;
|
|
4657
|
-
if (kind === "text") return c.srcModule.src.slice(c.start, c.end);
|
|
4658
|
-
else if (kind === "ref") return refToString(c.ident);
|
|
4659
|
-
else return `?${c.kind}?`;
|
|
4660
|
-
}).join(" ");
|
|
4661
|
-
else if (elem.kind === "name") return elem.name;
|
|
4662
|
-
else assertUnreachable(elem);
|
|
4663
|
-
}
|
|
4664
|
-
//#endregion
|
|
4665
|
-
//#region src/Reflection.ts
|
|
4666
|
-
const textureStorage = matchOneOf(textureStorageTypes);
|
|
4667
|
-
matchOneOf(sampledTextureTypes);
|
|
4668
|
-
matchOneOf(multisampledTextureTypes);
|
|
4669
|
-
//#endregion
|
|
4670
|
-
//#region src/TransformBindingStructs.ts
|
|
4671
|
-
function bindingStructsPlugin() {
|
|
4672
|
-
return { transform: lowerBindingStructs };
|
|
4673
|
-
}
|
|
4674
|
-
/**
|
|
4675
|
-
* Transform binding structures into binding variables by mutating the AST.
|
|
4676
|
-
*
|
|
4677
|
-
* First we find all the binding structs:
|
|
4678
|
-
* . find all the structs in the module by filtering the moduleElem.contents
|
|
4679
|
-
* . for each struct:
|
|
4680
|
-
* . mark any structs with that contain @group or @binding annotations as 'binding structs' and save them in a list
|
|
4681
|
-
* . (later) create reverse links from structs to struct members
|
|
4682
|
-
* . (later) visit all the binding structs and traverse to referencing structs, marking the referencing structs as binding structs too
|
|
4683
|
-
* Generate synethic AST nodes for binding variables
|
|
4684
|
-
*
|
|
4685
|
-
* Find all references to binding struct members
|
|
4686
|
-
* . find the componound idents by traversing moduleElem.contents
|
|
4687
|
-
* . filter to find the compound idents that refer to 'binding structs'
|
|
4688
|
-
* . go from each ident to its declaration,
|
|
4689
|
-
* . declaration to typeRef reference
|
|
4690
|
-
* . typeRef to type declaration
|
|
4691
|
-
* . check type declaration to see if it's a binding struct
|
|
4692
|
-
* . record the intermediate declaration (e.g. a fn param b:Bindings from 'fn(b:Bindings)' )
|
|
4693
|
-
* rewrite references to binding struct members as synthetic elements
|
|
4694
|
-
*
|
|
4695
|
-
* Remove the binding structs from the AST
|
|
4696
|
-
* Remove the intermediate fn param declarations from the AST
|
|
4697
|
-
* Add the new binding variables to the AST
|
|
4698
|
-
*
|
|
4699
|
-
* @return the binding structs and the mutated AST
|
|
4700
|
-
*/
|
|
4701
|
-
function lowerBindingStructs(ast) {
|
|
4702
|
-
const clonedAst = structuredClone(ast);
|
|
4703
|
-
const { moduleElem, globalNames, notableElems } = clonedAst;
|
|
4704
|
-
const bindingStructs = markBindingStructs(moduleElem);
|
|
4705
|
-
markEntryTypes(moduleElem, bindingStructs);
|
|
4706
|
-
const newVars = bindingStructs.flatMap((s) => transformBindingStruct(s, globalNames));
|
|
4707
|
-
const bindingRefs = findRefsToBindingStructs(moduleElem);
|
|
4708
|
-
bindingRefs.forEach(({ memberRef, struct }) => {
|
|
4709
|
-
transformBindingReference(memberRef, struct);
|
|
4710
|
-
});
|
|
4711
|
-
bindingRefs.forEach(({ intermediates }) => {
|
|
4712
|
-
intermediates.forEach((e) => {
|
|
4713
|
-
e.contents = [];
|
|
4714
|
-
});
|
|
4715
|
-
});
|
|
4716
|
-
const contents = removeBindingStructs(moduleElem);
|
|
4717
|
-
moduleElem.contents = [...newVars, ...contents];
|
|
4718
|
-
notableElems.bindingStructs = bindingStructs;
|
|
4719
|
-
return {
|
|
4720
|
-
...clonedAst,
|
|
4721
|
-
moduleElem
|
|
4722
|
-
};
|
|
4723
|
-
}
|
|
4724
|
-
function markEntryTypes(moduleElem, bindingStructs) {
|
|
4725
|
-
const fnFound = fnReferencesBindingStruct(moduleElem.contents.filter((e) => e.kind === "fn"), bindingStructs);
|
|
4726
|
-
if (fnFound) {
|
|
4727
|
-
const { fn, struct } = fnFound;
|
|
4728
|
-
struct.entryFn = fn;
|
|
4729
|
-
}
|
|
4730
|
-
}
|
|
4731
|
-
function fnReferencesBindingStruct(fns, bindingStructs) {
|
|
4732
|
-
for (const fn of fns) {
|
|
4733
|
-
const { params } = fn;
|
|
4734
|
-
for (const p of params) {
|
|
4735
|
-
const referencedElem = ((p.name?.typeRef?.name)?.refersTo)?.declElem;
|
|
4736
|
-
const struct = bindingStructs.find((s) => s === referencedElem);
|
|
4737
|
-
if (struct) return {
|
|
4738
|
-
fn,
|
|
4739
|
-
struct
|
|
4740
|
-
};
|
|
4741
|
-
}
|
|
4742
|
-
}
|
|
4743
|
-
}
|
|
4744
|
-
function removeBindingStructs(moduleElem) {
|
|
4745
|
-
return moduleElem.contents.filter((elem) => elem.kind !== "struct" || !elem.bindingStruct);
|
|
4746
|
-
}
|
|
4747
|
-
/** mutate the AST, marking StructElems as bindingStructs
|
|
4748
|
-
* (if they contain ptrs with @group @binding annotations)
|
|
4749
|
-
* @return the binding structs
|
|
4750
|
-
*/
|
|
4751
|
-
function markBindingStructs(moduleElem) {
|
|
4752
|
-
const bindingStructs = moduleElem.contents.filter((elem) => elem.kind === "struct").filter(containsBinding);
|
|
4753
|
-
bindingStructs.forEach((struct) => {
|
|
4754
|
-
struct.bindingStruct = true;
|
|
4755
|
-
});
|
|
4756
|
-
return bindingStructs;
|
|
4757
|
-
}
|
|
4758
|
-
/** @return true if this struct contains a member with marked with @binding or @group */
|
|
4759
|
-
function containsBinding(struct) {
|
|
4760
|
-
return struct.members.some(({ attributes }) => bindingAttribute(attributes));
|
|
4761
|
-
}
|
|
4762
|
-
function bindingAttribute(attributes) {
|
|
4763
|
-
if (!attributes) return false;
|
|
4764
|
-
return attributes.some(({ attribute }) => attribute.kind === "@attribute" && (attribute.name === "binding" || attribute.name === "group"));
|
|
4765
|
-
}
|
|
4766
|
-
/** convert each member of the binding struct into a synthetic global variable */
|
|
4767
|
-
function transformBindingStruct(s, globalNames) {
|
|
4768
|
-
return s.members.map((member) => {
|
|
4769
|
-
const { typeRef, name: memberName } = member;
|
|
4770
|
-
const { name: typeName } = typeRef;
|
|
4771
|
-
const typeParameters = typeRef?.templateParams;
|
|
4772
|
-
const varName = minimallyMangledName(memberName.name, globalNames);
|
|
4773
|
-
member.mangledVarName = varName;
|
|
4774
|
-
globalNames.add(varName);
|
|
4775
|
-
const attributes = member.attributes?.map(attributeToString$1).join(" ") ?? "";
|
|
4776
|
-
const varTypes = lowerPtrMember(typeName, typeParameters) ?? lowerStdTypeMember(typeName, typeParameters) ?? lowerStorageTextureMember(typeName, typeParameters);
|
|
4777
|
-
if (!varTypes) {
|
|
4778
|
-
console.log("unhandled case transforming member", typeName);
|
|
4779
|
-
return syntheticVar(attributes, varName, "", "??");
|
|
4780
|
-
}
|
|
4781
|
-
const { storage: storageType, varType } = varTypes;
|
|
4782
|
-
return syntheticVar(attributes, varName, storageType, varType);
|
|
4783
|
-
});
|
|
4784
|
-
}
|
|
4785
|
-
function lowerPtrMember(typeName, typeParameters) {
|
|
4786
|
-
if (typeName.originalName === "ptr") {
|
|
4787
|
-
const origParams = typeParameters ?? [];
|
|
4788
|
-
const newParams = [origParams[0]];
|
|
4789
|
-
if (origParams[2]) newParams.push(origParams[2]);
|
|
4790
|
-
return {
|
|
4791
|
-
storage: typeListToString(newParams),
|
|
4792
|
-
varType: typeParamToString(origParams?.[1])
|
|
4793
|
-
};
|
|
4794
|
-
}
|
|
4795
|
-
}
|
|
4796
|
-
function lowerStdTypeMember(typeName, typeParameters) {
|
|
4797
|
-
if (typeof typeName !== "string") return {
|
|
4798
|
-
varType: (typeName.std ? typeName.originalName : "??") + (typeParameters ? typeListToString(typeParameters) : ""),
|
|
4799
|
-
storage: ""
|
|
4800
|
-
};
|
|
4801
|
-
}
|
|
4802
|
-
function lowerStorageTextureMember(typeName, typeParameters) {
|
|
4803
|
-
if (textureStorage.test(typeName.originalName)) return {
|
|
4804
|
-
varType: typeName + (typeParameters ? typeListToString(typeParameters) : ""),
|
|
4805
|
-
storage: ""
|
|
4806
|
-
};
|
|
4807
|
-
}
|
|
4808
|
-
function syntheticVar(attributes, varName, storageTemplate, varType) {
|
|
4809
|
-
return {
|
|
4810
|
-
kind: "synthetic",
|
|
4811
|
-
text: `${attributes} var${storageTemplate} ${varName} : ${varType};\n`
|
|
4812
|
-
};
|
|
4813
|
-
}
|
|
4814
|
-
/** find all simple member references in the module that refer to binding structs */
|
|
4815
|
-
function findRefsToBindingStructs(moduleElem) {
|
|
4816
|
-
const members = [];
|
|
4817
|
-
visitAst(moduleElem, (elem) => {
|
|
4818
|
-
if (elem.kind === "memberRef") members.push(elem);
|
|
4819
|
-
});
|
|
4820
|
-
return filterMap(members, refersToBindingStruct);
|
|
4821
|
-
}
|
|
4822
|
-
/** @return true if this memberRef refers to a binding struct */
|
|
4823
|
-
function refersToBindingStruct(memberRef) {
|
|
4824
|
-
const found = traceToStruct(memberRef.name.ident);
|
|
4825
|
-
if (found?.struct.bindingStruct) return {
|
|
4826
|
-
memberRef,
|
|
4827
|
-
...found
|
|
4828
|
-
};
|
|
4829
|
-
}
|
|
4830
|
-
/** If this identifier ultimately refers to a struct type, return the struct declaration */
|
|
4831
|
-
function traceToStruct(ident) {
|
|
4832
|
-
const declElem = findDecl(ident).declElem;
|
|
4833
|
-
if (declElem && declElem.kind === "param") {
|
|
4834
|
-
const name = declElem.name.typeRef?.name;
|
|
4835
|
-
if (typeof name !== "string") {
|
|
4836
|
-
if (name?.std) return;
|
|
4837
|
-
const structElem = findDecl(name).declElem;
|
|
4838
|
-
if (structElem?.kind === "struct") return {
|
|
4839
|
-
struct: structElem,
|
|
4840
|
-
intermediates: [declElem]
|
|
4841
|
-
};
|
|
4842
|
-
return;
|
|
4843
|
-
}
|
|
4844
|
-
}
|
|
4845
|
-
}
|
|
4846
|
-
/** Mutate the member reference elem to instead contain synthetic elem text.
|
|
4847
|
-
* The new text is the mangled var name of the struct member that the memberRef refers to. */
|
|
4848
|
-
function transformBindingReference(memberRef, struct) {
|
|
4849
|
-
const refName = memberRef.member.name;
|
|
4850
|
-
const structMember = struct.members.find((m) => m.name.name === refName);
|
|
4851
|
-
if (!structMember || !structMember.mangledVarName) {
|
|
4852
|
-
console.log(`missing mangledVarName for ${refName}`);
|
|
4853
|
-
return {
|
|
4854
|
-
kind: "synthetic",
|
|
4855
|
-
text: refName
|
|
4856
|
-
};
|
|
4857
|
-
}
|
|
4858
|
-
const { extraComponents } = memberRef;
|
|
4859
|
-
const extraText = extraComponents ? contentsToString(extraComponents) : "";
|
|
4860
|
-
const synthElem = {
|
|
4861
|
-
kind: "synthetic",
|
|
4862
|
-
text: structMember.mangledVarName + extraText
|
|
4863
|
-
};
|
|
4864
|
-
memberRef.contents = [synthElem];
|
|
4865
|
-
return synthElem;
|
|
4866
|
-
}
|
|
4867
|
-
//#endregion
|
|
4868
5508
|
//#region src/WeslDevice.ts
|
|
4869
5509
|
/**
|
|
4870
5510
|
* Creates {@link WeslDevice} for usage with WESL.
|
|
@@ -4933,4 +5573,4 @@ function makeWeslDevice(device) {
|
|
|
4933
5573
|
return device;
|
|
4934
5574
|
}
|
|
4935
5575
|
//#endregion
|
|
4936
|
-
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 };
|