wgsl-play 0.1.0 → 0.1.2
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/{WgslPlay-zK9mE5rV.js → WgslPlay-Bmm8iBvO.js} +5 -3
- package/dist/WgslPlay.js +1 -1
- package/dist/index.js +1 -1
- package/dist/wgsl-play.js +1727 -837
- package/package.json +10 -6
- package/src/Renderer.ts +8 -5
- package/src/WgslPlay.ts +6 -2
package/dist/wgsl-play.js
CHANGED
|
@@ -630,6 +630,112 @@ function emptyScope(parent, kind = "scope") {
|
|
|
630
630
|
};
|
|
631
631
|
}
|
|
632
632
|
//#endregion
|
|
633
|
+
//#region ../wesl/src/LinkerUtil.ts
|
|
634
|
+
/** A module's top-level declarations of a given kind, narrowed to that elem type. */
|
|
635
|
+
function declsOfKind(moduleElem, kind) {
|
|
636
|
+
return moduleElem.decls.filter((e) => e.kind === kind);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Child elems of any AST node: the typed structural fields (attributes plus
|
|
640
|
+
* body / condition / decls / ...) in source order. Returns [] for leaf elems.
|
|
641
|
+
*/
|
|
642
|
+
function childElems(elem) {
|
|
643
|
+
return [...elem.attributes ?? [], ...structuralFields(elem)];
|
|
644
|
+
}
|
|
645
|
+
/** The child elems held in a node's typed fields, in source order (attributes
|
|
646
|
+
* are added separately by {@link childElems}). Empty for leaf kinds. The
|
|
647
|
+
* switch is exhaustive: a new elem kind without a case fails to compile in the
|
|
648
|
+
* default, so children can't be silently dropped. */
|
|
649
|
+
function structuralFields(elem) {
|
|
650
|
+
switch (elem.kind) {
|
|
651
|
+
case "module": return elem.decls;
|
|
652
|
+
case "var":
|
|
653
|
+
case "gvar": return [
|
|
654
|
+
...elem.template ?? [],
|
|
655
|
+
elem.name,
|
|
656
|
+
...elem.init ? [elem.init] : []
|
|
657
|
+
];
|
|
658
|
+
case "let":
|
|
659
|
+
case "const":
|
|
660
|
+
case "override": return [elem.name, ...elem.init ? [elem.init] : []];
|
|
661
|
+
case "alias": return [elem.name, elem.typeRef];
|
|
662
|
+
case "assert": return [elem.expression];
|
|
663
|
+
case "struct": return [elem.name, ...elem.members];
|
|
664
|
+
case "member": return [elem.name, elem.typeRef];
|
|
665
|
+
case "type": return [elem.name.refIdentElem, ...elem.templateParams ?? []];
|
|
666
|
+
case "expression": return [elem.expression];
|
|
667
|
+
case "binary-expression": return [elem.left, elem.right];
|
|
668
|
+
case "unary-expression":
|
|
669
|
+
case "parenthesized-expression": return [elem.expression];
|
|
670
|
+
case "component-expression": return [elem.base, elem.access];
|
|
671
|
+
case "component-member-expression": return [elem.base, elem.access];
|
|
672
|
+
case "call-expression": return [
|
|
673
|
+
elem.function,
|
|
674
|
+
...elem.templateArgs ?? [],
|
|
675
|
+
...elem.arguments
|
|
676
|
+
];
|
|
677
|
+
case "param": return [elem.name];
|
|
678
|
+
case "typeDecl": return elem.typeRef ? [elem.decl, elem.typeRef] : [elem.decl];
|
|
679
|
+
case "fn": return [
|
|
680
|
+
elem.name,
|
|
681
|
+
...elem.params,
|
|
682
|
+
...elem.returnType ? [elem.returnType] : [],
|
|
683
|
+
elem.body
|
|
684
|
+
];
|
|
685
|
+
case "block": return elem.body;
|
|
686
|
+
case "if": return [
|
|
687
|
+
elem.condition,
|
|
688
|
+
elem.body,
|
|
689
|
+
...elem.else ? [elem.else] : []
|
|
690
|
+
];
|
|
691
|
+
case "for": return [
|
|
692
|
+
elem.init,
|
|
693
|
+
elem.condition,
|
|
694
|
+
elem.update,
|
|
695
|
+
elem.body
|
|
696
|
+
].filter(isDefined);
|
|
697
|
+
case "while": return [elem.condition, elem.body];
|
|
698
|
+
case "loop":
|
|
699
|
+
case "continuing": return [elem.body];
|
|
700
|
+
case "switch": return [
|
|
701
|
+
elem.selector,
|
|
702
|
+
...elem.bodyAttributes ?? [],
|
|
703
|
+
...elem.clauses
|
|
704
|
+
];
|
|
705
|
+
case "switch-clause": return [...exprSelectors(elem.selectors), elem.body];
|
|
706
|
+
case "return": return elem.value ? [elem.value] : [];
|
|
707
|
+
case "break": return elem.condition ? [elem.condition] : [];
|
|
708
|
+
case "assign": return elem.lhs.kind === "phony" ? [elem.rhs] : [elem.lhs, elem.rhs];
|
|
709
|
+
case "increment":
|
|
710
|
+
case "decrement": return [elem.target];
|
|
711
|
+
case "call": return [elem.call];
|
|
712
|
+
case "continue":
|
|
713
|
+
case "discard":
|
|
714
|
+
case "empty": return [];
|
|
715
|
+
case "do": return [
|
|
716
|
+
elem.name,
|
|
717
|
+
...elem.params,
|
|
718
|
+
elem.body
|
|
719
|
+
];
|
|
720
|
+
case "import":
|
|
721
|
+
case "directive":
|
|
722
|
+
case "attribute":
|
|
723
|
+
case "name":
|
|
724
|
+
case "literal":
|
|
725
|
+
case "ref":
|
|
726
|
+
case "decl":
|
|
727
|
+
case "synthetic": return [];
|
|
728
|
+
default: return [];
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
function isDefined(value) {
|
|
732
|
+
return value !== void 0;
|
|
733
|
+
}
|
|
734
|
+
/** Drop the `"default"` sentinel, keeping only real case-selector expressions. */
|
|
735
|
+
function exprSelectors(selectors) {
|
|
736
|
+
return selectors.filter((s) => s !== "default");
|
|
737
|
+
}
|
|
738
|
+
//#endregion
|
|
633
739
|
//#region ../wesl/src/StandardTypes.ts
|
|
634
740
|
const stdFns = `bitcast all any select arrayLength
|
|
635
741
|
abs acos acosh asin asinh atan atanh atan2 ceil clamp cos cosh
|
|
@@ -660,17 +766,6 @@ const stdFns = `bitcast all any select arrayLength
|
|
|
660
766
|
subgroupInclusiveMul subgroupMax subgroupMin subgroupMul subgroupOr
|
|
661
767
|
subgroupShuffle subgroupShuffleUp subgroupShuffleXor subgroupXor
|
|
662
768
|
quadBroadcast quadSwapDiagonal quadSwapX quadSwapY`.split(/\s+/);
|
|
663
|
-
const sampledTextureTypes = `
|
|
664
|
-
texture_1d texture_2d texture_2d_array texture_3d
|
|
665
|
-
texture_cube texture_cube_array
|
|
666
|
-
`;
|
|
667
|
-
const multisampledTextureTypes = `
|
|
668
|
-
texture_multisampled_2d texture_depth_multisampled_2d
|
|
669
|
-
`;
|
|
670
|
-
const textureStorageTypes = `
|
|
671
|
-
texture_storage_1d texture_storage_2d texture_storage_2d_array
|
|
672
|
-
texture_storage_3d
|
|
673
|
-
`;
|
|
674
769
|
const stdTypes = `array atomic bool f16 f32 i32
|
|
675
770
|
mat2x2 mat2x3 mat2x4 mat3x2 mat3x3 mat3x4 mat4x2 mat4x3 mat4x4
|
|
676
771
|
mat2x2f mat2x3f mat2x4f mat3x2f mat3x3f mat3x4f
|
|
@@ -680,10 +775,18 @@ const stdTypes = `array atomic bool f16 f32 i32
|
|
|
680
775
|
u32 vec2 vec3 vec4 ptr
|
|
681
776
|
vec2i vec3i vec4i vec2u vec3u vec4u
|
|
682
777
|
vec2f vec3f vec4f vec2h vec3h vec4h
|
|
683
|
-
|
|
684
|
-
|
|
778
|
+
|
|
779
|
+
texture_1d texture_2d texture_2d_array texture_3d
|
|
780
|
+
texture_cube texture_cube_array
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
texture_multisampled_2d texture_depth_multisampled_2d
|
|
784
|
+
|
|
685
785
|
texture_external
|
|
686
|
-
|
|
786
|
+
|
|
787
|
+
texture_storage_1d texture_storage_2d texture_storage_2d_array
|
|
788
|
+
texture_storage_3d
|
|
789
|
+
|
|
687
790
|
texture_depth_2d texture_depth_2d_array texture_depth_cube
|
|
688
791
|
texture_depth_cube_array
|
|
689
792
|
sampler sampler_comparison
|
|
@@ -731,6 +834,22 @@ function stdEnumerant(name) {
|
|
|
731
834
|
}
|
|
732
835
|
//#endregion
|
|
733
836
|
//#region ../wesl/src/LowerAndEmit.ts
|
|
837
|
+
/** Statement kinds that emitStatement must not follow with a ';': compound
|
|
838
|
+
* statements take none, locals already carry their own, empty needs none. */
|
|
839
|
+
const noSemicolon = new Set([
|
|
840
|
+
"block",
|
|
841
|
+
"if",
|
|
842
|
+
"for",
|
|
843
|
+
"while",
|
|
844
|
+
"loop",
|
|
845
|
+
"continuing",
|
|
846
|
+
"switch",
|
|
847
|
+
"empty",
|
|
848
|
+
"var",
|
|
849
|
+
"let",
|
|
850
|
+
"const",
|
|
851
|
+
"assert"
|
|
852
|
+
]);
|
|
734
853
|
/** Traverse the AST, starting from root elements, emitting WGSL for each. */
|
|
735
854
|
function lowerAndEmit(params) {
|
|
736
855
|
const { srcBuilder, rootElems, conditions } = params;
|
|
@@ -738,17 +857,30 @@ function lowerAndEmit(params) {
|
|
|
738
857
|
const emitContext = {
|
|
739
858
|
conditions,
|
|
740
859
|
srcBuilder,
|
|
741
|
-
extracting
|
|
860
|
+
extracting,
|
|
861
|
+
indent: 0
|
|
742
862
|
};
|
|
743
863
|
const validElements = skipConditionalFiltering ? rootElems : filterValidElements(rootElems, conditions);
|
|
744
864
|
for (const e of validElements) lowerAndEmitElem(e, emitContext);
|
|
745
865
|
}
|
|
866
|
+
/** Format a diagnostic control as "(severity, rule)" for @diagnostic text. */
|
|
867
|
+
function diagnosticControlToString(severity, rule) {
|
|
868
|
+
const ruleStr = rule[0].name + (rule[1] !== null ? "." + rule[1].name : "");
|
|
869
|
+
return `(${severity.name}, ${ruleStr})`;
|
|
870
|
+
}
|
|
871
|
+
/** Trace through refersTo links until we find the declaration. */
|
|
872
|
+
function findDecl(ident) {
|
|
873
|
+
let i = ident;
|
|
874
|
+
do {
|
|
875
|
+
if (i.kind === "decl") return i;
|
|
876
|
+
i = i.refersTo;
|
|
877
|
+
} while (i);
|
|
878
|
+
throw new Error(`unresolved identifer: ${ident.originalName}`);
|
|
879
|
+
}
|
|
746
880
|
function lowerAndEmitElem(e, ctx) {
|
|
747
881
|
switch (e.kind) {
|
|
748
882
|
case "import": return;
|
|
749
|
-
case "
|
|
750
|
-
emitText(e, ctx);
|
|
751
|
-
return;
|
|
883
|
+
case "do": return;
|
|
752
884
|
case "name":
|
|
753
885
|
emitName(e, ctx);
|
|
754
886
|
return;
|
|
@@ -771,24 +903,45 @@ function lowerAndEmitElem(e, ctx) {
|
|
|
771
903
|
emitExpression(e, ctx);
|
|
772
904
|
return;
|
|
773
905
|
case "param":
|
|
906
|
+
emitAttributes(e.attributes, ctx);
|
|
907
|
+
emitTypedDecl(e.name, ctx);
|
|
908
|
+
return;
|
|
774
909
|
case "typeDecl":
|
|
910
|
+
emitTypedDecl(e, ctx);
|
|
911
|
+
return;
|
|
775
912
|
case "member":
|
|
776
|
-
|
|
913
|
+
emitMember(e, ctx);
|
|
914
|
+
return;
|
|
777
915
|
case "expression":
|
|
778
|
-
|
|
916
|
+
emitExpression(e.expression, ctx);
|
|
917
|
+
return;
|
|
779
918
|
case "switch-clause":
|
|
780
|
-
|
|
919
|
+
emitSwitchClause(e, ctx);
|
|
781
920
|
return;
|
|
782
|
-
case "
|
|
783
|
-
|
|
921
|
+
case "type":
|
|
922
|
+
emitTypeRef(e, ctx);
|
|
784
923
|
return;
|
|
785
924
|
case "module":
|
|
786
925
|
emitModule(e, ctx);
|
|
787
926
|
return;
|
|
788
927
|
case "var":
|
|
789
928
|
case "let":
|
|
790
|
-
case "
|
|
929
|
+
case "block":
|
|
930
|
+
case "if":
|
|
931
|
+
case "for":
|
|
932
|
+
case "while":
|
|
933
|
+
case "loop":
|
|
791
934
|
case "continuing":
|
|
935
|
+
case "switch":
|
|
936
|
+
case "return":
|
|
937
|
+
case "break":
|
|
938
|
+
case "continue":
|
|
939
|
+
case "discard":
|
|
940
|
+
case "assign":
|
|
941
|
+
case "increment":
|
|
942
|
+
case "decrement":
|
|
943
|
+
case "call":
|
|
944
|
+
case "empty":
|
|
792
945
|
emitStatement(e, ctx);
|
|
793
946
|
return;
|
|
794
947
|
case "override":
|
|
@@ -800,52 +953,131 @@ function lowerAndEmitElem(e, ctx) {
|
|
|
800
953
|
return;
|
|
801
954
|
case "fn":
|
|
802
955
|
emitRootElemNl(ctx);
|
|
956
|
+
emitRootLeading(e, ctx);
|
|
803
957
|
emitFn(e, ctx);
|
|
958
|
+
emitTrailingComments(e, ctx);
|
|
804
959
|
return;
|
|
805
960
|
case "struct":
|
|
806
961
|
emitRootElemNl(ctx);
|
|
962
|
+
emitRootLeading(e, ctx);
|
|
807
963
|
emitStruct(e, ctx);
|
|
964
|
+
emitTrailingComments(e, ctx);
|
|
808
965
|
return;
|
|
809
966
|
case "attribute":
|
|
810
967
|
emitAttribute(e, ctx);
|
|
811
968
|
return;
|
|
812
969
|
case "directive":
|
|
970
|
+
ctx.srcBuilder.addNl();
|
|
971
|
+
emitRootLeading(e, ctx);
|
|
813
972
|
emitDirective(e, ctx);
|
|
973
|
+
emitTrailingComments(e, ctx);
|
|
814
974
|
return;
|
|
815
975
|
default: assertUnreachable(e);
|
|
816
976
|
}
|
|
817
977
|
}
|
|
818
|
-
function
|
|
819
|
-
|
|
978
|
+
function emitName(e, ctx) {
|
|
979
|
+
ctx.srcBuilder.add(e.name, e.start, e.end);
|
|
820
980
|
}
|
|
821
|
-
function
|
|
822
|
-
const
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
981
|
+
function emitSynthetic(e, ctx) {
|
|
982
|
+
const { text } = e;
|
|
983
|
+
ctx.srcBuilder.addSynthetic(text, text, 0, text.length);
|
|
984
|
+
}
|
|
985
|
+
function emitRefIdent(e, ctx) {
|
|
986
|
+
if (e.ident.std) ctx.srcBuilder.add(e.ident.originalName, e.start, e.end);
|
|
987
|
+
else ctx.srcBuilder.add(displayName(findDecl(e.ident)), e.start, e.end);
|
|
988
|
+
}
|
|
989
|
+
function emitDeclIdent(e, ctx) {
|
|
990
|
+
ctx.srcBuilder.add(displayName(e.ident), e.start, e.end);
|
|
991
|
+
}
|
|
992
|
+
/** Emit an expression with any comments attached to it. Comments inside an
|
|
993
|
+
* expression (e.g. `foo(1, /* x *\/ 2)`) ride on the relevant sub-node and are
|
|
994
|
+
* emitted inline around it here. */
|
|
995
|
+
function emitExpression(e, ctx) {
|
|
996
|
+
emitInlineLeading(e, ctx);
|
|
997
|
+
emitExpressionCore(e, ctx);
|
|
998
|
+
emitInlineTrailing(e, ctx);
|
|
999
|
+
}
|
|
1000
|
+
function emitAttributes(attributes, ctx) {
|
|
1001
|
+
attributes?.forEach((a) => {
|
|
1002
|
+
emitInlineLeading(a, ctx);
|
|
1003
|
+
if (emitAttribute(a, ctx)) {
|
|
1004
|
+
emitInlineTrailing(a, ctx);
|
|
1005
|
+
ctx.srcBuilder.add(" ", a.start, a.end);
|
|
826
1006
|
}
|
|
827
|
-
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
/** Emit a declared identifier with its optional `: type` annotation. */
|
|
1010
|
+
function emitTypedDecl(name, ctx) {
|
|
1011
|
+
emitInlineLeading(name, ctx);
|
|
1012
|
+
emitDeclIdent(name.decl, ctx);
|
|
1013
|
+
if (name.typeRef) {
|
|
1014
|
+
ctx.srcBuilder.appendNext(": ");
|
|
1015
|
+
emitTypeRef(name.typeRef, ctx);
|
|
1016
|
+
}
|
|
1017
|
+
emitInlineTrailing(name, ctx);
|
|
1018
|
+
}
|
|
1019
|
+
/** Emit a struct member from its typed fields: `[attrs] name: type`. */
|
|
1020
|
+
function emitMember(member, ctx) {
|
|
1021
|
+
emitAttributes(member.attributes, ctx);
|
|
1022
|
+
emitName(member.name, ctx);
|
|
1023
|
+
ctx.srcBuilder.appendNext(": ");
|
|
1024
|
+
emitTypeRef(member.typeRef, ctx);
|
|
1025
|
+
}
|
|
1026
|
+
/** A `case sel, ...:` or `default:` clause with its `{ ... }` body. The selector
|
|
1027
|
+
* colon is optional in WGSL but kept here as the canonical form. */
|
|
1028
|
+
function emitSwitchClause(e, ctx) {
|
|
1029
|
+
const builder = ctx.srcBuilder;
|
|
1030
|
+
emitLeadingComments(e, ctx);
|
|
1031
|
+
newLine(ctx);
|
|
1032
|
+
emitAttributes(e.attributes, ctx);
|
|
1033
|
+
if (e.selectors.length === 1 && e.selectors[0] === "default") builder.appendNext("default");
|
|
1034
|
+
else {
|
|
1035
|
+
builder.appendNext("case ");
|
|
1036
|
+
e.selectors.forEach((sel, i) => {
|
|
1037
|
+
if (i > 0) builder.appendNext(", ");
|
|
1038
|
+
if (sel === "default") builder.appendNext("default");
|
|
1039
|
+
else emitExpression(sel, ctx);
|
|
1040
|
+
});
|
|
828
1041
|
}
|
|
1042
|
+
builder.appendNext(": ");
|
|
1043
|
+
emitBlock(e.body, ctx);
|
|
1044
|
+
emitTrailingComments(e, ctx);
|
|
829
1045
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1046
|
+
/** Emit a type reference structurally: name plus an optional <...> arg list. */
|
|
1047
|
+
function emitTypeRef(e, ctx) {
|
|
1048
|
+
emitRefIdent(e.name.refIdentElem, ctx);
|
|
1049
|
+
if (e.templateParams) emitTemplateArgs(e.templateParams, ctx);
|
|
1050
|
+
}
|
|
1051
|
+
function emitModule(e, ctx) {
|
|
1052
|
+
const validElements = filterValidElements(e.decls, ctx.conditions);
|
|
1053
|
+
for (const child of validElements) lowerAndEmitElem(child, ctx);
|
|
1054
|
+
}
|
|
1055
|
+
/** Emit one statement on its own line, with attached leading/trailing comments. */
|
|
1056
|
+
function emitStatement(stmt, ctx) {
|
|
1057
|
+
emitLeadingComments(stmt, ctx);
|
|
1058
|
+
newLine(ctx);
|
|
1059
|
+
emitCoreSemi(stmt, ctx);
|
|
1060
|
+
emitTrailingComments(stmt, ctx);
|
|
833
1061
|
}
|
|
834
1062
|
function emitRootDecl(e, ctx) {
|
|
835
1063
|
emitRootElemNl(ctx);
|
|
836
|
-
|
|
837
|
-
|
|
1064
|
+
emitRootLeading(e, ctx);
|
|
1065
|
+
emitValueDecl(e, ctx);
|
|
1066
|
+
emitTrailingComments(e, ctx);
|
|
838
1067
|
}
|
|
839
1068
|
/** Emit newlines between root elements. */
|
|
840
1069
|
function emitRootElemNl(ctx) {
|
|
841
1070
|
ctx.srcBuilder.addNl();
|
|
842
1071
|
ctx.srcBuilder.addNl();
|
|
843
1072
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
function
|
|
848
|
-
|
|
1073
|
+
/** Leading comments for a root declaration, each on its own line. The inter-decl
|
|
1074
|
+
* spacing already left us at a fresh line, so a newline follows each comment
|
|
1075
|
+
* (rather than preceding it as in emitLeadingComments). */
|
|
1076
|
+
function emitRootLeading(e, ctx) {
|
|
1077
|
+
for (const c of e.commentsBefore ?? []) {
|
|
1078
|
+
emitComment(c, ctx);
|
|
1079
|
+
newLine(ctx);
|
|
1080
|
+
}
|
|
849
1081
|
}
|
|
850
1082
|
/** Emit function explicitly to control commas between conditional parameters. */
|
|
851
1083
|
function emitFn(e, ctx) {
|
|
@@ -853,27 +1085,35 @@ function emitFn(e, ctx) {
|
|
|
853
1085
|
const { conditions, srcBuilder: builder } = ctx;
|
|
854
1086
|
emitAttributes(attributes, ctx);
|
|
855
1087
|
builder.add("fn ", name.start - 3, name.start);
|
|
1088
|
+
emitInlineLeading(name, ctx);
|
|
856
1089
|
emitDeclIdent(name, ctx);
|
|
1090
|
+
emitInlineTrailing(name, ctx);
|
|
857
1091
|
builder.appendNext("(");
|
|
858
1092
|
const validParams = filterValidElements(params, conditions);
|
|
859
1093
|
validParams.forEach((p, i) => {
|
|
860
|
-
|
|
861
|
-
|
|
1094
|
+
emitInlineLeading(p, ctx);
|
|
1095
|
+
emitAttributes(p.attributes, ctx);
|
|
1096
|
+
emitTypedDecl(p.name, ctx);
|
|
1097
|
+
emitInlineTrailing(p, ctx);
|
|
862
1098
|
if (i < validParams.length - 1) builder.appendNext(", ");
|
|
863
1099
|
});
|
|
864
1100
|
builder.appendNext(") ");
|
|
865
1101
|
if (returnType) {
|
|
866
1102
|
builder.appendNext("-> ");
|
|
867
1103
|
emitAttributes(returnAttributes, ctx);
|
|
868
|
-
|
|
1104
|
+
emitInlineLeading(returnType, ctx);
|
|
1105
|
+
emitTypeRef(returnType, ctx);
|
|
869
1106
|
builder.appendNext(" ");
|
|
870
1107
|
}
|
|
871
|
-
|
|
1108
|
+
emitBlock(body, ctx);
|
|
872
1109
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1110
|
+
/** Trailing comments: kept on the element's line, after its text. */
|
|
1111
|
+
function emitTrailingComments(e, ctx) {
|
|
1112
|
+
if (!e.commentsAfter) return;
|
|
1113
|
+
for (const c of e.commentsAfter) {
|
|
1114
|
+
ctx.srcBuilder.appendNext(" ");
|
|
1115
|
+
emitComment(c, ctx);
|
|
1116
|
+
}
|
|
877
1117
|
}
|
|
878
1118
|
/** Emit structs explicitly to control commas between conditional members. */
|
|
879
1119
|
function emitStruct(e, ctx) {
|
|
@@ -887,125 +1127,22 @@ function emitStruct(e, ctx) {
|
|
|
887
1127
|
}
|
|
888
1128
|
emitAttributes(attributes, ctx);
|
|
889
1129
|
srcBuilder.add("struct ", start, name.start);
|
|
1130
|
+
emitInlineLeading(name, ctx);
|
|
890
1131
|
emitDeclIdent(name, ctx);
|
|
891
|
-
|
|
1132
|
+
emitInlineTrailing(name, ctx);
|
|
1133
|
+
if (validLength === 1 && !hasComments(validMembers[0])) {
|
|
892
1134
|
srcBuilder.appendNext(" { ");
|
|
893
|
-
|
|
1135
|
+
emitMember(validMembers[0], ctx);
|
|
894
1136
|
srcBuilder.appendNext(" }");
|
|
895
1137
|
srcBuilder.addNl();
|
|
896
1138
|
} else {
|
|
897
1139
|
srcBuilder.appendNext(" {");
|
|
898
1140
|
srcBuilder.addNl();
|
|
899
|
-
|
|
900
|
-
srcBuilder.appendNext(" ");
|
|
901
|
-
emitContentsNoWs(m, ctx);
|
|
902
|
-
srcBuilder.appendNext(",");
|
|
903
|
-
srcBuilder.addNl();
|
|
904
|
-
});
|
|
1141
|
+
for (const m of validMembers) emitMemberLine(m, ctx);
|
|
905
1142
|
srcBuilder.appendNext("}");
|
|
906
1143
|
srcBuilder.addNl();
|
|
907
1144
|
}
|
|
908
1145
|
}
|
|
909
|
-
function warnEmptyStruct(e) {
|
|
910
|
-
const { name, members } = e;
|
|
911
|
-
const condStr = members.length ? "(with current conditions)" : "";
|
|
912
|
-
failIdentElem(name, `struct '${name.ident.originalName}' has no members ${condStr}`);
|
|
913
|
-
}
|
|
914
|
-
function emitSynthetic(e, ctx) {
|
|
915
|
-
const { text } = e;
|
|
916
|
-
ctx.srcBuilder.addSynthetic(text, text, 0, text.length);
|
|
917
|
-
}
|
|
918
|
-
function emitContents(elem, ctx) {
|
|
919
|
-
const validElements = filterValidElements(elem.contents, ctx.conditions);
|
|
920
|
-
for (const e of validElements) lowerAndEmitElem(e, ctx);
|
|
921
|
-
}
|
|
922
|
-
/** Emit contents with leading/trailing whitespace trimming (V2 parser). */
|
|
923
|
-
function emitContentsWithTrimming(elem, ctx) {
|
|
924
|
-
const validElements = filterValidElements(elem.contents, ctx.conditions);
|
|
925
|
-
const firstEmit = validElements.findIndex((e) => !isConditionalAttr(e));
|
|
926
|
-
const lastEmit = validElements.findLastIndex((e) => !isConditionalAttr(e));
|
|
927
|
-
validElements.forEach((elem, i) => {
|
|
928
|
-
if (elem.kind === "text") {
|
|
929
|
-
let text = elem.srcModule.src.slice(elem.start, elem.end);
|
|
930
|
-
if (i === firstEmit) text = text.trimStart();
|
|
931
|
-
if (i === lastEmit) text = text.trimEnd();
|
|
932
|
-
if (text) ctx.srcBuilder.add(text, elem.start, elem.end);
|
|
933
|
-
} else lowerAndEmitElem(elem, ctx);
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
function isConditionalAttr(e) {
|
|
937
|
-
if (e.kind !== "attribute") return false;
|
|
938
|
-
const { kind } = e.attribute;
|
|
939
|
-
return kind === "@if" || kind === "@elif" || kind === "@else";
|
|
940
|
-
}
|
|
941
|
-
/** Emit contents without whitespace. */
|
|
942
|
-
function emitContentsNoWs(elem, ctx) {
|
|
943
|
-
filterValidElements(elem.contents, ctx.conditions).forEach((e) => {
|
|
944
|
-
if (e.kind === "text") {
|
|
945
|
-
const { srcModule, start, end } = e;
|
|
946
|
-
if (srcModule.src.slice(start, end).trim() === "") return;
|
|
947
|
-
}
|
|
948
|
-
lowerAndEmitElem(e, ctx);
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
function emitRefIdent(e, ctx) {
|
|
952
|
-
if (e.ident.std) ctx.srcBuilder.add(e.ident.originalName, e.start, e.end);
|
|
953
|
-
else {
|
|
954
|
-
const mangledName = displayName(findDecl(e.ident));
|
|
955
|
-
ctx.srcBuilder.add(mangledName, e.start, e.end);
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
function emitDeclIdent(e, ctx) {
|
|
959
|
-
const mangledName = displayName(e.ident);
|
|
960
|
-
ctx.srcBuilder.add(mangledName, e.start, e.end);
|
|
961
|
-
}
|
|
962
|
-
function emitExpression(e, ctx) {
|
|
963
|
-
const { kind } = e;
|
|
964
|
-
if (kind === "literal") {
|
|
965
|
-
ctx.srcBuilder.add(e.value, e.start, e.end);
|
|
966
|
-
return;
|
|
967
|
-
}
|
|
968
|
-
if (kind === "ref") {
|
|
969
|
-
emitRefIdent(e, ctx);
|
|
970
|
-
return;
|
|
971
|
-
}
|
|
972
|
-
if (kind === "type") {
|
|
973
|
-
emitContents(e, ctx);
|
|
974
|
-
return;
|
|
975
|
-
}
|
|
976
|
-
if (kind === "binary-expression") {
|
|
977
|
-
emitExpression(e.left, ctx);
|
|
978
|
-
ctx.srcBuilder.add(` ${e.operator.value} `, e.operator.span[0], e.operator.span[1]);
|
|
979
|
-
emitExpression(e.right, ctx);
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
982
|
-
if (kind === "unary-expression") {
|
|
983
|
-
ctx.srcBuilder.add(e.operator.value, e.operator.span[0], e.operator.span[1]);
|
|
984
|
-
emitExpression(e.expression, ctx);
|
|
985
|
-
return;
|
|
986
|
-
}
|
|
987
|
-
if (kind === "parenthesized-expression") {
|
|
988
|
-
emitExpression(e.expression, ctx);
|
|
989
|
-
return;
|
|
990
|
-
}
|
|
991
|
-
if (kind === "call-expression") {
|
|
992
|
-
emitExpression(e.function, ctx);
|
|
993
|
-
if (e.templateArgs) for (const targ of e.templateArgs) lowerAndEmitElem(targ, ctx);
|
|
994
|
-
for (const arg of e.arguments) emitExpression(arg, ctx);
|
|
995
|
-
return;
|
|
996
|
-
}
|
|
997
|
-
if (kind === "component-expression") {
|
|
998
|
-
emitExpression(e.base, ctx);
|
|
999
|
-
emitExpression(e.access, ctx);
|
|
1000
|
-
return;
|
|
1001
|
-
}
|
|
1002
|
-
if (kind === "component-member-expression") {
|
|
1003
|
-
emitExpression(e.base, ctx);
|
|
1004
|
-
if (e.access.kind === "name") ctx.srcBuilder.add(e.access.name, e.access.start, e.access.end);
|
|
1005
|
-
return;
|
|
1006
|
-
}
|
|
1007
|
-
assertUnreachable(kind);
|
|
1008
|
-
}
|
|
1009
1146
|
function emitAttribute(e, ctx) {
|
|
1010
1147
|
const { kind } = e.attribute;
|
|
1011
1148
|
if (kind === "@if" || kind === "@elif" || kind === "@else") return false;
|
|
@@ -1015,11 +1152,13 @@ function emitAttribute(e, ctx) {
|
|
|
1015
1152
|
return true;
|
|
1016
1153
|
}
|
|
1017
1154
|
if (kind === "@builtin") {
|
|
1018
|
-
|
|
1155
|
+
const builtinStr = `@builtin(${e.attribute.param.name})`;
|
|
1156
|
+
ctx.srcBuilder.add(builtinStr, e.start, e.end);
|
|
1019
1157
|
return true;
|
|
1020
1158
|
}
|
|
1021
1159
|
if (kind === "@diagnostic") {
|
|
1022
|
-
const
|
|
1160
|
+
const { severity, rule } = e.attribute;
|
|
1161
|
+
const diagStr = `@diagnostic${diagnosticControlToString(severity, rule)}`;
|
|
1023
1162
|
ctx.srcBuilder.add(diagStr, e.start, e.end);
|
|
1024
1163
|
return true;
|
|
1025
1164
|
}
|
|
@@ -1030,24 +1169,6 @@ function emitAttribute(e, ctx) {
|
|
|
1030
1169
|
}
|
|
1031
1170
|
assertUnreachable(kind);
|
|
1032
1171
|
}
|
|
1033
|
-
function emitStandardAttribute(e, ctx) {
|
|
1034
|
-
if (e.attribute.kind !== "@attribute") return;
|
|
1035
|
-
const { params } = e.attribute;
|
|
1036
|
-
if (!params || params.length === 0) {
|
|
1037
|
-
ctx.srcBuilder.add("@" + e.attribute.name, e.start, e.end);
|
|
1038
|
-
return;
|
|
1039
|
-
}
|
|
1040
|
-
ctx.srcBuilder.add("@" + e.attribute.name + "(", e.start, params[0].start);
|
|
1041
|
-
for (let i = 0; i < params.length; i++) {
|
|
1042
|
-
emitContents(params[i], ctx);
|
|
1043
|
-
if (i < params.length - 1) ctx.srcBuilder.add(",", params[i].end, params[i + 1].start);
|
|
1044
|
-
}
|
|
1045
|
-
ctx.srcBuilder.add(")", params[params.length - 1].end, e.end);
|
|
1046
|
-
}
|
|
1047
|
-
function diagnosticControlToString(severity, rule) {
|
|
1048
|
-
const ruleStr = rule[0].name + (rule[1] !== null ? "." + rule[1].name : "");
|
|
1049
|
-
return `(${severity.name}, ${ruleStr})`;
|
|
1050
|
-
}
|
|
1051
1172
|
function emitDirective(e, ctx) {
|
|
1052
1173
|
const { directive } = e;
|
|
1053
1174
|
const { kind } = directive;
|
|
@@ -1069,14 +1190,351 @@ function displayName(declIdent) {
|
|
|
1069
1190
|
}
|
|
1070
1191
|
return declIdent.mangledName || declIdent.originalName;
|
|
1071
1192
|
}
|
|
1072
|
-
/**
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1193
|
+
/** Leading comments on an inline node: block comments get a trailing space, line
|
|
1194
|
+
* comments force a newline so the following code is not swallowed. */
|
|
1195
|
+
function emitInlineLeading(e, ctx) {
|
|
1196
|
+
for (const c of e.commentsBefore ?? []) {
|
|
1197
|
+
emitComment(c, ctx);
|
|
1198
|
+
if (c.style === "line") newLine(ctx);
|
|
1199
|
+
else ctx.srcBuilder.appendNext(" ");
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
function emitExpressionCore(e, ctx) {
|
|
1203
|
+
const builder = ctx.srcBuilder;
|
|
1204
|
+
switch (e.kind) {
|
|
1205
|
+
case "literal":
|
|
1206
|
+
builder.add(e.value, e.start, e.end);
|
|
1207
|
+
return;
|
|
1208
|
+
case "ref":
|
|
1209
|
+
emitRefIdent(e, ctx);
|
|
1210
|
+
return;
|
|
1211
|
+
case "type":
|
|
1212
|
+
emitTypeRef(e, ctx);
|
|
1213
|
+
return;
|
|
1214
|
+
case "binary-expression": {
|
|
1215
|
+
const [start, end] = e.operator.span;
|
|
1216
|
+
emitExpression(e.left, ctx);
|
|
1217
|
+
builder.add(` ${e.operator.value} `, start, end);
|
|
1218
|
+
emitExpression(e.right, ctx);
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
case "unary-expression": {
|
|
1222
|
+
const { value, start, end } = e.operator;
|
|
1223
|
+
builder.add(value, start, end);
|
|
1224
|
+
emitExpression(e.expression, ctx);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
case "parenthesized-expression":
|
|
1228
|
+
builder.appendNext("(");
|
|
1229
|
+
emitExpression(e.expression, ctx);
|
|
1230
|
+
builder.appendNext(")");
|
|
1231
|
+
return;
|
|
1232
|
+
case "call-expression":
|
|
1233
|
+
emitExpression(e.function, ctx);
|
|
1234
|
+
if (e.templateArgs) emitTemplateArgs(e.templateArgs, ctx);
|
|
1235
|
+
builder.appendNext("(");
|
|
1236
|
+
e.arguments.forEach((arg, i) => {
|
|
1237
|
+
if (i > 0) builder.appendNext(", ");
|
|
1238
|
+
emitExpression(arg, ctx);
|
|
1239
|
+
});
|
|
1240
|
+
builder.appendNext(")");
|
|
1241
|
+
return;
|
|
1242
|
+
case "component-expression":
|
|
1243
|
+
emitExpression(e.base, ctx);
|
|
1244
|
+
builder.appendNext("[");
|
|
1245
|
+
emitExpression(e.access, ctx);
|
|
1246
|
+
builder.appendNext("]");
|
|
1247
|
+
return;
|
|
1248
|
+
case "component-member-expression":
|
|
1249
|
+
emitExpression(e.base, ctx);
|
|
1250
|
+
builder.add("." + e.access.name, e.access.start, e.access.end);
|
|
1251
|
+
return;
|
|
1252
|
+
default: assertUnreachable(e);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
/** Trailing comments on an inline node: a leading space, plus a newline after a
|
|
1256
|
+
* line comment so following code stays off its line. */
|
|
1257
|
+
function emitInlineTrailing(e, ctx) {
|
|
1258
|
+
for (const c of e.commentsAfter ?? []) {
|
|
1259
|
+
ctx.srcBuilder.appendNext(" ");
|
|
1260
|
+
emitComment(c, ctx);
|
|
1261
|
+
if (c.style === "line") newLine(ctx);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
/** Leading comments: each on its own indented line above the element. */
|
|
1265
|
+
function emitLeadingComments(e, ctx) {
|
|
1266
|
+
if (!e.commentsBefore) return;
|
|
1267
|
+
for (const c of e.commentsBefore) {
|
|
1268
|
+
if (c.blankBefore) ctx.srcBuilder.addNl();
|
|
1269
|
+
newLine(ctx);
|
|
1270
|
+
emitComment(c, ctx);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
/** Start a fresh line at the current indent. */
|
|
1274
|
+
function newLine(ctx) {
|
|
1275
|
+
ctx.srcBuilder.addNl();
|
|
1276
|
+
if (ctx.indent > 0) ctx.srcBuilder.appendNext(" ".repeat(ctx.indent));
|
|
1277
|
+
}
|
|
1278
|
+
/** Emit a `{ ... }` block, one statement per indented line. A block with no
|
|
1279
|
+
* statements collapses to `{ }` unless it holds dangling inner comments. */
|
|
1280
|
+
function emitBlock(e, ctx) {
|
|
1281
|
+
emitAttributes(e.attributes, ctx);
|
|
1282
|
+
const stmts = filterValidElements(e.body, ctx.conditions);
|
|
1283
|
+
if (stmts.length === 0 && !e.innerComments?.length) {
|
|
1284
|
+
ctx.srcBuilder.appendNext("{ }");
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
ctx.srcBuilder.appendNext("{");
|
|
1288
|
+
const inner = childIndent(ctx);
|
|
1289
|
+
for (const comment of e.innerComments ?? []) {
|
|
1290
|
+
newLine(inner);
|
|
1291
|
+
emitComment(comment, inner);
|
|
1292
|
+
}
|
|
1293
|
+
for (const stmt of stmts) emitStatement(stmt, inner);
|
|
1294
|
+
newLine(ctx);
|
|
1295
|
+
ctx.srcBuilder.appendNext("}");
|
|
1296
|
+
}
|
|
1297
|
+
/** Emit a comma-separated template argument list: <a, b, c>. */
|
|
1298
|
+
function emitTemplateArgs(args, ctx) {
|
|
1299
|
+
ctx.srcBuilder.appendNext("<");
|
|
1300
|
+
args.forEach((a, i) => {
|
|
1301
|
+
if (i > 0) ctx.srcBuilder.appendNext(", ");
|
|
1302
|
+
emitExpression(a, ctx);
|
|
1303
|
+
});
|
|
1304
|
+
ctx.srcBuilder.appendNext(">");
|
|
1305
|
+
}
|
|
1306
|
+
/** Emit a statement's syntax followed by ';' unless its kind takes none. */
|
|
1307
|
+
function emitCoreSemi(stmt, ctx) {
|
|
1308
|
+
emitStatementCore(stmt, ctx);
|
|
1309
|
+
if (!noSemicolon.has(stmt.kind)) ctx.srcBuilder.appendNext(";");
|
|
1310
|
+
}
|
|
1311
|
+
/** Emit a declaration from its typed fields, including its trailing ';':
|
|
1312
|
+
* `[attrs] var<...> name: T = init;`, `const name = init;`, `override n;`,
|
|
1313
|
+
* `alias name = T;`, `const_assert expr;`. */
|
|
1314
|
+
function emitValueDecl(e, ctx) {
|
|
1315
|
+
emitAttributes(e.attributes, ctx);
|
|
1316
|
+
const builder = ctx.srcBuilder;
|
|
1317
|
+
switch (e.kind) {
|
|
1318
|
+
case "var":
|
|
1319
|
+
case "gvar":
|
|
1320
|
+
builder.appendNext("var");
|
|
1321
|
+
if (e.template) emitVarTemplate(e.template, ctx);
|
|
1322
|
+
builder.appendNext(" ");
|
|
1323
|
+
emitTypedDecl(e.name, ctx);
|
|
1324
|
+
emitInit(e.init, ctx);
|
|
1325
|
+
break;
|
|
1326
|
+
case "let":
|
|
1327
|
+
case "const":
|
|
1328
|
+
case "override":
|
|
1329
|
+
builder.appendNext(`${e.kind} `);
|
|
1330
|
+
emitTypedDecl(e.name, ctx);
|
|
1331
|
+
emitInit(e.init, ctx);
|
|
1332
|
+
break;
|
|
1333
|
+
case "alias":
|
|
1334
|
+
builder.appendNext("alias ");
|
|
1335
|
+
emitInlineLeading(e.name, ctx);
|
|
1336
|
+
emitDeclIdent(e.name, ctx);
|
|
1337
|
+
emitInlineTrailing(e.name, ctx);
|
|
1338
|
+
builder.appendNext(" = ");
|
|
1339
|
+
emitTypeRef(e.typeRef, ctx);
|
|
1340
|
+
break;
|
|
1341
|
+
case "assert":
|
|
1342
|
+
builder.appendNext("const_assert ");
|
|
1343
|
+
emitExpression(e.expression, ctx);
|
|
1344
|
+
break;
|
|
1345
|
+
default: assertUnreachable(e);
|
|
1346
|
+
}
|
|
1347
|
+
builder.appendNext(";");
|
|
1348
|
+
}
|
|
1349
|
+
function emitComment(c, ctx) {
|
|
1350
|
+
ctx.srcBuilder.add(c.srcModule.src.slice(c.start, c.end), c.start, c.end);
|
|
1351
|
+
}
|
|
1352
|
+
function warnEmptyStruct(e) {
|
|
1353
|
+
const { name, members } = e;
|
|
1354
|
+
const condStr = members.length ? "(with current conditions)" : "";
|
|
1355
|
+
failIdentElem(name, `struct '${name.ident.originalName}' has no members ${condStr}`);
|
|
1356
|
+
}
|
|
1357
|
+
/** Whether an element carries any attached comments. */
|
|
1358
|
+
function hasComments(e) {
|
|
1359
|
+
return !!(e.commentsBefore?.length || e.commentsAfter?.length);
|
|
1360
|
+
}
|
|
1361
|
+
/** Emit one struct member on its own line: leading comments above, `name: type,`,
|
|
1362
|
+
* then trailing comments inline. */
|
|
1363
|
+
function emitMemberLine(m, ctx) {
|
|
1364
|
+
const builder = ctx.srcBuilder;
|
|
1365
|
+
for (const c of m.commentsBefore ?? []) {
|
|
1366
|
+
builder.appendNext(" ");
|
|
1367
|
+
emitComment(c, ctx);
|
|
1368
|
+
builder.addNl();
|
|
1369
|
+
}
|
|
1370
|
+
builder.appendNext(" ");
|
|
1371
|
+
emitMember(m, ctx);
|
|
1372
|
+
builder.appendNext(",");
|
|
1373
|
+
for (const c of m.commentsAfter ?? []) {
|
|
1374
|
+
builder.appendNext(" ");
|
|
1375
|
+
emitComment(c, ctx);
|
|
1376
|
+
}
|
|
1377
|
+
builder.addNl();
|
|
1378
|
+
}
|
|
1379
|
+
function emitStandardAttribute(e, ctx) {
|
|
1380
|
+
if (e.attribute.kind !== "@attribute") return;
|
|
1381
|
+
const { params } = e.attribute;
|
|
1382
|
+
if (!params || params.length === 0) {
|
|
1383
|
+
ctx.srcBuilder.add("@" + e.attribute.name, e.start, e.end);
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
ctx.srcBuilder.add("@" + e.attribute.name + "(", e.start, params[0].start);
|
|
1387
|
+
params.forEach((param, i) => {
|
|
1388
|
+
if (i > 0) ctx.srcBuilder.appendNext(", ");
|
|
1389
|
+
emitExpression(param.expression, ctx);
|
|
1390
|
+
});
|
|
1391
|
+
ctx.srcBuilder.add(")", params[params.length - 1].end, e.end);
|
|
1392
|
+
}
|
|
1393
|
+
/** A child context indented one level deeper. */
|
|
1394
|
+
function childIndent(ctx) {
|
|
1395
|
+
return {
|
|
1396
|
+
...ctx,
|
|
1397
|
+
indent: ctx.indent + 1
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
/** Emit a statement's syntax, without surrounding line breaks, ';', or comments. */
|
|
1401
|
+
function emitStatementCore(stmt, ctx) {
|
|
1402
|
+
if (stmt.kind === "var" || stmt.kind === "let" || stmt.kind === "const" || stmt.kind === "assert") {
|
|
1403
|
+
emitValueDecl(stmt, ctx);
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
if (stmt.kind !== "block") emitAttributes(stmt.attributes, ctx);
|
|
1407
|
+
const builder = ctx.srcBuilder;
|
|
1408
|
+
switch (stmt.kind) {
|
|
1409
|
+
case "block":
|
|
1410
|
+
emitBlock(stmt, ctx);
|
|
1411
|
+
return;
|
|
1412
|
+
case "if":
|
|
1413
|
+
emitIf(stmt, ctx);
|
|
1414
|
+
return;
|
|
1415
|
+
case "for":
|
|
1416
|
+
emitFor(stmt, ctx);
|
|
1417
|
+
return;
|
|
1418
|
+
case "while":
|
|
1419
|
+
emitWhile(stmt, ctx);
|
|
1420
|
+
return;
|
|
1421
|
+
case "loop":
|
|
1422
|
+
builder.appendNext("loop ");
|
|
1423
|
+
emitBlock(stmt.body, ctx);
|
|
1424
|
+
return;
|
|
1425
|
+
case "continuing":
|
|
1426
|
+
builder.appendNext("continuing ");
|
|
1427
|
+
emitBlock(stmt.body, ctx);
|
|
1428
|
+
return;
|
|
1429
|
+
case "switch":
|
|
1430
|
+
emitSwitch(stmt, ctx);
|
|
1431
|
+
return;
|
|
1432
|
+
case "return":
|
|
1433
|
+
builder.appendNext("return");
|
|
1434
|
+
if (stmt.value) {
|
|
1435
|
+
builder.appendNext(" ");
|
|
1436
|
+
emitExpression(stmt.value, ctx);
|
|
1437
|
+
}
|
|
1438
|
+
return;
|
|
1439
|
+
case "break":
|
|
1440
|
+
builder.appendNext("break");
|
|
1441
|
+
if (stmt.condition) {
|
|
1442
|
+
builder.appendNext(" if ");
|
|
1443
|
+
emitExpression(stmt.condition, ctx);
|
|
1444
|
+
}
|
|
1445
|
+
return;
|
|
1446
|
+
case "continue":
|
|
1447
|
+
builder.appendNext("continue");
|
|
1448
|
+
return;
|
|
1449
|
+
case "discard":
|
|
1450
|
+
builder.appendNext("discard");
|
|
1451
|
+
return;
|
|
1452
|
+
case "assign":
|
|
1453
|
+
emitAssign(stmt, ctx);
|
|
1454
|
+
return;
|
|
1455
|
+
case "increment":
|
|
1456
|
+
emitExpression(stmt.target, ctx);
|
|
1457
|
+
builder.appendNext("++");
|
|
1458
|
+
return;
|
|
1459
|
+
case "decrement":
|
|
1460
|
+
emitExpression(stmt.target, ctx);
|
|
1461
|
+
builder.appendNext("--");
|
|
1462
|
+
return;
|
|
1463
|
+
case "call":
|
|
1464
|
+
emitExpression(stmt.call, ctx);
|
|
1465
|
+
return;
|
|
1466
|
+
case "empty": return;
|
|
1467
|
+
default: assertUnreachable(stmt);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
/** Emit a var's `<address_space[, access_mode]>` enumerant template. */
|
|
1471
|
+
function emitVarTemplate(template, ctx) {
|
|
1472
|
+
const builder = ctx.srcBuilder;
|
|
1473
|
+
builder.appendNext("<");
|
|
1474
|
+
template.forEach((name, i) => {
|
|
1475
|
+
if (i > 0) builder.appendNext(", ");
|
|
1476
|
+
emitName(name, ctx);
|
|
1477
|
+
});
|
|
1478
|
+
builder.appendNext(">");
|
|
1479
|
+
}
|
|
1480
|
+
/** Emit a ` = init` clause, if present. */
|
|
1481
|
+
function emitInit(init, ctx) {
|
|
1482
|
+
if (!init) return;
|
|
1483
|
+
ctx.srcBuilder.appendNext(" = ");
|
|
1484
|
+
emitExpression(init, ctx);
|
|
1485
|
+
}
|
|
1486
|
+
/** if / else-if / else: a nested IfElem prints as `else if`, a BlockElem as `else`. */
|
|
1487
|
+
function emitIf(e, ctx) {
|
|
1488
|
+
const builder = ctx.srcBuilder;
|
|
1489
|
+
builder.appendNext("if ");
|
|
1490
|
+
emitExpression(e.condition, ctx);
|
|
1491
|
+
builder.appendNext(" ");
|
|
1492
|
+
emitBlock(e.body, ctx);
|
|
1493
|
+
if (e.else) {
|
|
1494
|
+
builder.appendNext(" else ");
|
|
1495
|
+
if (e.else.kind === "if") emitIf(e.else, ctx);
|
|
1496
|
+
else emitBlock(e.else, ctx);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
/** for (init; condition; update) { ... }. The init takes a ';' like any
|
|
1500
|
+
* statement; the update is the last clause before ')', so it gets none. */
|
|
1501
|
+
function emitFor(e, ctx) {
|
|
1502
|
+
const builder = ctx.srcBuilder;
|
|
1503
|
+
builder.appendNext("for (");
|
|
1504
|
+
if (e.init) emitCoreSemi(e.init, ctx);
|
|
1505
|
+
else builder.appendNext(";");
|
|
1506
|
+
builder.appendNext(" ");
|
|
1507
|
+
if (e.condition) emitExpression(e.condition, ctx);
|
|
1508
|
+
builder.appendNext("; ");
|
|
1509
|
+
if (e.update) emitStatementCore(e.update, ctx);
|
|
1510
|
+
builder.appendNext(") ");
|
|
1511
|
+
emitBlock(e.body, ctx);
|
|
1512
|
+
}
|
|
1513
|
+
function emitWhile(e, ctx) {
|
|
1514
|
+
ctx.srcBuilder.appendNext("while ");
|
|
1515
|
+
emitExpression(e.condition, ctx);
|
|
1516
|
+
ctx.srcBuilder.appendNext(" ");
|
|
1517
|
+
emitBlock(e.body, ctx);
|
|
1518
|
+
}
|
|
1519
|
+
function emitSwitch(e, ctx) {
|
|
1520
|
+
const builder = ctx.srcBuilder;
|
|
1521
|
+
builder.appendNext("switch ");
|
|
1522
|
+
emitExpression(e.selector, ctx);
|
|
1523
|
+
builder.appendNext(" ");
|
|
1524
|
+
emitAttributes(e.bodyAttributes, ctx);
|
|
1525
|
+
builder.appendNext("{");
|
|
1526
|
+
const inner = childIndent(ctx);
|
|
1527
|
+
for (const clause of filterValidElements(e.clauses, ctx.conditions)) emitSwitchClause(clause, inner);
|
|
1528
|
+
newLine(ctx);
|
|
1529
|
+
builder.appendNext("}");
|
|
1530
|
+
}
|
|
1531
|
+
/** lhs op rhs, with the phony target printed as `_`. */
|
|
1532
|
+
function emitAssign(e, ctx) {
|
|
1533
|
+
if (e.lhs.kind === "phony") ctx.srcBuilder.add("_", e.lhs.span[0], e.lhs.span[1]);
|
|
1534
|
+
else emitExpression(e.lhs, ctx);
|
|
1535
|
+
const [start, end] = e.op.span;
|
|
1536
|
+
ctx.srcBuilder.add(` ${e.op.value} `, start, end);
|
|
1537
|
+
emitExpression(e.rhs, ctx);
|
|
1080
1538
|
}
|
|
1081
1539
|
//#endregion
|
|
1082
1540
|
//#region ../wesl/src/debug/ScopeToString.ts
|
|
@@ -1188,53 +1646,184 @@ var ParseError = class extends Error {
|
|
|
1188
1646
|
}
|
|
1189
1647
|
};
|
|
1190
1648
|
//#endregion
|
|
1191
|
-
//#region ../wesl/src/parse/
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1649
|
+
//#region ../wesl/src/parse/AttachComments.ts
|
|
1650
|
+
const tab = 9;
|
|
1651
|
+
const lineFeed = 10;
|
|
1652
|
+
const verticalTab = 11;
|
|
1653
|
+
const formFeed = 12;
|
|
1654
|
+
const carriageReturn = 13;
|
|
1655
|
+
const space = 32;
|
|
1656
|
+
const nextLine = 133;
|
|
1657
|
+
const leftToRightMark = 8206;
|
|
1658
|
+
const rightToLeftMark = 8207;
|
|
1659
|
+
const lineSeparator = 8232;
|
|
1660
|
+
const paragraphSeparator = 8233;
|
|
1661
|
+
/**
|
|
1662
|
+
* Attach every recorded comment to a node in the parsed tree.
|
|
1663
|
+
*
|
|
1664
|
+
* Each comment run (a contiguous group of comments between two real tokens) is
|
|
1665
|
+
* anchored to the deepest node that contains it in a gap between children, then
|
|
1666
|
+
* distributed to the surrounding children:
|
|
1667
|
+
* - a comment that begins its own line leads the next child (`commentsBefore`);
|
|
1668
|
+
* - an inline comment trails the previous child (`commentsAfter`), unless it
|
|
1669
|
+
* hugs the next child -- only blank space and comments, no separator token,
|
|
1670
|
+
* between them -- in which case it leads the next child instead;
|
|
1671
|
+
* - comments before a closing token with no following child trail the last child,
|
|
1672
|
+
* or land in an empty block's `innerComments`.
|
|
1673
|
+
*
|
|
1674
|
+
* Descending into expressions (via {@link childElems}) means interior comments
|
|
1675
|
+
* like the one in `foo(1, /* x *\/ 2)` are preserved on the `2`, not dropped.
|
|
1676
|
+
*/
|
|
1677
|
+
function attachComments(ctx, moduleElem) {
|
|
1678
|
+
const { srcModule } = ctx.state.stable;
|
|
1679
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1680
|
+
for (const run of ctx.stream.commentRuns()) distribute(deepestContaining(moduleElem, run[0].start, runEnd(run), cache), run, srcModule, cache);
|
|
1681
|
+
}
|
|
1682
|
+
/** The deepest node whose span contains the whole [start, end) range, found by
|
|
1683
|
+
* descending into the child that brackets it. Comments live in gaps between
|
|
1684
|
+
* tokens, so the result is the node holding the gap, with the run between two
|
|
1685
|
+
* of its children. */
|
|
1686
|
+
function deepestContaining(root, start, end, cache) {
|
|
1687
|
+
let node = root;
|
|
1688
|
+
while (true) {
|
|
1689
|
+
const child = positionedChildren(node, cache).find((c) => c.start <= start && end <= c.end);
|
|
1690
|
+
if (!child) return node;
|
|
1691
|
+
node = child;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
function runEnd(run) {
|
|
1695
|
+
return run[run.length - 1].end;
|
|
1696
|
+
}
|
|
1697
|
+
/** Split a comment run between the previous child (trailing) and the next child
|
|
1698
|
+
* (leading) of its anchor, or onto the last child / inner comments when it
|
|
1699
|
+
* dangles before a closing token. */
|
|
1700
|
+
function distribute(anchor, run, srcModule, cache) {
|
|
1701
|
+
const children = positionedChildren(anchor, cache);
|
|
1702
|
+
const start = run[0].start;
|
|
1703
|
+
const end = runEnd(run);
|
|
1704
|
+
const prev = children.findLast((c) => c.end <= start);
|
|
1705
|
+
const next = children.find((c) => c.start >= end);
|
|
1706
|
+
if (!next) {
|
|
1707
|
+
if (prev) addComments(prev, "commentsAfter", run, srcModule);
|
|
1708
|
+
else if (anchor.kind === "block") anchor.innerComments = run.map((t) => makeComment(t, srcModule));
|
|
1709
|
+
else addComments(anchor, "commentsBefore", run, srcModule);
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
const split = splitPoint(prev, next, run, srcModule.src);
|
|
1713
|
+
if (prev && split > 0) addComments(prev, "commentsAfter", run.slice(0, split), srcModule);
|
|
1714
|
+
if (split < run.length) addComments(next, "commentsBefore", run.slice(split), srcModule);
|
|
1715
|
+
}
|
|
1716
|
+
function positionedChildren(node, cache) {
|
|
1717
|
+
let children = cache.get(node);
|
|
1718
|
+
if (children === void 0) {
|
|
1719
|
+
children = positioned(childElems(node));
|
|
1720
|
+
cache.set(node, children);
|
|
1721
|
+
}
|
|
1722
|
+
return children;
|
|
1723
|
+
}
|
|
1724
|
+
/** Append converted comments to an element's leading or trailing list. */
|
|
1725
|
+
function addComments(elem, field, trivia, srcModule) {
|
|
1726
|
+
const comments = trivia.map((t) => makeComment(t, srcModule));
|
|
1727
|
+
const existing = elem[field];
|
|
1728
|
+
elem[field] = existing ? [...existing, ...comments] : comments;
|
|
1729
|
+
}
|
|
1730
|
+
function makeComment(trivia, srcModule) {
|
|
1731
|
+
const { style, start, end } = trivia;
|
|
1732
|
+
const comment = {
|
|
1733
|
+
kind: "comment",
|
|
1734
|
+
style,
|
|
1220
1735
|
start,
|
|
1221
1736
|
end,
|
|
1222
1737
|
srcModule
|
|
1223
1738
|
};
|
|
1739
|
+
if (lineBreaksBefore(srcModule.src, start) >= 2) comment.blankBefore = true;
|
|
1740
|
+
return comment;
|
|
1224
1741
|
}
|
|
1225
|
-
/**
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1742
|
+
/**
|
|
1743
|
+
* Index into `run` where it flips from trailing `prev` to leading `next`:
|
|
1744
|
+
* caller attaches run[0, split) to `prev` and run[split, end) to `next`.
|
|
1745
|
+
*
|
|
1746
|
+
* The aim is to keep each comment with the code it describes, so it stays
|
|
1747
|
+
* meaningful after the tree is reordered or reformatted. That follows how people
|
|
1748
|
+
* write comments: a comment on its own line documents what comes after it, while
|
|
1749
|
+
* a comment sharing a line with code documents that code. Hence:
|
|
1750
|
+
* - with no previous child the whole run leads (split 0);
|
|
1751
|
+
* - otherwise the split is the first comment that begins its own line;
|
|
1752
|
+
* - failing that (an all-inline run), it is the start of the suffix that hugs
|
|
1753
|
+
* `next` on the same line, so the comment in `foo(1, /* x *\/ 2)` documents,
|
|
1754
|
+
* and lands on, the `2`.
|
|
1755
|
+
*/
|
|
1756
|
+
function splitPoint(prev, next, run, src) {
|
|
1757
|
+
if (!prev) return 0;
|
|
1758
|
+
const ownLine = firstOwnLine(run, src);
|
|
1759
|
+
return ownLine >= 0 ? ownLine : hugsNextStart(run, next.start, src);
|
|
1760
|
+
}
|
|
1761
|
+
/** Source-positioned children, sorted in source order. Synthetic elems (no
|
|
1762
|
+
* source position) cannot anchor comments and are dropped. Children usually
|
|
1763
|
+
* arrive already in source order, so the sort is skipped when possible.
|
|
1764
|
+
* Filtering and order-checking share one pass: this runs per node, so it's
|
|
1765
|
+
* kept deliberately lean. */
|
|
1766
|
+
function positioned(elems) {
|
|
1767
|
+
const result = [];
|
|
1768
|
+
let sorted = true;
|
|
1769
|
+
let lastStart = -1;
|
|
1770
|
+
for (const e of elems) {
|
|
1771
|
+
if (e.kind === "synthetic") continue;
|
|
1772
|
+
if (e.start < lastStart) sorted = false;
|
|
1773
|
+
lastStart = e.start;
|
|
1774
|
+
result.push(e);
|
|
1775
|
+
}
|
|
1776
|
+
if (!sorted) result.sort((a, b) => a.start - b.start);
|
|
1777
|
+
return result;
|
|
1778
|
+
}
|
|
1779
|
+
/** Count line breaks in the whitespace run immediately before `pos`, capped at 2:
|
|
1780
|
+
* callers only need none / one line break / a blank line. Scans backward over
|
|
1781
|
+
* blankspace, stopping at the first non-blankspace char, so cost is the gap
|
|
1782
|
+
* length, not the source length. `\r\n` counts as one break. */
|
|
1783
|
+
function lineBreaksBefore(src, pos) {
|
|
1784
|
+
let count = 0;
|
|
1785
|
+
for (let i = pos - 1; i >= 0; i--) {
|
|
1786
|
+
const c = src.charCodeAt(i);
|
|
1787
|
+
if (isLineBreak(c)) {
|
|
1788
|
+
if (c === lineFeed && src.charCodeAt(i - 1) === carriageReturn) i--;
|
|
1789
|
+
} else if (isInlineSpace(c)) continue;
|
|
1790
|
+
else break;
|
|
1791
|
+
if (++count >= 2) return 2;
|
|
1792
|
+
}
|
|
1793
|
+
return count;
|
|
1794
|
+
}
|
|
1795
|
+
/** Index of the first comment that begins its own line (after a line break),
|
|
1796
|
+
* or -1 if none do. */
|
|
1797
|
+
function firstOwnLine(run, src) {
|
|
1798
|
+
return run.findIndex((t) => lineBreaksBefore(src, t.start) >= 1);
|
|
1799
|
+
}
|
|
1800
|
+
/** Start index of the trailing suffix of `run` that is joined to `next` by
|
|
1801
|
+
* same-line whitespace only (no line break, no separator token between them).
|
|
1802
|
+
* Returns run.length when nothing hugs `next`. */
|
|
1803
|
+
function hugsNextStart(run, nextStart, src) {
|
|
1804
|
+
let suffixStart = run.length;
|
|
1805
|
+
let rightStart = nextStart;
|
|
1806
|
+
for (let i = run.length - 1; i >= 0; i--) {
|
|
1807
|
+
const comment = run[i];
|
|
1808
|
+
if (!sameLineGap(src, comment.end, rightStart)) break;
|
|
1809
|
+
suffixStart = i;
|
|
1810
|
+
rightStart = comment.start;
|
|
1811
|
+
}
|
|
1812
|
+
return suffixStart;
|
|
1813
|
+
}
|
|
1814
|
+
/** A WGSL line break code point. `\r\n` is two of these; callers coalesce it. */
|
|
1815
|
+
function isLineBreak(c) {
|
|
1816
|
+
return c === lineFeed || c === carriageReturn || c === verticalTab || c === formFeed || c === nextLine || c === lineSeparator || c === paragraphSeparator;
|
|
1817
|
+
}
|
|
1818
|
+
/** WGSL blankspace that stays on the same line (not a line break). */
|
|
1819
|
+
function isInlineSpace(c) {
|
|
1820
|
+
return c === space || c === tab || c === leftToRightMark || c === rightToLeftMark;
|
|
1821
|
+
}
|
|
1822
|
+
/** True when [from, to) is inline blankspace only (no line break): the two ends
|
|
1823
|
+
* sit on the same line with nothing but same-line spaces between. */
|
|
1824
|
+
function sameLineGap(src, from, to) {
|
|
1825
|
+
for (let i = from; i < to; i++) if (!isInlineSpace(src.charCodeAt(i))) return false;
|
|
1826
|
+
return true;
|
|
1238
1827
|
}
|
|
1239
1828
|
//#endregion
|
|
1240
1829
|
//#region ../wesl/src/parse/ExpressionUtil.ts
|
|
@@ -1248,9 +1837,11 @@ function makeLiteral(token) {
|
|
|
1248
1837
|
};
|
|
1249
1838
|
}
|
|
1250
1839
|
function makeUnaryOperator(token) {
|
|
1840
|
+
const [start, end] = token.span;
|
|
1251
1841
|
return {
|
|
1252
1842
|
value: token.text,
|
|
1253
|
-
|
|
1843
|
+
start,
|
|
1844
|
+
end
|
|
1254
1845
|
};
|
|
1255
1846
|
}
|
|
1256
1847
|
function makeBinaryOperator(token) {
|
|
@@ -1260,12 +1851,11 @@ function makeBinaryOperator(token) {
|
|
|
1260
1851
|
};
|
|
1261
1852
|
}
|
|
1262
1853
|
function makeUnaryExpression(operator, expr) {
|
|
1263
|
-
const [start] = operator.span;
|
|
1264
1854
|
return {
|
|
1265
1855
|
kind: "unary-expression",
|
|
1266
1856
|
operator,
|
|
1267
1857
|
expression: expr,
|
|
1268
|
-
start,
|
|
1858
|
+
start: operator.start,
|
|
1269
1859
|
end: expr.end
|
|
1270
1860
|
};
|
|
1271
1861
|
}
|
|
@@ -1326,13 +1916,16 @@ function expectWord(stream, errorMsg) {
|
|
|
1326
1916
|
stream.nextToken();
|
|
1327
1917
|
return token;
|
|
1328
1918
|
}
|
|
1329
|
-
/** Parse
|
|
1919
|
+
/** Parse a required expression, throwing if absent. */
|
|
1330
1920
|
function expectExpression(ctx, errorMsg = "Expected expression") {
|
|
1331
1921
|
const expr = parseExpression(ctx);
|
|
1332
1922
|
if (!expr) throwParseError(ctx.stream, errorMsg);
|
|
1333
|
-
if (ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
1334
1923
|
return expr;
|
|
1335
1924
|
}
|
|
1925
|
+
/** Parse an optional expression. */
|
|
1926
|
+
function parseContentExpression(ctx, opts) {
|
|
1927
|
+
return parseExpression(ctx, opts);
|
|
1928
|
+
}
|
|
1336
1929
|
/** Throw a ParseError at the current/next token position. */
|
|
1337
1930
|
function throwParseError(stream, message) {
|
|
1338
1931
|
const weslStream = stream;
|
|
@@ -1388,18 +1981,27 @@ function makeRefIdentElem(ctx, refIdent, start, end) {
|
|
|
1388
1981
|
refIdent.refIdentElem = elem;
|
|
1389
1982
|
return elem;
|
|
1390
1983
|
}
|
|
1391
|
-
|
|
1984
|
+
/** @return true if the attribute is a conditional (@if, @elif, @else) */
|
|
1985
|
+
function isConditionalAttribute(attr) {
|
|
1392
1986
|
const { kind } = attr;
|
|
1393
1987
|
return kind === "@if" || kind === "@elif" || kind === "@else";
|
|
1394
1988
|
}
|
|
1395
1989
|
/** @return true if any attribute is a conditional (@if, @elif, @else) */
|
|
1396
1990
|
function hasConditionalAttribute(attributes) {
|
|
1397
|
-
return attributes.some((attr) => isConditionalAttribute
|
|
1991
|
+
return attributes.some((attr) => isConditionalAttribute(attr.attribute));
|
|
1992
|
+
}
|
|
1993
|
+
/** The first conditional (@if, @elif, @else) attribute in the list, if any. */
|
|
1994
|
+
function conditionalAttribute(attributes) {
|
|
1995
|
+
return attributes.find((a) => isConditionalAttribute(a.attribute))?.attribute;
|
|
1398
1996
|
}
|
|
1399
1997
|
/** Attach non-empty attributes array to element. */
|
|
1400
1998
|
function attachAttributes(elem, attributes) {
|
|
1401
1999
|
if (attributes?.length) elem.attributes = attributes;
|
|
1402
2000
|
}
|
|
2001
|
+
/** Normalize an empty attribute list to undefined (the elem's "no attributes"). */
|
|
2002
|
+
function attrsOrUndef(attrs) {
|
|
2003
|
+
return attrs.length ? attrs : void 0;
|
|
2004
|
+
}
|
|
1403
2005
|
/** Link a DeclIdentElem's ident to its parent declaration. */
|
|
1404
2006
|
function linkDeclIdentElem(declIdentElem, declElem) {
|
|
1405
2007
|
declIdentElem.ident.declElem = declElem;
|
|
@@ -1542,10 +2144,7 @@ function parseIdent(ctx, conditionRef) {
|
|
|
1542
2144
|
const ident = ctx.createRefIdent(parts.join("::"));
|
|
1543
2145
|
if (conditionRef) ident.conditionRef = true;
|
|
1544
2146
|
const refIdentElem = makeRefIdentElem(ctx, ident, start, end);
|
|
1545
|
-
if (!conditionRef)
|
|
1546
|
-
ctx.saveIdent(ident);
|
|
1547
|
-
ctx.addElem(refIdentElem);
|
|
1548
|
-
}
|
|
2147
|
+
if (!conditionRef) ctx.saveIdent(ident);
|
|
1549
2148
|
return refIdentElem;
|
|
1550
2149
|
}
|
|
1551
2150
|
/** Check if token is valid as a path segment (word or allowed keyword). */
|
|
@@ -1567,19 +2166,20 @@ function parseSimpleTypeRef(ctx) {
|
|
|
1567
2166
|
if (!path) return null;
|
|
1568
2167
|
const { parts, start, end: nameEnd } = path;
|
|
1569
2168
|
const refIdent = ctx.createRefIdent(parts.join("::"));
|
|
1570
|
-
|
|
1571
|
-
const refIdentElem = makeRefIdentElem(ctx, refIdent, start, nameEnd);
|
|
2169
|
+
makeRefIdentElem(ctx, refIdent, start, nameEnd);
|
|
1572
2170
|
ctx.saveIdent(refIdent);
|
|
1573
|
-
|
|
1574
|
-
|
|
2171
|
+
return {
|
|
2172
|
+
kind: "type",
|
|
1575
2173
|
name: refIdent,
|
|
1576
|
-
templateParams: ctx.stream.nextTemplateStartToken() ? parseTemplateParams(ctx) : void 0
|
|
1577
|
-
|
|
2174
|
+
templateParams: ctx.stream.nextTemplateStartToken() ? parseTemplateParams(ctx) : void 0,
|
|
2175
|
+
start,
|
|
2176
|
+
end: ctx.stream.checkpoint()
|
|
2177
|
+
};
|
|
1578
2178
|
}
|
|
1579
2179
|
/** Parse comma-separated template parameters until closing '>'. */
|
|
1580
2180
|
function parseTemplateParams(ctx) {
|
|
1581
2181
|
const { stream } = ctx;
|
|
1582
|
-
if (consumeTemplateEnd(stream))
|
|
2182
|
+
if (consumeTemplateEnd(stream)) throwParseError(stream, "Empty template parameter list '<>'");
|
|
1583
2183
|
const params = [parseTemplateParam(ctx)];
|
|
1584
2184
|
while (stream.matchText(",")) params.push(parseTemplateParam(ctx));
|
|
1585
2185
|
if (!consumeTemplateEnd(stream)) throwParseError(stream, "Expected '>' or ',' after template parameter");
|
|
@@ -1594,8 +2194,8 @@ function consumeTemplateEnd(stream) {
|
|
|
1594
2194
|
/** Grammar: template_arg_expression : expression */
|
|
1595
2195
|
function parseTemplateParam(ctx) {
|
|
1596
2196
|
const expr = parseExpression(ctx, { inTemplate: true });
|
|
1597
|
-
if (expr)
|
|
1598
|
-
|
|
2197
|
+
if (!expr) throwParseError(ctx.stream, "Expected expression in template parameters");
|
|
2198
|
+
return expr;
|
|
1599
2199
|
}
|
|
1600
2200
|
//#endregion
|
|
1601
2201
|
//#region ../wesl/src/parse/ParseCall.ts
|
|
@@ -1606,7 +2206,7 @@ function parseTemplateParam(ctx) {
|
|
|
1606
2206
|
function parseCallSuffix(ctx, current, parseExpr) {
|
|
1607
2207
|
if (current.kind !== "ref" && current.kind !== "type") return null;
|
|
1608
2208
|
const { stream } = ctx;
|
|
1609
|
-
const templateArgs = current.kind === "
|
|
2209
|
+
const templateArgs = current.kind === "ref" ? parseCallTemplateArgs(ctx) : null;
|
|
1610
2210
|
if (!stream.matchText("(")) return null;
|
|
1611
2211
|
const args = [];
|
|
1612
2212
|
while (true) {
|
|
@@ -1771,8 +2371,7 @@ function parseTemplateElaboratedIdent(ctx, conditionRef) {
|
|
|
1771
2371
|
name: refIdent.ident,
|
|
1772
2372
|
templateParams,
|
|
1773
2373
|
start: refIdent.start,
|
|
1774
|
-
end: ctx.stream.checkpoint()
|
|
1775
|
-
contents: []
|
|
2374
|
+
end: ctx.stream.checkpoint()
|
|
1776
2375
|
};
|
|
1777
2376
|
}
|
|
1778
2377
|
/** Parse postfix operators: member access, indexing, function calls. */
|
|
@@ -1799,7 +2398,9 @@ function parseMemberAccess(ctx, base) {
|
|
|
1799
2398
|
function parseIndexAccess(ctx, base) {
|
|
1800
2399
|
const { stream } = ctx;
|
|
1801
2400
|
if (!stream.matchText("[")) return null;
|
|
1802
|
-
|
|
2401
|
+
const indexExpr = parseExpression(ctx);
|
|
2402
|
+
if (!indexExpr) throwParseError(stream, "Expected expression in array index");
|
|
2403
|
+
return makeComponentExpression(base, indexExpr, expect(stream, "]", "array index").span[1]);
|
|
1803
2404
|
}
|
|
1804
2405
|
//#endregion
|
|
1805
2406
|
//#region ../wesl/src/parse/ParseAttribute.ts
|
|
@@ -1820,6 +2421,20 @@ function parseElseAttribute(ctx) {
|
|
|
1820
2421
|
function parseElifAttribute(ctx) {
|
|
1821
2422
|
return parseConditionalAttribute(ctx, "elif", makeElifAttribute);
|
|
1822
2423
|
}
|
|
2424
|
+
/** Parse WESL conditional attributes (@if, @elif, @else) */
|
|
2425
|
+
function parseWeslConditional(ctx) {
|
|
2426
|
+
const { stream } = ctx;
|
|
2427
|
+
const peeked = stream.peek();
|
|
2428
|
+
if (peeked?.text !== "@") return null;
|
|
2429
|
+
const startPos = peeked.span[0];
|
|
2430
|
+
const ifAttr = parseIfAttribute(ctx);
|
|
2431
|
+
if (ifAttr) return attributeElem(ifAttr, startPos, stream.checkpoint());
|
|
2432
|
+
const elifAttr = parseElifAttribute(ctx);
|
|
2433
|
+
if (elifAttr) return attributeElem(elifAttr, startPos, stream.checkpoint());
|
|
2434
|
+
const elseAttr = parseElseAttribute(ctx);
|
|
2435
|
+
if (elseAttr) return attributeElem(elseAttr, startPos, stream.checkpoint());
|
|
2436
|
+
return null;
|
|
2437
|
+
}
|
|
1823
2438
|
/**
|
|
1824
2439
|
* Grammar: attribute :
|
|
1825
2440
|
* '@' ident_pattern_token argument_expression_list ?
|
|
@@ -1830,15 +2445,8 @@ function parseElifAttribute(ctx) {
|
|
|
1830
2445
|
* WESL extensions: @if, @elif, @else
|
|
1831
2446
|
*/
|
|
1832
2447
|
function parseAttribute(ctx) {
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
if (!stream.matchText("@")) return null;
|
|
1836
|
-
stream.reset(startPos);
|
|
1837
|
-
const weslAttr = parseWeslConditional(ctx);
|
|
1838
|
-
if (weslAttr) return weslAttr;
|
|
1839
|
-
const stdAttr = parseStandardAttribute(ctx);
|
|
1840
|
-
if (stdAttr) return stdAttr;
|
|
1841
|
-
return null;
|
|
2448
|
+
if (ctx.stream.peek()?.text !== "@") return null;
|
|
2449
|
+
return parseWeslConditional(ctx) ?? parseStandardAttribute(ctx);
|
|
1842
2450
|
}
|
|
1843
2451
|
/** Parse `@if(expr)` or `@elif(expr)` conditional attributes. */
|
|
1844
2452
|
function parseConditionalAttribute(ctx, keyword, makeAttr) {
|
|
@@ -1847,13 +2455,15 @@ function parseConditionalAttribute(ctx, keyword, makeAttr) {
|
|
|
1847
2455
|
if (!stream.matchSequence("@", keyword)) return null;
|
|
1848
2456
|
expect(stream, "(", `@${keyword}`);
|
|
1849
2457
|
const expr = parseExpression(ctx, true);
|
|
1850
|
-
if (!expr)
|
|
2458
|
+
if (!expr) throwParseError(stream, `Expected expression after @${keyword}(`);
|
|
1851
2459
|
stream.matchText(",");
|
|
1852
2460
|
expect(stream, ")", `@${keyword} expression`);
|
|
1853
|
-
return makeAttr(
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
2461
|
+
return makeAttr({
|
|
2462
|
+
kind: "translate-time-expression",
|
|
2463
|
+
expression: expr,
|
|
2464
|
+
start: startPos,
|
|
2465
|
+
end: stream.checkpoint()
|
|
2466
|
+
});
|
|
1857
2467
|
}
|
|
1858
2468
|
function makeIfAttribute(param) {
|
|
1859
2469
|
return {
|
|
@@ -1870,19 +2480,13 @@ function makeElifAttribute(param) {
|
|
|
1870
2480
|
param
|
|
1871
2481
|
};
|
|
1872
2482
|
}
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
if (ifAttr) return attributeElem(ifAttr, startPos, stream.checkpoint());
|
|
1881
|
-
const elifAttr = parseElifAttribute(ctx);
|
|
1882
|
-
if (elifAttr) return attributeElem(elifAttr, startPos, stream.checkpoint());
|
|
1883
|
-
const elseAttr = parseElseAttribute(ctx);
|
|
1884
|
-
if (elseAttr) return attributeElem(elseAttr, startPos, stream.checkpoint());
|
|
1885
|
-
return null;
|
|
2483
|
+
function attributeElem(attribute, start, end) {
|
|
2484
|
+
return {
|
|
2485
|
+
kind: "attribute",
|
|
2486
|
+
attribute,
|
|
2487
|
+
start,
|
|
2488
|
+
end
|
|
2489
|
+
};
|
|
1886
2490
|
}
|
|
1887
2491
|
/** Parse a standard attribute (not @if/@elif/@else) */
|
|
1888
2492
|
function parseStandardAttribute(ctx) {
|
|
@@ -1892,7 +2496,7 @@ function parseStandardAttribute(ctx) {
|
|
|
1892
2496
|
if (!atToken) return null;
|
|
1893
2497
|
const startPos = atToken.span[0];
|
|
1894
2498
|
const nameToken = stream.peek();
|
|
1895
|
-
if (
|
|
2499
|
+
if (nameToken?.kind !== "word" && nameToken?.kind !== "keyword") {
|
|
1896
2500
|
stream.reset(resetPos);
|
|
1897
2501
|
return null;
|
|
1898
2502
|
}
|
|
@@ -1915,22 +2519,6 @@ function parseStandardAttribute(ctx) {
|
|
|
1915
2519
|
params
|
|
1916
2520
|
}, startPos, stream.checkpoint());
|
|
1917
2521
|
}
|
|
1918
|
-
function makeTranslateTimeExpressionElem(args) {
|
|
1919
|
-
return {
|
|
1920
|
-
kind: "translate-time-expression",
|
|
1921
|
-
expression: args.value,
|
|
1922
|
-
span: args.span
|
|
1923
|
-
};
|
|
1924
|
-
}
|
|
1925
|
-
function attributeElem(attribute, start, end) {
|
|
1926
|
-
return {
|
|
1927
|
-
kind: "attribute",
|
|
1928
|
-
attribute,
|
|
1929
|
-
start,
|
|
1930
|
-
end,
|
|
1931
|
-
contents: []
|
|
1932
|
-
};
|
|
1933
|
-
}
|
|
1934
2522
|
function parseBuiltinAttribute(ctx, startPos) {
|
|
1935
2523
|
const { stream } = ctx;
|
|
1936
2524
|
expect(stream, "(", "@builtin");
|
|
@@ -1951,9 +2539,6 @@ function parseInterpolateAttribute(ctx, startPos) {
|
|
|
1951
2539
|
params
|
|
1952
2540
|
}, startPos, stream.checkpoint());
|
|
1953
2541
|
}
|
|
1954
|
-
function parseNameElem(ctx) {
|
|
1955
|
-
return makeNameElem(expectWord(ctx.stream, "Expected identifier"));
|
|
1956
|
-
}
|
|
1957
2542
|
/** @diagnostic(severity, rule) or @diagnostic(severity, namespace.rule) */
|
|
1958
2543
|
function parseDiagnosticAttribute(ctx, startPos) {
|
|
1959
2544
|
const { stream } = ctx;
|
|
@@ -1975,78 +2560,20 @@ function parseDiagnosticAttribute(ctx, startPos) {
|
|
|
1975
2560
|
function parseAttributeParams(ctx) {
|
|
1976
2561
|
return parseCommaList(ctx, parseAttrParam);
|
|
1977
2562
|
}
|
|
2563
|
+
function parseNameElem(ctx) {
|
|
2564
|
+
return makeNameElem(expectWord(ctx.stream, "Expected identifier"));
|
|
2565
|
+
}
|
|
1978
2566
|
function parseAttrParam(ctx) {
|
|
1979
2567
|
const { stream } = ctx;
|
|
1980
2568
|
const start = stream.checkpoint();
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
const end = stream.checkpoint();
|
|
2569
|
+
const expression = parseExpression(ctx);
|
|
2570
|
+
if (!expression) throwParseError(stream, "Expected attribute parameter");
|
|
1984
2571
|
return {
|
|
1985
2572
|
kind: "expression",
|
|
2573
|
+
expression,
|
|
1986
2574
|
start,
|
|
1987
|
-
end,
|
|
1988
|
-
contents: finishContents(ctx, start, end)
|
|
1989
|
-
};
|
|
1990
|
-
}
|
|
1991
|
-
//#endregion
|
|
1992
|
-
//#region ../wesl/src/parse/ParseDirective.ts
|
|
1993
|
-
/** Grammar: global_directive : diagnostic_directive | enable_directive | requires_directive */
|
|
1994
|
-
function parseDirective(ctx) {
|
|
1995
|
-
const { stream } = ctx;
|
|
1996
|
-
const startPos = stream.checkpoint();
|
|
1997
|
-
const attributes = parseAttributeList(ctx);
|
|
1998
|
-
const attrs = attributes.length > 0 ? attributes : void 0;
|
|
1999
|
-
const result = parseExtensionDirective(ctx, "enable", attrs) || parseExtensionDirective(ctx, "requires", attrs) || parseDiagnosticDirective(ctx, attrs);
|
|
2000
|
-
if (!result) stream.reset(startPos);
|
|
2001
|
-
return result;
|
|
2002
|
-
}
|
|
2003
|
-
/** Grammar: enable_directive | requires_directive : keyword extension_list ';' */
|
|
2004
|
-
function parseExtensionDirective(ctx, keyword, attributes) {
|
|
2005
|
-
const { stream } = ctx;
|
|
2006
|
-
const token = stream.matchText(keyword);
|
|
2007
|
-
if (!token) return null;
|
|
2008
|
-
const extensions = parseCommaList(ctx, parseDirectiveName);
|
|
2009
|
-
expect(stream, ";", `${keyword} directive`);
|
|
2010
|
-
return makeDirectiveElem({
|
|
2011
|
-
kind: keyword,
|
|
2012
|
-
extensions
|
|
2013
|
-
}, token, stream, attributes);
|
|
2014
|
-
}
|
|
2015
|
-
function parseDirectiveName(ctx) {
|
|
2016
|
-
return makeNameElem(expectWord(ctx.stream, "Expected identifier in name list"));
|
|
2017
|
-
}
|
|
2018
|
-
/**
|
|
2019
|
-
* Grammar: diagnostic_directive : 'diagnostic' diagnostic_control ';'
|
|
2020
|
-
* Grammar: diagnostic_control : '(' severity_control_name ',' diagnostic_rule_name ',' ? ')'
|
|
2021
|
-
*/
|
|
2022
|
-
function parseDiagnosticDirective(ctx, attributes) {
|
|
2023
|
-
const { stream } = ctx;
|
|
2024
|
-
const token = stream.matchText("diagnostic");
|
|
2025
|
-
if (!token) return null;
|
|
2026
|
-
expect(stream, "(", "diagnostic");
|
|
2027
|
-
const severity = makeNameElem(expectWord(stream, "Expected severity in diagnostic"));
|
|
2028
|
-
expect(stream, ",", "diagnostic severity");
|
|
2029
|
-
const ruleName = makeNameElem(expectWord(stream, "Expected rule name in diagnostic"));
|
|
2030
|
-
let subrule = null;
|
|
2031
|
-
if (stream.matchText(".")) subrule = makeNameElem(expectWord(stream, "Expected subrule name after '.'"));
|
|
2032
|
-
stream.matchText(",");
|
|
2033
|
-
expect(stream, ")", "diagnostic rule");
|
|
2034
|
-
expect(stream, ";", "diagnostic directive");
|
|
2035
|
-
return makeDirectiveElem({
|
|
2036
|
-
kind: "diagnostic",
|
|
2037
|
-
severity,
|
|
2038
|
-
rule: [ruleName, subrule]
|
|
2039
|
-
}, token, stream, attributes);
|
|
2040
|
-
}
|
|
2041
|
-
function makeDirectiveElem(directive, token, stream, attributes) {
|
|
2042
|
-
const elem = {
|
|
2043
|
-
kind: "directive",
|
|
2044
|
-
directive,
|
|
2045
|
-
start: attributes?.[0]?.start ?? token.span[0],
|
|
2046
2575
|
end: stream.checkpoint()
|
|
2047
2576
|
};
|
|
2048
|
-
attachAttributes(elem, attributes);
|
|
2049
|
-
return elem;
|
|
2050
2577
|
}
|
|
2051
2578
|
//#endregion
|
|
2052
2579
|
//#region ../wesl/src/parse/ParseControlFlow.ts
|
|
@@ -2057,37 +2584,50 @@ function makeDirectiveElem(directive, token, stream, attributes) {
|
|
|
2057
2584
|
function parseIfStatement(ctx, attributes) {
|
|
2058
2585
|
const startPos = beginStatement(ctx, "if", attributes);
|
|
2059
2586
|
if (startPos === null) return null;
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2587
|
+
return finishStatement("if", startPos, ctx, {
|
|
2588
|
+
condition: expectExpression(ctx, "Expected condition after 'if'"),
|
|
2589
|
+
body: expectCompound(ctx, "Expected '{' after if condition"),
|
|
2590
|
+
else: parseElseChain(ctx)
|
|
2591
|
+
}, attributes);
|
|
2065
2592
|
}
|
|
2066
2593
|
/** Grammar: switch_statement : attribute* 'switch' expression switch_body */
|
|
2067
2594
|
function parseSwitchStatement(ctx, attributes) {
|
|
2068
2595
|
const startPos = beginStatement(ctx, "switch", attributes);
|
|
2069
2596
|
if (startPos === null) return null;
|
|
2070
|
-
expectExpression(ctx, "Expected expression after 'switch'");
|
|
2071
|
-
expectSwitchClauses(ctx);
|
|
2072
|
-
return
|
|
2597
|
+
const selector = expectExpression(ctx, "Expected expression after 'switch'");
|
|
2598
|
+
const { bodyAttributes, clauses } = expectSwitchClauses(ctx);
|
|
2599
|
+
return finishStatement("switch", startPos, ctx, {
|
|
2600
|
+
selector,
|
|
2601
|
+
clauses,
|
|
2602
|
+
bodyAttributes
|
|
2603
|
+
}, attributes);
|
|
2073
2604
|
}
|
|
2074
2605
|
/**
|
|
2075
2606
|
* Grammar: else_if_clause : 'else' 'if' expression compound_statement
|
|
2076
2607
|
* Grammar: else_clause : 'else' compound_statement
|
|
2608
|
+
*
|
|
2609
|
+
* An else-if nests as an IfElem in the outer if's `else` field; a plain else is
|
|
2610
|
+
* a BlockElem. Emit and the AST dump read these typed fields.
|
|
2077
2611
|
*/
|
|
2078
2612
|
function parseElseChain(ctx) {
|
|
2079
2613
|
const { stream } = ctx;
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2614
|
+
const elseToken = stream.matchText("else");
|
|
2615
|
+
if (!elseToken) return void 0;
|
|
2616
|
+
if (stream.matchText("if")) {
|
|
2617
|
+
const condition = expectExpression(ctx, "Expected expression after 'else if'");
|
|
2618
|
+
const body = expectCompound(ctx, "Expected '{' after else if");
|
|
2619
|
+
const elseBranch = parseElseChain(ctx);
|
|
2620
|
+
const end = stream.checkpoint();
|
|
2621
|
+
return {
|
|
2622
|
+
kind: "if",
|
|
2623
|
+
condition,
|
|
2624
|
+
body,
|
|
2625
|
+
else: elseBranch,
|
|
2626
|
+
start: elseToken.span[0],
|
|
2627
|
+
end
|
|
2628
|
+
};
|
|
2090
2629
|
}
|
|
2630
|
+
return expectCompound(ctx, "Expected '{' after else");
|
|
2091
2631
|
}
|
|
2092
2632
|
/**
|
|
2093
2633
|
* Grammar: switch_body : attribute* '{' switch_clause+ '}'
|
|
@@ -2097,27 +2637,43 @@ function parseElseChain(ctx) {
|
|
|
2097
2637
|
*/
|
|
2098
2638
|
function expectSwitchClauses(ctx) {
|
|
2099
2639
|
const { stream } = ctx;
|
|
2100
|
-
parseAttributeList(ctx);
|
|
2640
|
+
const bodyAttrs = parseAttributeList(ctx);
|
|
2101
2641
|
expect(stream, "{", "switch expression");
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2642
|
+
const clauses = [];
|
|
2643
|
+
while (!stream.matchText("}")) clauses.push(parseSwitchClause(ctx));
|
|
2644
|
+
return {
|
|
2645
|
+
bodyAttributes: attrsOrUndef(bodyAttrs),
|
|
2646
|
+
clauses
|
|
2647
|
+
};
|
|
2648
|
+
}
|
|
2649
|
+
/** Parse one 'case'/'default' clause (the keyword has not yet been consumed). */
|
|
2650
|
+
function parseSwitchClause(ctx) {
|
|
2651
|
+
const { stream } = ctx;
|
|
2652
|
+
const attrs = attrsOrUndef(parseAttributeList(ctx));
|
|
2653
|
+
const caseTok = stream.matchText("case");
|
|
2654
|
+
const keyword = caseTok ?? stream.matchText("default");
|
|
2655
|
+
if (!keyword) throwParseError(stream, "Expected 'case', 'default', or '}' in switch");
|
|
2656
|
+
const clauseStart = getStartWithAttributes(attrs, keyword.span[0]);
|
|
2657
|
+
let selectors;
|
|
2658
|
+
let body;
|
|
2659
|
+
if (caseTok) {
|
|
2660
|
+
selectors = parseCaseSelectors(ctx);
|
|
2661
|
+
body = parseCaseBody(ctx, "Expected '{' after case value");
|
|
2662
|
+
} else {
|
|
2663
|
+
selectors = ["default"];
|
|
2664
|
+
body = parseCaseBody(ctx, "Expected '{' after 'default'");
|
|
2114
2665
|
}
|
|
2666
|
+
return finishStatement("switch-clause", clauseStart, ctx, {
|
|
2667
|
+
selectors,
|
|
2668
|
+
body
|
|
2669
|
+
}, attrs);
|
|
2115
2670
|
}
|
|
2116
2671
|
/** Grammar: case_selectors : case_selector (',' case_selector)* ','? */
|
|
2117
2672
|
function parseCaseSelectors(ctx) {
|
|
2118
2673
|
const { stream } = ctx;
|
|
2119
|
-
expectExpression(ctx, "Expected expression after 'case'");
|
|
2120
|
-
while (stream.matchText(",")) expectExpression(ctx, "Expected expression after ',' in case values");
|
|
2674
|
+
const selectors = [expectExpression(ctx, "Expected expression after 'case'")];
|
|
2675
|
+
while (stream.matchText(",")) selectors.push(expectExpression(ctx, "Expected expression after ',' in case values"));
|
|
2676
|
+
return selectors;
|
|
2121
2677
|
}
|
|
2122
2678
|
/**
|
|
2123
2679
|
* Grammar: case_clause : 'case' case_selectors ':'? compound_statement
|
|
@@ -2125,16 +2681,15 @@ function parseCaseSelectors(ctx) {
|
|
|
2125
2681
|
*/
|
|
2126
2682
|
function parseCaseBody(ctx, errorMsg) {
|
|
2127
2683
|
ctx.stream.matchText(":");
|
|
2128
|
-
const
|
|
2129
|
-
const body = parseCompoundStatement(ctx, bodyAttrs.length > 0 ? bodyAttrs : void 0);
|
|
2684
|
+
const body = parseCompoundStatement(ctx, attrsOrUndef(parseAttributeList(ctx)));
|
|
2130
2685
|
if (!body) throwParseError(ctx.stream, errorMsg);
|
|
2131
|
-
|
|
2686
|
+
return body;
|
|
2132
2687
|
}
|
|
2133
2688
|
//#endregion
|
|
2134
2689
|
//#region ../wesl/src/parse/ParseValueDeclaration.ts
|
|
2135
2690
|
/** Grammar: 'const' optionally_typed_ident '=' expression (global or local) */
|
|
2136
2691
|
function parseConstDecl(ctx, attributes) {
|
|
2137
|
-
return parseValueDecl(ctx, "const", true, isModuleScope(
|
|
2692
|
+
return parseValueDecl(ctx, "const", true, ctx.isModuleScope(), attributes);
|
|
2138
2693
|
}
|
|
2139
2694
|
/** Grammar: 'override' optionally_typed_ident ( '=' expression )? */
|
|
2140
2695
|
function parseOverrideDecl(ctx, attributes) {
|
|
@@ -2145,20 +2700,16 @@ function parseTypedDecl(ctx, isGlobal = true) {
|
|
|
2145
2700
|
const nameToken = ctx.stream.matchKind("word");
|
|
2146
2701
|
if (!nameToken) return null;
|
|
2147
2702
|
const start = nameToken.span[0];
|
|
2148
|
-
beginElem(ctx, "typeDecl");
|
|
2149
2703
|
const decl = createDeclIdentElem(ctx, nameToken, isGlobal);
|
|
2150
|
-
ctx.addElem(decl);
|
|
2151
2704
|
ctx.saveIdent(decl.ident);
|
|
2152
2705
|
const { typeRef, typeScope } = parseOptionalType(ctx);
|
|
2153
|
-
const end = ctx.stream.checkpoint();
|
|
2154
2706
|
return {
|
|
2155
2707
|
kind: "typeDecl",
|
|
2156
2708
|
decl,
|
|
2157
2709
|
typeRef,
|
|
2158
2710
|
typeScope,
|
|
2159
2711
|
start,
|
|
2160
|
-
end
|
|
2161
|
-
contents: finishContents(ctx, start, end)
|
|
2712
|
+
end: ctx.stream.checkpoint()
|
|
2162
2713
|
};
|
|
2163
2714
|
}
|
|
2164
2715
|
/** Shared parser for const/override declarations. */
|
|
@@ -2168,43 +2719,29 @@ function parseValueDecl(ctx, keyword, requiresInit, isGlobal, attributes) {
|
|
|
2168
2719
|
if (!token) return null;
|
|
2169
2720
|
const startPos = getStartWithAttributes(attributes, token.span[0]);
|
|
2170
2721
|
ctx.pushScope("partial");
|
|
2171
|
-
beginElem(ctx, keyword, attributes);
|
|
2172
2722
|
const typedDecl = parseTypedDecl(ctx, isGlobal);
|
|
2173
2723
|
if (!typedDecl) throwParseError(stream, `Expected identifier after '${keyword}'`);
|
|
2174
|
-
|
|
2724
|
+
let init;
|
|
2175
2725
|
if (requiresInit) {
|
|
2176
2726
|
expect(stream, "=", `${keyword} identifier`);
|
|
2177
|
-
expectExpression(ctx);
|
|
2178
|
-
} else if (stream.matchText("=")) expectExpression(ctx);
|
|
2727
|
+
init = expectExpression(ctx);
|
|
2728
|
+
} else if (stream.matchText("=")) init = expectExpression(ctx);
|
|
2179
2729
|
expect(stream, ";", `${keyword} declaration`);
|
|
2180
|
-
const endPos = stream.checkpoint();
|
|
2181
|
-
const contents = finishContents(ctx, startPos, endPos);
|
|
2182
2730
|
typedDecl.decl.ident.dependentScope = ctx.currentScope();
|
|
2183
2731
|
ctx.popScope();
|
|
2184
|
-
const elem = {
|
|
2185
|
-
kind: keyword,
|
|
2732
|
+
const elem = finishStatement(keyword, startPos, ctx, {
|
|
2186
2733
|
name: typedDecl,
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
contents
|
|
2190
|
-
};
|
|
2191
|
-
attachAttributes(elem, attributes);
|
|
2734
|
+
init
|
|
2735
|
+
}, attributes);
|
|
2192
2736
|
linkDeclIdent(typedDecl, elem);
|
|
2193
2737
|
return elem;
|
|
2194
2738
|
}
|
|
2195
|
-
/** @return true if ctx is at module level (not inside fn/block) */
|
|
2196
|
-
function isModuleScope(ctx) {
|
|
2197
|
-
let scope = ctx.currentScope();
|
|
2198
|
-
while (scope.kind === "partial" && scope.parent) scope = scope.parent;
|
|
2199
|
-
return scope.parent === null;
|
|
2200
|
-
}
|
|
2201
2739
|
/** Parse optional ': type' annotation, managing scope for type references. */
|
|
2202
2740
|
function parseOptionalType(ctx) {
|
|
2203
2741
|
if (!ctx.stream.matchText(":")) return {};
|
|
2204
2742
|
ctx.pushScope();
|
|
2205
2743
|
const typeRef = parseSimpleTypeRef(ctx);
|
|
2206
2744
|
if (!typeRef) throwParseError(ctx.stream, "Expected type after ':'");
|
|
2207
|
-
ctx.addElem(typeRef);
|
|
2208
2745
|
const typeScope = ctx.currentScope();
|
|
2209
2746
|
ctx.popScope();
|
|
2210
2747
|
return {
|
|
@@ -2224,17 +2761,19 @@ function parseGlobalVarDecl(ctx, attributes) {
|
|
|
2224
2761
|
if (!varToken) return null;
|
|
2225
2762
|
const startPos = getStartWithAttributes(attributes, varToken.span[0]);
|
|
2226
2763
|
ctx.pushScope("partial");
|
|
2227
|
-
|
|
2228
|
-
skipTemplateList(ctx);
|
|
2764
|
+
const template = parseTemplateList(ctx);
|
|
2229
2765
|
const typedDecl = parseTypedDecl(ctx);
|
|
2230
2766
|
if (!typedDecl) throwParseError(stream, "Expected identifier after 'var'");
|
|
2231
|
-
|
|
2232
|
-
if (stream.matchText("=")) expectExpression(ctx);
|
|
2767
|
+
let init;
|
|
2768
|
+
if (stream.matchText("=")) init = expectExpression(ctx);
|
|
2233
2769
|
expect(stream, ";", "var declaration");
|
|
2234
2770
|
typedDecl.decl.ident.dependentScope = ctx.currentScope();
|
|
2235
2771
|
ctx.popScope();
|
|
2236
|
-
const varElem =
|
|
2237
|
-
|
|
2772
|
+
const varElem = finishStatement("gvar", startPos, ctx, {
|
|
2773
|
+
name: typedDecl,
|
|
2774
|
+
template,
|
|
2775
|
+
init
|
|
2776
|
+
}, attributes);
|
|
2238
2777
|
linkDeclIdent(typedDecl, varElem);
|
|
2239
2778
|
return varElem;
|
|
2240
2779
|
}
|
|
@@ -2244,23 +2783,19 @@ function parseAliasDecl(ctx, attributes) {
|
|
|
2244
2783
|
const aliasToken = stream.matchText("alias");
|
|
2245
2784
|
if (!aliasToken) return null;
|
|
2246
2785
|
const startPos = getStartWithAttributes(attributes, aliasToken.span[0]);
|
|
2247
|
-
beginElem(ctx, "alias", attributes);
|
|
2248
2786
|
const declIdentElem = createDeclIdentElem(ctx, expectWord(stream, "Expected identifier after 'alias'"), true);
|
|
2249
|
-
ctx.addElem(declIdentElem);
|
|
2250
2787
|
ctx.saveIdent(declIdentElem.ident);
|
|
2251
2788
|
expect(stream, "=", "alias name");
|
|
2252
2789
|
ctx.pushScope();
|
|
2253
2790
|
const typeRef = parseSimpleTypeRef(ctx);
|
|
2254
2791
|
if (!typeRef) throwParseError(stream, "Expected type after '=' in alias declaration");
|
|
2255
|
-
ctx.addElem(typeRef);
|
|
2256
2792
|
declIdentElem.ident.dependentScope = ctx.currentScope();
|
|
2257
2793
|
ctx.popScope();
|
|
2258
2794
|
expect(stream, ";", "alias declaration");
|
|
2259
|
-
const aliasElem =
|
|
2795
|
+
const aliasElem = finishStatement("alias", startPos, ctx, {
|
|
2260
2796
|
name: declIdentElem,
|
|
2261
2797
|
typeRef
|
|
2262
|
-
});
|
|
2263
|
-
attachAttributes(aliasElem, attributes);
|
|
2798
|
+
}, attributes);
|
|
2264
2799
|
linkDeclIdentElem(declIdentElem, aliasElem);
|
|
2265
2800
|
return aliasElem;
|
|
2266
2801
|
}
|
|
@@ -2269,25 +2804,29 @@ function parseConstAssert(ctx, attributes) {
|
|
|
2269
2804
|
const assertToken = ctx.stream.matchText("const_assert");
|
|
2270
2805
|
if (!assertToken) return null;
|
|
2271
2806
|
const startPos = getStartWithAttributes(attributes, assertToken.span[0]);
|
|
2272
|
-
|
|
2273
|
-
expectExpression(ctx);
|
|
2807
|
+
const expression = expectExpression(ctx);
|
|
2274
2808
|
expect(ctx.stream, ";", "const_assert expression");
|
|
2275
|
-
|
|
2276
|
-
attachAttributes(elem, attributes);
|
|
2277
|
-
return elem;
|
|
2809
|
+
return finishStatement("assert", startPos, ctx, { expression }, attributes);
|
|
2278
2810
|
}
|
|
2279
|
-
/**
|
|
2280
|
-
|
|
2811
|
+
/**
|
|
2812
|
+
* Parse an optional var template list `<storage, read_write>`. The entries are
|
|
2813
|
+
* predeclared enumerants (address space / access mode), not user idents, so they
|
|
2814
|
+
* are captured as unbound NameElems.
|
|
2815
|
+
*/
|
|
2816
|
+
function parseTemplateList(ctx) {
|
|
2281
2817
|
const { stream } = ctx;
|
|
2282
|
-
if (!stream.nextTemplateStartToken()) return;
|
|
2818
|
+
if (!stream.nextTemplateStartToken()) return void 0;
|
|
2819
|
+
const names = [];
|
|
2283
2820
|
while (true) {
|
|
2284
2821
|
const next = stream.peek();
|
|
2285
2822
|
if (!next) throwParseError(stream, "Unclosed template in var declaration");
|
|
2286
2823
|
if (next.text.startsWith(">")) {
|
|
2287
2824
|
stream.nextTemplateEndToken();
|
|
2288
|
-
return;
|
|
2825
|
+
return names;
|
|
2289
2826
|
}
|
|
2290
|
-
stream.
|
|
2827
|
+
const word = stream.matchKind("word");
|
|
2828
|
+
if (word) names.push(makeNameElem(word));
|
|
2829
|
+
else if (!stream.matchText(",")) throwParseError(stream, "Expected ',' or '>' in var template list");
|
|
2291
2830
|
}
|
|
2292
2831
|
}
|
|
2293
2832
|
//#endregion
|
|
@@ -2309,18 +2848,20 @@ function parseVarOrLet(ctx, keyword, hasTemplate, requiresInit, attributes) {
|
|
|
2309
2848
|
const token = stream.matchText(keyword);
|
|
2310
2849
|
if (!token) return null;
|
|
2311
2850
|
const startPos = getStartWithAttributes(attributes, token.span[0]);
|
|
2312
|
-
|
|
2313
|
-
if (hasTemplate) skipTemplateList(ctx);
|
|
2851
|
+
const template = hasTemplate ? parseTemplateList(ctx) : void 0;
|
|
2314
2852
|
const typedDecl = parseTypedDecl(ctx, false);
|
|
2315
2853
|
if (!typedDecl) throwParseError(stream, `Expected identifier after '${keyword}'`);
|
|
2316
|
-
|
|
2854
|
+
let init;
|
|
2317
2855
|
if (requiresInit) {
|
|
2318
2856
|
expect(stream, "=", `${keyword} identifier (${keyword} requires initialization)`);
|
|
2319
|
-
expectExpression(ctx);
|
|
2320
|
-
} else if (stream.matchText("=")) expectExpression(ctx);
|
|
2857
|
+
init = expectExpression(ctx);
|
|
2858
|
+
} else if (stream.matchText("=")) init = expectExpression(ctx);
|
|
2321
2859
|
expect(stream, ";", `${keyword} declaration`);
|
|
2322
|
-
const elem =
|
|
2323
|
-
|
|
2860
|
+
const elem = finishStatement(keyword, startPos, ctx, {
|
|
2861
|
+
name: typedDecl,
|
|
2862
|
+
init
|
|
2863
|
+
}, attributes);
|
|
2864
|
+
if (template && elem.kind === "var") elem.template = template;
|
|
2324
2865
|
linkDeclIdent(typedDecl, elem);
|
|
2325
2866
|
return elem;
|
|
2326
2867
|
}
|
|
@@ -2349,17 +2890,42 @@ const assignmentOps = new Set([
|
|
|
2349
2890
|
function parseSimpleStatement(ctx, attributes) {
|
|
2350
2891
|
const { stream } = ctx;
|
|
2351
2892
|
const startPos = getStartWithAttributes(attributes, stream.checkpoint());
|
|
2352
|
-
return parseReturnStmt(ctx, startPos, attributes) || parseBreakStmt(ctx, startPos, attributes) ||
|
|
2893
|
+
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);
|
|
2894
|
+
}
|
|
2895
|
+
/** Match an assignment operator, capturing its value and span. */
|
|
2896
|
+
function parseAssignmentOp(stream) {
|
|
2897
|
+
const token = stream.nextIf(({ text }) => assignmentOps.has(text));
|
|
2898
|
+
if (!token) return null;
|
|
2899
|
+
return {
|
|
2900
|
+
value: token.text,
|
|
2901
|
+
span: token.span
|
|
2902
|
+
};
|
|
2903
|
+
}
|
|
2904
|
+
/** Match '++' or '--', returning its text and span (or null if absent). */
|
|
2905
|
+
function parseIncDecOp(stream) {
|
|
2906
|
+
const token = stream.nextIf(({ text }) => text === "++" || text === "--");
|
|
2907
|
+
if (!token) return null;
|
|
2908
|
+
return {
|
|
2909
|
+
op: token.text,
|
|
2910
|
+
span: token.span
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
/** Grammar: ( '=' | compound_assignment_operator ) expression. Returns op + rhs. */
|
|
2914
|
+
function parseAssignmentRhs(ctx) {
|
|
2915
|
+
const op = parseAssignmentOp(ctx.stream);
|
|
2916
|
+
if (!op) return null;
|
|
2917
|
+
return {
|
|
2918
|
+
op,
|
|
2919
|
+
rhs: expectExpression(ctx, "Expected expression after assignment operator")
|
|
2920
|
+
};
|
|
2353
2921
|
}
|
|
2354
2922
|
/** Grammar: return_statement : 'return' expression? ';' */
|
|
2355
2923
|
function parseReturnStmt(ctx, startPos, attributes) {
|
|
2356
2924
|
const { stream } = ctx;
|
|
2357
2925
|
if (!stream.matchText("return")) return null;
|
|
2358
|
-
|
|
2359
|
-
const expr = parseExpression(ctx);
|
|
2360
|
-
if (expr && ctx.options.preserveExpressions) ctx.addElem(expr);
|
|
2926
|
+
const value = parseContentExpression(ctx) ?? void 0;
|
|
2361
2927
|
expect(stream, ";", "return statement");
|
|
2362
|
-
return
|
|
2928
|
+
return finishStatement("return", startPos, ctx, { value }, attributes);
|
|
2363
2929
|
}
|
|
2364
2930
|
/**
|
|
2365
2931
|
* Grammar: break_statement : 'break' ';'
|
|
@@ -2368,70 +2934,79 @@ function parseReturnStmt(ctx, startPos, attributes) {
|
|
|
2368
2934
|
function parseBreakStmt(ctx, startPos, attributes) {
|
|
2369
2935
|
const { stream } = ctx;
|
|
2370
2936
|
if (!stream.matchText("break")) return null;
|
|
2371
|
-
|
|
2372
|
-
if (stream.matchText("if")) expectExpression(ctx, "Expected condition after 'break if'");
|
|
2937
|
+
let condition;
|
|
2938
|
+
if (stream.matchText("if")) condition = expectExpression(ctx, "Expected condition after 'break if'");
|
|
2373
2939
|
expect(stream, ";", "break statement");
|
|
2374
|
-
return
|
|
2940
|
+
return finishStatement("break", startPos, ctx, { condition }, attributes);
|
|
2941
|
+
}
|
|
2942
|
+
/** Grammar: continue_statement : 'continue' ';' */
|
|
2943
|
+
function parseContinueStmt(ctx, startPos, attributes) {
|
|
2944
|
+
const { stream } = ctx;
|
|
2945
|
+
if (!stream.matchText("continue")) return null;
|
|
2946
|
+
expect(stream, ";", "continue statement");
|
|
2947
|
+
return finishStatement("continue", startPos, ctx, {}, attributes);
|
|
2375
2948
|
}
|
|
2376
|
-
/** Grammar:
|
|
2377
|
-
function
|
|
2949
|
+
/** Grammar: 'discard' ';' */
|
|
2950
|
+
function parseDiscardStmt(ctx, startPos, attributes) {
|
|
2378
2951
|
const { stream } = ctx;
|
|
2379
|
-
if (!stream.matchText(
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
return finishBlockStatement(startPos, ctx, attributes);
|
|
2952
|
+
if (!stream.matchText("discard")) return null;
|
|
2953
|
+
expect(stream, ";", "discard statement");
|
|
2954
|
+
return finishStatement("discard", startPos, ctx, {}, attributes);
|
|
2383
2955
|
}
|
|
2384
|
-
/** Parse empty statement (just ';'). */
|
|
2956
|
+
/** Parse empty statement (just ';'). Spans the ';' so it emits no extra text. */
|
|
2385
2957
|
function parseEmptyStmt(stream, start) {
|
|
2386
2958
|
if (!stream.matchText(";")) return null;
|
|
2387
2959
|
return {
|
|
2388
|
-
kind: "
|
|
2960
|
+
kind: "empty",
|
|
2389
2961
|
start,
|
|
2390
|
-
end: stream.checkpoint()
|
|
2391
|
-
contents: []
|
|
2962
|
+
end: stream.checkpoint()
|
|
2392
2963
|
};
|
|
2393
2964
|
}
|
|
2394
2965
|
/** Grammar: assignment_statement : '_' '=' expression ';' (phony assignment) */
|
|
2395
2966
|
function parsePhonyAssignment(ctx, startPos, attributes) {
|
|
2396
2967
|
const { stream } = ctx;
|
|
2397
|
-
|
|
2398
|
-
if (!
|
|
2399
|
-
|
|
2400
|
-
|
|
2968
|
+
const underscore = stream.matchText("_");
|
|
2969
|
+
if (!underscore) return null;
|
|
2970
|
+
const lhs = {
|
|
2971
|
+
kind: "phony",
|
|
2972
|
+
span: underscore.span
|
|
2973
|
+
};
|
|
2974
|
+
const eq = stream.matchText("=");
|
|
2975
|
+
if (!eq) throwParseError(stream, "Expected '=' after '_'");
|
|
2976
|
+
const op = {
|
|
2977
|
+
value: "=",
|
|
2978
|
+
span: eq.span
|
|
2979
|
+
};
|
|
2980
|
+
const rhs = expectExpression(ctx, "Expected expression after '_ ='");
|
|
2401
2981
|
expect(stream, ";", "assignment");
|
|
2402
|
-
return
|
|
2982
|
+
return finishStatement("assign", startPos, ctx, {
|
|
2983
|
+
lhs,
|
|
2984
|
+
op,
|
|
2985
|
+
rhs
|
|
2986
|
+
}, attributes);
|
|
2403
2987
|
}
|
|
2404
|
-
/**
|
|
2405
|
-
* Parses expression statements: assignments, increments/decrements, or function calls.
|
|
2406
|
-
* Grammar: ( assignment_statement | increment_statement | decrement_statement | call_phrase ) ';'
|
|
2407
|
-
*/
|
|
2988
|
+
/** Grammar: ( assignment_statement | increment_statement | decrement_statement | call_phrase ) ';' */
|
|
2408
2989
|
function parseExpressionStmt(ctx, startPos, attributes) {
|
|
2409
2990
|
const { stream } = ctx;
|
|
2410
|
-
beginElem(ctx, "statement", attributes);
|
|
2411
2991
|
const expr = parseExpression(ctx);
|
|
2412
2992
|
if (!expr) {
|
|
2413
|
-
finishContents(ctx, startPos, startPos);
|
|
2414
2993
|
stream.reset(startPos);
|
|
2415
2994
|
return null;
|
|
2416
2995
|
}
|
|
2417
|
-
|
|
2418
|
-
if (
|
|
2996
|
+
const incDec = parseIncDecOp(stream);
|
|
2997
|
+
if (incDec) {
|
|
2998
|
+
expect(stream, ";", "expression");
|
|
2999
|
+
return finishStatement(incDec.op === "++" ? "increment" : "decrement", startPos, ctx, { target: expr }, attributes);
|
|
3000
|
+
}
|
|
3001
|
+
const assign = parseAssignmentRhs(ctx);
|
|
2419
3002
|
expect(stream, ";", "expression");
|
|
2420
|
-
return
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
function parseAssignmentRhs(ctx) {
|
|
2428
|
-
if (!parseAssignmentOperator(ctx.stream)) return false;
|
|
2429
|
-
expectExpression(ctx, "Expected expression after assignment operator");
|
|
2430
|
-
return true;
|
|
2431
|
-
}
|
|
2432
|
-
/** Grammar: increment_statement : lhs_expression '++' ; decrement_statement : lhs_expression '--' */
|
|
2433
|
-
function parseIncDecOperator(stream) {
|
|
2434
|
-
return !!stream.nextIf(({ text }) => text === "++" || text === "--");
|
|
3003
|
+
if (assign) return finishStatement("assign", startPos, ctx, {
|
|
3004
|
+
lhs: expr,
|
|
3005
|
+
op: assign.op,
|
|
3006
|
+
rhs: assign.rhs
|
|
3007
|
+
}, attributes);
|
|
3008
|
+
if (expr.kind !== "call-expression") throwParseError(stream, "Expected call, assignment, or increment");
|
|
3009
|
+
return finishStatement("call", startPos, ctx, { call: expr }, attributes);
|
|
2435
3010
|
}
|
|
2436
3011
|
//#endregion
|
|
2437
3012
|
//#region ../wesl/src/parse/ParseLoop.ts
|
|
@@ -2445,41 +3020,48 @@ function parseForStatement(ctx, attributes) {
|
|
|
2445
3020
|
if (startPos === null) return null;
|
|
2446
3021
|
ctx.pushScope();
|
|
2447
3022
|
expect(stream, "(", "'for'");
|
|
2448
|
-
parseForInit(ctx);
|
|
2449
|
-
const
|
|
2450
|
-
if (cond && ctx.options.preserveExpressions) ctx.addElem(cond);
|
|
3023
|
+
const init = parseForInit(ctx);
|
|
3024
|
+
const condition = parseContentExpression(ctx) ?? void 0;
|
|
2451
3025
|
expect(stream, ";", "for loop condition");
|
|
2452
|
-
parseForUpdate(ctx);
|
|
3026
|
+
const update = parseForUpdate(ctx);
|
|
2453
3027
|
expect(stream, ")", "for loop header");
|
|
2454
3028
|
const body = expectCompound(ctx, "Expected '{' after for loop header");
|
|
2455
|
-
ctx.addElem(body);
|
|
2456
3029
|
ctx.popScope();
|
|
2457
|
-
return
|
|
3030
|
+
return finishStatement("for", startPos, ctx, {
|
|
3031
|
+
init,
|
|
3032
|
+
condition,
|
|
3033
|
+
update,
|
|
3034
|
+
body
|
|
3035
|
+
}, attributes);
|
|
2458
3036
|
}
|
|
2459
3037
|
/** Grammar: while_statement : attribute* 'while' expression compound_statement */
|
|
2460
3038
|
function parseWhileStatement(ctx, attributes) {
|
|
2461
3039
|
const startPos = beginStatement(ctx, "while", attributes);
|
|
2462
3040
|
if (startPos === null) return null;
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
3041
|
+
return finishStatement("while", startPos, ctx, {
|
|
3042
|
+
condition: expectExpression(ctx, "Expected condition after 'while'"),
|
|
3043
|
+
body: expectCompound(ctx, "Expected '{' after while condition")
|
|
3044
|
+
}, attributes);
|
|
2467
3045
|
}
|
|
2468
3046
|
/** Grammar: loop_statement : attribute* 'loop' attribute* '{' statement* continuing_statement? '}' */
|
|
2469
3047
|
function parseLoopStatement(ctx, attributes) {
|
|
2470
3048
|
const startPos = beginStatement(ctx, "loop", attributes);
|
|
2471
3049
|
if (startPos === null) return null;
|
|
2472
3050
|
const body = expectCompound(ctx, "Expected '{' after 'loop'", true);
|
|
2473
|
-
ctx
|
|
2474
|
-
|
|
3051
|
+
return finishStatement("loop", startPos, ctx, {
|
|
3052
|
+
body,
|
|
3053
|
+
continuing: body.body.find((s) => s.kind === "continuing")
|
|
3054
|
+
}, attributes);
|
|
2475
3055
|
}
|
|
2476
3056
|
/** Grammar: continuing_statement : 'continuing' continuing_compound_statement */
|
|
2477
3057
|
function parseContinuingStatement(ctx, attributes) {
|
|
2478
|
-
const startPos = beginStatement(ctx, "continuing", attributes
|
|
3058
|
+
const startPos = beginStatement(ctx, "continuing", attributes);
|
|
2479
3059
|
if (startPos === null) return null;
|
|
2480
3060
|
const body = expectCompound(ctx, "Expected '{' after 'continuing'");
|
|
2481
|
-
ctx
|
|
2482
|
-
|
|
3061
|
+
return finishStatement("continuing", startPos, ctx, {
|
|
3062
|
+
body,
|
|
3063
|
+
breakIf: body.body.find((s) => s.kind === "break")?.condition
|
|
3064
|
+
}, attributes);
|
|
2483
3065
|
}
|
|
2484
3066
|
/** Grammar: for_init? ';'
|
|
2485
3067
|
* for_init : variable_or_value_statement | variable_updating_statement | func_call_statement
|
|
@@ -2487,19 +3069,60 @@ function parseContinuingStatement(ctx, attributes) {
|
|
|
2487
3069
|
function parseForInit(ctx) {
|
|
2488
3070
|
const { stream } = ctx;
|
|
2489
3071
|
const varDecl = parseLocalVarDecl(ctx);
|
|
2490
|
-
if (varDecl)
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
}
|
|
3072
|
+
if (varDecl) return varDecl;
|
|
3073
|
+
const expr = parseContentExpression(ctx);
|
|
3074
|
+
const update = expr ? finishForUpdate(ctx, expr) : void 0;
|
|
3075
|
+
expect(stream, ";", "for loop init");
|
|
3076
|
+
return update;
|
|
2496
3077
|
}
|
|
2497
|
-
/** Grammar: for_update : variable_updating_statement | func_call_statement
|
|
2498
|
-
* variable_updating_statement : assignment_statement | increment_statement | decrement_statement */
|
|
3078
|
+
/** Grammar: for_update : variable_updating_statement | func_call_statement */
|
|
2499
3079
|
function parseForUpdate(ctx) {
|
|
2500
|
-
const expr =
|
|
2501
|
-
if (expr
|
|
2502
|
-
|
|
3080
|
+
const expr = parseContentExpression(ctx);
|
|
3081
|
+
if (!expr) return void 0;
|
|
3082
|
+
return finishForUpdate(ctx, expr);
|
|
3083
|
+
}
|
|
3084
|
+
/**
|
|
3085
|
+
* Build a typed for-init/for-update node from an already-parsed lhs expression,
|
|
3086
|
+
* consuming its trailing operator (++/-- or an assignment + rhs).
|
|
3087
|
+
*/
|
|
3088
|
+
function finishForUpdate(ctx, expr) {
|
|
3089
|
+
const { stream } = ctx;
|
|
3090
|
+
const start = expr.start;
|
|
3091
|
+
const incDec = parseIncDecOp(stream);
|
|
3092
|
+
if (incDec) {
|
|
3093
|
+
const end = stream.checkpoint();
|
|
3094
|
+
if (incDec.op === "++") return {
|
|
3095
|
+
kind: "increment",
|
|
3096
|
+
target: expr,
|
|
3097
|
+
start,
|
|
3098
|
+
end
|
|
3099
|
+
};
|
|
3100
|
+
return {
|
|
3101
|
+
kind: "decrement",
|
|
3102
|
+
target: expr,
|
|
3103
|
+
start,
|
|
3104
|
+
end
|
|
3105
|
+
};
|
|
3106
|
+
}
|
|
3107
|
+
const assign = parseAssignmentRhs(ctx);
|
|
3108
|
+
if (assign) {
|
|
3109
|
+
const { op, rhs } = assign;
|
|
3110
|
+
return {
|
|
3111
|
+
kind: "assign",
|
|
3112
|
+
lhs: expr,
|
|
3113
|
+
op,
|
|
3114
|
+
rhs,
|
|
3115
|
+
start,
|
|
3116
|
+
end: stream.checkpoint()
|
|
3117
|
+
};
|
|
3118
|
+
}
|
|
3119
|
+
if (expr.kind === "call-expression") return {
|
|
3120
|
+
kind: "call",
|
|
3121
|
+
call: expr,
|
|
3122
|
+
start,
|
|
3123
|
+
end: stream.checkpoint()
|
|
3124
|
+
};
|
|
3125
|
+
throwParseError(stream, "Expected an assignment, increment, decrement, or call in for-clause");
|
|
2503
3126
|
}
|
|
2504
3127
|
//#endregion
|
|
2505
3128
|
//#region ../wesl/src/parse/ParseStatement.ts
|
|
@@ -2515,17 +3138,17 @@ function parseCompoundStatement(ctx, attributes, options) {
|
|
|
2515
3138
|
const brace = ctx.stream.matchText("{");
|
|
2516
3139
|
if (!brace) return null;
|
|
2517
3140
|
const startPos = getStartWithAttributes(attributes, brace.span[0]);
|
|
2518
|
-
beginElem(ctx, "statement", attributes);
|
|
2519
3141
|
const skipScope = options?.noScope || hasConditionalAttr(attributes);
|
|
2520
3142
|
if (!skipScope) ctx.pushScope();
|
|
2521
|
-
parseBlockStatements(ctx, options?.loopBody);
|
|
3143
|
+
const body = parseBlockStatements(ctx, options?.loopBody);
|
|
2522
3144
|
if (!skipScope) ctx.popScope();
|
|
2523
|
-
return
|
|
3145
|
+
return finishStatement("block", startPos, ctx, { body }, attributes);
|
|
2524
3146
|
}
|
|
2525
3147
|
/** Grammar: attribute* compound_statement (for control flow bodies) */
|
|
2526
3148
|
function expectCompound(ctx, errorMsg, loopBody) {
|
|
2527
3149
|
const attrs = parseAttributeList(ctx);
|
|
2528
|
-
const
|
|
3150
|
+
const options = loopBody ? { loopBody } : void 0;
|
|
3151
|
+
const block = parseCompoundStatement(ctx, attrsOrUndef(attrs), options);
|
|
2529
3152
|
if (!block) throwParseError(ctx.stream, errorMsg);
|
|
2530
3153
|
return block;
|
|
2531
3154
|
}
|
|
@@ -2533,16 +3156,20 @@ function expectCompound(ctx, errorMsg, loopBody) {
|
|
|
2533
3156
|
function getStartWithAttributes(attributes, keywordPos) {
|
|
2534
3157
|
return attributes?.[0]?.start ?? keywordPos;
|
|
2535
3158
|
}
|
|
2536
|
-
/** Match keyword and
|
|
2537
|
-
function beginStatement(ctx, keyword, attributes
|
|
2538
|
-
const
|
|
2539
|
-
if (!
|
|
2540
|
-
|
|
2541
|
-
beginElem(ctx, kind, attributes);
|
|
2542
|
-
return startPos;
|
|
3159
|
+
/** Match keyword and return the statement's start position (or null if no match). */
|
|
3160
|
+
function beginStatement(ctx, keyword, attributes) {
|
|
3161
|
+
const token = ctx.stream.matchText(keyword);
|
|
3162
|
+
if (!token) return null;
|
|
3163
|
+
return getStartWithAttributes(attributes, token.span[0]);
|
|
2543
3164
|
}
|
|
2544
|
-
|
|
2545
|
-
|
|
3165
|
+
/** Build a statement element from its typed fields and attach its attributes. */
|
|
3166
|
+
function finishStatement(kind, start, ctx, params, attributes) {
|
|
3167
|
+
const elem = {
|
|
3168
|
+
kind,
|
|
3169
|
+
start,
|
|
3170
|
+
end: ctx.stream.checkpoint(),
|
|
3171
|
+
...params
|
|
3172
|
+
};
|
|
2546
3173
|
attachAttributes(elem, attributes);
|
|
2547
3174
|
return elem;
|
|
2548
3175
|
}
|
|
@@ -2552,16 +3179,18 @@ function hasConditionalAttr(attributes) {
|
|
|
2552
3179
|
/** Grammar: statement* '}' (after '{' consumed). Loop bodies may end with continuing. */
|
|
2553
3180
|
function parseBlockStatements(ctx, loopBody) {
|
|
2554
3181
|
const { stream } = ctx;
|
|
3182
|
+
const body = [];
|
|
2555
3183
|
while (true) {
|
|
2556
3184
|
if (stream.matchText("}")) break;
|
|
2557
3185
|
const stmt = parseStatement(ctx);
|
|
2558
3186
|
if (!stmt) throwParseError(stream, "Expected statement or '}'");
|
|
2559
|
-
|
|
3187
|
+
body.push(stmt);
|
|
2560
3188
|
if (loopBody && stmt.kind === "continuing") {
|
|
2561
3189
|
expect(stream, "}", "continuing block");
|
|
2562
3190
|
break;
|
|
2563
3191
|
}
|
|
2564
3192
|
}
|
|
3193
|
+
return body;
|
|
2565
3194
|
}
|
|
2566
3195
|
/**
|
|
2567
3196
|
* Grammar: statement :
|
|
@@ -2582,7 +3211,6 @@ function parseStatement(ctx) {
|
|
|
2582
3211
|
}
|
|
2583
3212
|
const hasConditional = attributes.length > 0 && hasConditionalAttribute(attributes);
|
|
2584
3213
|
if (hasConditional) ctx.pushScope("partial");
|
|
2585
|
-
const attrsOrUndef = attributes.length > 0 ? attributes : void 0;
|
|
2586
3214
|
const stmt = findMap([
|
|
2587
3215
|
parseLocalVarDecl,
|
|
2588
3216
|
parseLetDecl,
|
|
@@ -2596,19 +3224,71 @@ function parseStatement(ctx) {
|
|
|
2596
3224
|
parseLoopStatement,
|
|
2597
3225
|
parseContinuingStatement,
|
|
2598
3226
|
parseSimpleStatement
|
|
2599
|
-
], (p) => p(ctx, attrsOrUndef));
|
|
2600
|
-
if (
|
|
2601
|
-
|
|
2602
|
-
|
|
3227
|
+
], (p) => p(ctx, attrsOrUndef(attributes)));
|
|
3228
|
+
if (hasConditional) {
|
|
3229
|
+
const partialScope = ctx.popScope();
|
|
3230
|
+
if (stmt) partialScope.condAttribute = conditionalAttribute(attributes);
|
|
3231
|
+
}
|
|
3232
|
+
return stmt ? stmt : null;
|
|
3233
|
+
}
|
|
3234
|
+
//#endregion
|
|
3235
|
+
//#region ../wesl/src/parse/ParseDirective.ts
|
|
3236
|
+
/** Grammar: global_directive : diagnostic_directive | enable_directive | requires_directive */
|
|
3237
|
+
function parseDirective(ctx) {
|
|
3238
|
+
const { stream } = ctx;
|
|
3239
|
+
const startPos = stream.checkpoint();
|
|
3240
|
+
const attrs = attrsOrUndef(parseAttributeList(ctx));
|
|
3241
|
+
const result = parseExtensionDirective(ctx, "enable", attrs) || parseExtensionDirective(ctx, "requires", attrs) || parseDiagnosticDirective(ctx, attrs);
|
|
3242
|
+
if (!result) stream.reset(startPos);
|
|
3243
|
+
return result;
|
|
2603
3244
|
}
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
3245
|
+
/** Grammar: enable_directive | requires_directive : keyword extension_list ';' */
|
|
3246
|
+
function parseExtensionDirective(ctx, keyword, attributes) {
|
|
3247
|
+
const { stream } = ctx;
|
|
3248
|
+
const token = stream.matchText(keyword);
|
|
3249
|
+
if (!token) return null;
|
|
3250
|
+
const extensions = parseCommaList(ctx, parseDirectiveName);
|
|
3251
|
+
expect(stream, ";", `${keyword} directive`);
|
|
3252
|
+
return makeDirectiveElem({
|
|
3253
|
+
kind: keyword,
|
|
3254
|
+
extensions
|
|
3255
|
+
}, token, stream, attributes);
|
|
3256
|
+
}
|
|
3257
|
+
/**
|
|
3258
|
+
* Grammar: diagnostic_directive : 'diagnostic' diagnostic_control ';'
|
|
3259
|
+
* Grammar: diagnostic_control : '(' severity_control_name ',' diagnostic_rule_name ',' ? ')'
|
|
3260
|
+
*/
|
|
3261
|
+
function parseDiagnosticDirective(ctx, attributes) {
|
|
3262
|
+
const { stream } = ctx;
|
|
3263
|
+
const token = stream.matchText("diagnostic");
|
|
3264
|
+
if (!token) return null;
|
|
3265
|
+
expect(stream, "(", "diagnostic");
|
|
3266
|
+
const severity = makeNameElem(expectWord(stream, "Expected severity in diagnostic"));
|
|
3267
|
+
expect(stream, ",", "diagnostic severity");
|
|
3268
|
+
const ruleName = makeNameElem(expectWord(stream, "Expected rule name in diagnostic"));
|
|
3269
|
+
let subrule = null;
|
|
3270
|
+
if (stream.matchText(".")) subrule = makeNameElem(expectWord(stream, "Expected subrule name after '.'"));
|
|
3271
|
+
stream.matchText(",");
|
|
3272
|
+
expect(stream, ")", "diagnostic rule");
|
|
3273
|
+
expect(stream, ";", "diagnostic directive");
|
|
3274
|
+
return makeDirectiveElem({
|
|
3275
|
+
kind: "diagnostic",
|
|
3276
|
+
severity,
|
|
3277
|
+
rule: [ruleName, subrule]
|
|
3278
|
+
}, token, stream, attributes);
|
|
3279
|
+
}
|
|
3280
|
+
function parseDirectiveName(ctx) {
|
|
3281
|
+
return makeNameElem(expectWord(ctx.stream, "Expected identifier in name list"));
|
|
2609
3282
|
}
|
|
2610
|
-
function
|
|
2611
|
-
|
|
3283
|
+
function makeDirectiveElem(directive, token, stream, attributes) {
|
|
3284
|
+
const elem = {
|
|
3285
|
+
kind: "directive",
|
|
3286
|
+
directive,
|
|
3287
|
+
start: getStartWithAttributes(attributes, token.span[0]),
|
|
3288
|
+
end: stream.checkpoint()
|
|
3289
|
+
};
|
|
3290
|
+
attachAttributes(elem, attributes);
|
|
3291
|
+
return elem;
|
|
2612
3292
|
}
|
|
2613
3293
|
//#endregion
|
|
2614
3294
|
//#region ../wesl/src/parse/ParseFn.ts
|
|
@@ -2645,8 +3325,7 @@ function parseFnDecl(ctx, attributes) {
|
|
|
2645
3325
|
returnType,
|
|
2646
3326
|
returnAttributes,
|
|
2647
3327
|
start: startPos,
|
|
2648
|
-
end: stream.checkpoint()
|
|
2649
|
-
contents: buildFnContents(attributes, declIdentElem, params, returnType, body)
|
|
3328
|
+
end: stream.checkpoint()
|
|
2650
3329
|
};
|
|
2651
3330
|
attachAttributes(fnElem, attributes);
|
|
2652
3331
|
linkDeclIdentElem(declIdentElem, fnElem);
|
|
@@ -2676,37 +3355,63 @@ function parseFnReturn(ctx) {
|
|
|
2676
3355
|
if (!returnType) throwParseError(stream, "Expected type after '->'");
|
|
2677
3356
|
return {
|
|
2678
3357
|
returnType,
|
|
2679
|
-
returnAttributes: attrs
|
|
3358
|
+
returnAttributes: attrsOrUndef(attrs)
|
|
2680
3359
|
};
|
|
2681
3360
|
}
|
|
2682
|
-
/** Build contents array for function element */
|
|
2683
|
-
function buildFnContents(attributes, decl, params, returnType, body) {
|
|
2684
|
-
const base = returnType ? [
|
|
2685
|
-
decl,
|
|
2686
|
-
...params,
|
|
2687
|
-
returnType,
|
|
2688
|
-
body
|
|
2689
|
-
] : [
|
|
2690
|
-
decl,
|
|
2691
|
-
...params,
|
|
2692
|
-
body
|
|
2693
|
-
];
|
|
2694
|
-
return attributes?.length ? [...attributes, ...base] : base;
|
|
2695
|
-
}
|
|
2696
3361
|
/** Grammar: param : attribute* optionally_typed_ident */
|
|
2697
3362
|
function parseFnParam(ctx) {
|
|
2698
|
-
const
|
|
3363
|
+
const attrs = parseAttributeList(ctx);
|
|
2699
3364
|
if (ctx.stream.peek()?.kind !== "word") return null;
|
|
2700
|
-
|
|
3365
|
+
const attributes = attrsOrUndef(attrs);
|
|
2701
3366
|
const name = parseTypedDecl(ctx, false);
|
|
2702
3367
|
if (!name) throw new Error("Unexpected: peek succeeded but parseTypedDecl failed");
|
|
2703
|
-
|
|
2704
|
-
const elem = finishElem("param", getStartWithAttributes(attributes, name.start), ctx, { name });
|
|
3368
|
+
const elem = finishStatement("param", getStartWithAttributes(attributes, name.start), ctx, { name }, attributes);
|
|
2705
3369
|
linkDeclIdent(name, elem);
|
|
2706
|
-
attachAttributes(elem, attributes.length > 0 ? attributes : void 0);
|
|
2707
3370
|
return elem;
|
|
2708
3371
|
}
|
|
2709
3372
|
//#endregion
|
|
3373
|
+
//#region ../wesl/src/parse/ParseDoBlock.ts
|
|
3374
|
+
/**
|
|
3375
|
+
* Grammar: do_block_decl : attribute* 'do' ident '(' param_list? ')' compound_statement
|
|
3376
|
+
*
|
|
3377
|
+
* `do` is a module-level declaration when the `doBlocks` extension is on;
|
|
3378
|
+
* otherwise it stays a reserved word. The body is parsed structurally but
|
|
3379
|
+
* inside a scope that is detached from the module scope graph, so its idents
|
|
3380
|
+
* never reach bindIdents. do blocks are module-local, resolved later by AST
|
|
3381
|
+
* name match.
|
|
3382
|
+
*/
|
|
3383
|
+
function parseDoBlock(ctx, attributes) {
|
|
3384
|
+
if (!ctx.options.weslExtensions?.doBlocks) return null;
|
|
3385
|
+
const { stream } = ctx;
|
|
3386
|
+
const doToken = stream.matchText("do");
|
|
3387
|
+
if (!doToken) return null;
|
|
3388
|
+
const startPos = getStartWithAttributes(attributes, doToken.span[0]);
|
|
3389
|
+
const name = makeNameElem(expectWord(stream, "Expected identifier after 'do'"));
|
|
3390
|
+
const parentScope = ctx.currentScope();
|
|
3391
|
+
ctx.pushScope();
|
|
3392
|
+
const doScope = ctx.currentScope();
|
|
3393
|
+
const params = parseFnParams(ctx);
|
|
3394
|
+
const body = parseFunctionBody(ctx);
|
|
3395
|
+
if (!body) throwParseError(stream, "Expected do block body");
|
|
3396
|
+
ctx.popScope();
|
|
3397
|
+
detachScope(parentScope, doScope);
|
|
3398
|
+
const doBlock = {
|
|
3399
|
+
kind: "do",
|
|
3400
|
+
name,
|
|
3401
|
+
params,
|
|
3402
|
+
body,
|
|
3403
|
+
start: startPos,
|
|
3404
|
+
end: stream.checkpoint()
|
|
3405
|
+
};
|
|
3406
|
+
attachAttributes(doBlock, attributes);
|
|
3407
|
+
return doBlock;
|
|
3408
|
+
}
|
|
3409
|
+
/** Remove a child scope from its parent so bindIdents never traverses it. */
|
|
3410
|
+
function detachScope(parent, child) {
|
|
3411
|
+
const i = parent.contents.indexOf(child);
|
|
3412
|
+
if (i >= 0) parent.contents.splice(i, 1);
|
|
3413
|
+
}
|
|
3414
|
+
//#endregion
|
|
2710
3415
|
//#region ../wesl/src/parse/ParseImport.ts
|
|
2711
3416
|
/** WESL Grammar: translation_unit : import_statement* global_directive* global_decl* */
|
|
2712
3417
|
function parseWeslImports(ctx) {
|
|
@@ -2844,52 +3549,39 @@ function parseStructDecl(ctx, attributes) {
|
|
|
2844
3549
|
const start = getStartWithAttributes(attributes, structToken.span[0]);
|
|
2845
3550
|
const identElem = createDeclIdentElem(ctx, expectWord(stream, "Expected identifier after 'struct'"), true);
|
|
2846
3551
|
ctx.saveIdent(identElem.ident);
|
|
2847
|
-
beginElem(ctx, "struct", attributes);
|
|
2848
|
-
ctx.addElem(identElem);
|
|
2849
3552
|
expect(stream, "{", "struct name");
|
|
2850
3553
|
ctx.pushScope();
|
|
2851
|
-
const members =
|
|
3554
|
+
const members = parseCommaList(ctx, parseStructMember);
|
|
2852
3555
|
identElem.ident.dependentScope = ctx.currentScope();
|
|
2853
3556
|
ctx.popScope();
|
|
2854
3557
|
expect(stream, "}", "struct member");
|
|
2855
|
-
const elem =
|
|
3558
|
+
const elem = finishStatement("struct", start, ctx, {
|
|
2856
3559
|
name: identElem,
|
|
2857
3560
|
members
|
|
2858
|
-
});
|
|
2859
|
-
attachAttributes(elem, attributes);
|
|
3561
|
+
}, attributes);
|
|
2860
3562
|
linkDeclIdentElem(identElem, elem);
|
|
2861
3563
|
return elem;
|
|
2862
3564
|
}
|
|
2863
|
-
/** Grammar: struct_body_decl : '{' struct_member (',' struct_member)* ','? '}' */
|
|
2864
|
-
function parseStructMembers(ctx) {
|
|
2865
|
-
const members = parseCommaList(ctx, parseStructMember);
|
|
2866
|
-
for (const member of members) ctx.addElem(member);
|
|
2867
|
-
return members;
|
|
2868
|
-
}
|
|
2869
3565
|
/** Grammar: struct_member : attribute* member_ident ':' type_specifier */
|
|
2870
3566
|
function parseStructMember(ctx) {
|
|
2871
3567
|
const { stream } = ctx;
|
|
2872
3568
|
const checkpoint = stream.checkpoint();
|
|
2873
|
-
const
|
|
3569
|
+
const attrs = parseAttributeList(ctx);
|
|
2874
3570
|
const nameToken = stream.matchKind("word");
|
|
2875
3571
|
if (!nameToken) {
|
|
2876
3572
|
stream.reset(checkpoint);
|
|
2877
3573
|
return null;
|
|
2878
3574
|
}
|
|
3575
|
+
const attributes = attrsOrUndef(attrs);
|
|
2879
3576
|
const start = getStartWithAttributes(attributes, nameToken.span[0]);
|
|
2880
|
-
beginElem(ctx, "member", attributes.length ? attributes : void 0);
|
|
2881
3577
|
const name = makeNameElem(nameToken);
|
|
2882
|
-
ctx.addElem(name);
|
|
2883
3578
|
expect(stream, ":", "struct member name");
|
|
2884
3579
|
const typeRef = parseSimpleTypeRef(ctx);
|
|
2885
3580
|
if (!typeRef) throwParseError(stream, "Expected type after ':'");
|
|
2886
|
-
ctx
|
|
2887
|
-
const elem = finishElem("member", start, ctx, {
|
|
3581
|
+
return finishStatement("member", start, ctx, {
|
|
2888
3582
|
name,
|
|
2889
3583
|
typeRef
|
|
2890
|
-
});
|
|
2891
|
-
attachAttributes(elem, attributes.length ? attributes : void 0);
|
|
2892
|
-
return elem;
|
|
3584
|
+
}, attributes);
|
|
2893
3585
|
}
|
|
2894
3586
|
//#endregion
|
|
2895
3587
|
//#region ../wesl/src/parse/ParseModule.ts
|
|
@@ -2900,6 +3592,7 @@ const declParsers = [
|
|
|
2900
3592
|
parseAliasDecl,
|
|
2901
3593
|
parseStructDecl,
|
|
2902
3594
|
parseFnDecl,
|
|
3595
|
+
parseDoBlock,
|
|
2903
3596
|
parseConstAssert
|
|
2904
3597
|
];
|
|
2905
3598
|
/** Grammar: translation_unit : global_directive* ( global_decl | global_assert | ';' )* */
|
|
@@ -2907,19 +3600,39 @@ function parseModule(ctx) {
|
|
|
2907
3600
|
parseImports(ctx);
|
|
2908
3601
|
parseDirectives(ctx);
|
|
2909
3602
|
while (parseNextDeclaration(ctx));
|
|
3603
|
+
if (ctx.stream.peek() !== null) throwParseError(ctx.stream, "Expected a declaration or directive");
|
|
3604
|
+
}
|
|
3605
|
+
/**
|
|
3606
|
+
* Reject a module that gives a `do` block a name that clashes with a fn/global
|
|
3607
|
+
* or with another `do` block (`do` blocks share the module's declaration
|
|
3608
|
+
* namespace, and runners key blocks by name so a duplicate would silently
|
|
3609
|
+
* shadow the earlier one). This is a small module-local pass, deliberately not
|
|
3610
|
+
* part of bindIdents.
|
|
3611
|
+
*/
|
|
3612
|
+
function checkDoBlockNames(moduleElem) {
|
|
3613
|
+
const doBlocks = declsOfKind(moduleElem, "do");
|
|
3614
|
+
if (doBlocks.length === 0) return;
|
|
3615
|
+
const declNames = new Set(moduleElem.decls.map(globalDeclName));
|
|
3616
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3617
|
+
for (const block of doBlocks) {
|
|
3618
|
+
const { name, start, end } = block.name;
|
|
3619
|
+
if (declNames.has(name)) throw new ParseError(`'${name}' declared as both fn and do`, [start, end]);
|
|
3620
|
+
if (seen.has(name)) throw new ParseError(`'${name}' declared as do more than once`, [start, end]);
|
|
3621
|
+
seen.add(name);
|
|
3622
|
+
}
|
|
2910
3623
|
}
|
|
2911
3624
|
/** Parse WESL import statements at the start of the module. */
|
|
2912
3625
|
function parseImports(ctx) {
|
|
2913
3626
|
const importElems = parseWeslImports(ctx);
|
|
2914
3627
|
for (const importElem of importElems) {
|
|
2915
|
-
ctx.
|
|
3628
|
+
ctx.addModuleDecl(importElem);
|
|
2916
3629
|
ctx.state.stable.imports.push(importElem.imports);
|
|
2917
3630
|
}
|
|
2918
3631
|
}
|
|
2919
3632
|
/** Grammar: global_directive : diagnostic_directive | enable_directive | requires_directive */
|
|
2920
3633
|
function parseDirectives(ctx) {
|
|
2921
3634
|
const directives = parseMany(ctx, parseDirective);
|
|
2922
|
-
for (const elem of directives) ctx.
|
|
3635
|
+
for (const elem of directives) ctx.addModuleDecl(elem);
|
|
2923
3636
|
}
|
|
2924
3637
|
/** Parse one declaration, return true if more may exist. */
|
|
2925
3638
|
function parseNextDeclaration(ctx) {
|
|
@@ -2934,34 +3647,40 @@ function parseNextDeclaration(ctx) {
|
|
|
2934
3647
|
if (attrs.length) throwParseError(stream, "Expected declaration after attributes");
|
|
2935
3648
|
return false;
|
|
2936
3649
|
}
|
|
3650
|
+
/** @return the declared name of a module-level declaration, if it has one. */
|
|
3651
|
+
function globalDeclName(elem) {
|
|
3652
|
+
switch (elem.kind) {
|
|
3653
|
+
case "fn":
|
|
3654
|
+
case "struct":
|
|
3655
|
+
case "alias": return elem.name.ident.originalName;
|
|
3656
|
+
case "gvar":
|
|
3657
|
+
case "const":
|
|
3658
|
+
case "override": return elem.name.decl.ident.originalName;
|
|
3659
|
+
default: return;
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
2937
3662
|
/** Try each declaration parser until one succeeds. */
|
|
2938
3663
|
function parseDecl(ctx, attrs) {
|
|
2939
|
-
const
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
return true;
|
|
2944
|
-
}
|
|
2945
|
-
return false;
|
|
3664
|
+
const elem = findMap(declParsers, (p) => p(ctx, attrsOrUndef(attrs)));
|
|
3665
|
+
if (!elem) return false;
|
|
3666
|
+
recordDecl(ctx, elem, attrs);
|
|
3667
|
+
return true;
|
|
2946
3668
|
}
|
|
2947
3669
|
/** Pop conditional scope and attach the conditional attribute. */
|
|
2948
3670
|
function finalizeConditional(ctx, attrs) {
|
|
2949
3671
|
const partialScope = ctx.popScope();
|
|
2950
|
-
partialScope.condAttribute =
|
|
3672
|
+
partialScope.condAttribute = conditionalAttribute(attrs);
|
|
2951
3673
|
}
|
|
2952
3674
|
/** Record a parsed declaration, extending start to include attributes. */
|
|
2953
3675
|
function recordDecl(ctx, elem, attrs) {
|
|
2954
3676
|
if (attrs.length && elem.start > attrs[0].start) elem.start = attrs[0].start;
|
|
2955
|
-
ctx.
|
|
3677
|
+
ctx.addModuleDecl(elem);
|
|
2956
3678
|
if (elem.kind === "assert") {
|
|
2957
3679
|
const { stable } = ctx.state;
|
|
2958
3680
|
stable.moduleAsserts ??= [];
|
|
2959
3681
|
stable.moduleAsserts.push(elem);
|
|
2960
3682
|
}
|
|
2961
3683
|
}
|
|
2962
|
-
function isConditionalAttribute(a) {
|
|
2963
|
-
return a.kind === "@if" || a.kind === "@elif" || a.kind === "@else";
|
|
2964
|
-
}
|
|
2965
3684
|
//#endregion
|
|
2966
3685
|
//#region ../wesl/src/parse/ParsingContext.ts
|
|
2967
3686
|
/** Context for parsers to build AST and manage scopes. */
|
|
@@ -2984,9 +3703,9 @@ var ParsingContext = class {
|
|
|
2984
3703
|
currentScope() {
|
|
2985
3704
|
return this.state.context.scope;
|
|
2986
3705
|
}
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
3706
|
+
/** Append a top-level declaration to the module, in source order. */
|
|
3707
|
+
addModuleDecl(elem) {
|
|
3708
|
+
this.state.stable.moduleElem.decls.push(elem);
|
|
2990
3709
|
}
|
|
2991
3710
|
pushScope(kind = "scope") {
|
|
2992
3711
|
const { scope } = this.state.context;
|
|
@@ -3054,55 +3773,6 @@ const reservedWords = `NULL Self abstract active alignas alignof as asm asm_frag
|
|
|
3054
3773
|
target template this thread_local throw trait try type typedef typeid typename typeof
|
|
3055
3774
|
union unless unorm unsafe unsized use using varying virtual volatile wgsl where with writeonly yield`.split(/\s+/);
|
|
3056
3775
|
//#endregion
|
|
3057
|
-
//#region ../wesl/src/parse/stream/CachingStream.ts
|
|
3058
|
-
var CachingStream = class {
|
|
3059
|
-
cache = new Cache(5);
|
|
3060
|
-
inner;
|
|
3061
|
-
constructor(inner) {
|
|
3062
|
-
this.inner = inner;
|
|
3063
|
-
}
|
|
3064
|
-
checkpoint() {
|
|
3065
|
-
return this.inner.checkpoint();
|
|
3066
|
-
}
|
|
3067
|
-
reset(position) {
|
|
3068
|
-
this.inner.reset(position);
|
|
3069
|
-
}
|
|
3070
|
-
nextToken() {
|
|
3071
|
-
const startPos = this.checkpoint();
|
|
3072
|
-
const cachedValue = this.cache.get(startPos);
|
|
3073
|
-
if (cachedValue !== void 0) {
|
|
3074
|
-
this.reset(cachedValue.checkpoint);
|
|
3075
|
-
return cachedValue.token;
|
|
3076
|
-
} else {
|
|
3077
|
-
const token = this.inner.nextToken();
|
|
3078
|
-
const checkpoint = this.checkpoint();
|
|
3079
|
-
this.cache.set(startPos, {
|
|
3080
|
-
token,
|
|
3081
|
-
checkpoint
|
|
3082
|
-
});
|
|
3083
|
-
return token;
|
|
3084
|
-
}
|
|
3085
|
-
}
|
|
3086
|
-
get src() {
|
|
3087
|
-
return this.inner.src;
|
|
3088
|
-
}
|
|
3089
|
-
};
|
|
3090
|
-
/** size limited key value cache */
|
|
3091
|
-
var Cache = class extends Map {
|
|
3092
|
-
max;
|
|
3093
|
-
constructor(max) {
|
|
3094
|
-
super();
|
|
3095
|
-
this.max = max;
|
|
3096
|
-
}
|
|
3097
|
-
set(k, v) {
|
|
3098
|
-
if (this.size > this.max) {
|
|
3099
|
-
const first = this.keys().next().value;
|
|
3100
|
-
if (first) this.delete(first);
|
|
3101
|
-
}
|
|
3102
|
-
return super.set(k, v);
|
|
3103
|
-
}
|
|
3104
|
-
};
|
|
3105
|
-
//#endregion
|
|
3106
3776
|
//#region ../wesl/src/parse/stream/RegexHelpers.ts
|
|
3107
3777
|
function toRegexSource(nameExp) {
|
|
3108
3778
|
const [name, e] = nameExp;
|
|
@@ -3132,33 +3802,10 @@ function matchOneOf(syms) {
|
|
|
3132
3802
|
return new RegExp(escaped.join("|"));
|
|
3133
3803
|
}
|
|
3134
3804
|
//#endregion
|
|
3135
|
-
//#region ../wesl/src/parse/stream/
|
|
3136
|
-
/** Runs a `RegexMatchers` on an input string */
|
|
3137
|
-
var MatchersStream = class {
|
|
3138
|
-
position = 0;
|
|
3139
|
-
text;
|
|
3140
|
-
matchers;
|
|
3141
|
-
constructor(text, matchers) {
|
|
3142
|
-
this.text = text;
|
|
3143
|
-
this.matchers = matchers;
|
|
3144
|
-
}
|
|
3145
|
-
checkpoint() {
|
|
3146
|
-
return this.position;
|
|
3147
|
-
}
|
|
3148
|
-
reset(position) {
|
|
3149
|
-
this.position = position;
|
|
3150
|
-
}
|
|
3151
|
-
nextToken() {
|
|
3152
|
-
const result = this.matchers.execAt(this.text, this.position);
|
|
3153
|
-
if (result === null) return null;
|
|
3154
|
-
this.position = result.span[1];
|
|
3155
|
-
return result;
|
|
3156
|
-
}
|
|
3157
|
-
get src() {
|
|
3158
|
-
return this.text;
|
|
3159
|
-
}
|
|
3160
|
-
};
|
|
3805
|
+
//#region ../wesl/src/parse/stream/RegexMatchers.ts
|
|
3161
3806
|
/**
|
|
3807
|
+
* Matches tokens by kind with one combined regex.
|
|
3808
|
+
*
|
|
3162
3809
|
* The matchers passed to this object must follow certain rules:
|
|
3163
3810
|
* - They must use non-capturing groups: `(?:...)`
|
|
3164
3811
|
* - They must NOT use `^` or `$`
|
|
@@ -3169,55 +3816,230 @@ var RegexMatchers = class {
|
|
|
3169
3816
|
constructor(matchers) {
|
|
3170
3817
|
this.groups = Object.keys(matchers);
|
|
3171
3818
|
const expParts = Object.entries(matchers).map(toRegexSource).join("|");
|
|
3172
|
-
this.exp = new RegExp(expParts, "
|
|
3819
|
+
this.exp = new RegExp(expParts, "yu");
|
|
3173
3820
|
}
|
|
3174
3821
|
execAt(text, position) {
|
|
3175
3822
|
this.exp.lastIndex = position;
|
|
3176
|
-
const
|
|
3177
|
-
if (
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
text: text.slice(span[0], span[1])
|
|
3183
|
-
};
|
|
3184
|
-
} else return null;
|
|
3185
|
-
}
|
|
3186
|
-
};
|
|
3187
|
-
function findGroupDex(indices) {
|
|
3188
|
-
if (indices !== void 0) for (let i = 1; i < indices.length; i++) {
|
|
3189
|
-
const span = indices[i];
|
|
3190
|
-
if (span !== void 0) return {
|
|
3823
|
+
const matches = this.exp.exec(text);
|
|
3824
|
+
if (matches === null) return null;
|
|
3825
|
+
const matched = matches[0];
|
|
3826
|
+
const span = [position, position + matched.length];
|
|
3827
|
+
return {
|
|
3828
|
+
kind: this.groups[matchedGroupIndex(matches)],
|
|
3191
3829
|
span,
|
|
3192
|
-
|
|
3830
|
+
text: matched
|
|
3193
3831
|
};
|
|
3194
3832
|
}
|
|
3833
|
+
};
|
|
3834
|
+
/** @return index of the alternation group that matched */
|
|
3835
|
+
function matchedGroupIndex(matches) {
|
|
3836
|
+
for (let i = 1; i < matches.length; i++) if (matches[i] !== void 0) return i - 1;
|
|
3837
|
+
throw new Error("no matching group");
|
|
3195
3838
|
}
|
|
3196
3839
|
//#endregion
|
|
3197
|
-
//#region ../wesl/src/parse/
|
|
3840
|
+
//#region ../wesl/src/parse/stream/WeslLexer.ts
|
|
3841
|
+
const ident = /(?:(?:[_\p{XID_Start}][\p{XID_Continue}]+)|(?:[\p{XID_Start}]))/u;
|
|
3198
3842
|
/** Whitespaces including new lines */
|
|
3199
3843
|
const blankspaces = /[ \t\n\v\f\r\u{0085}\u{200E}\u{200F}\u{2028}\u{2029}]+/u;
|
|
3200
|
-
const
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
const
|
|
3844
|
+
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);
|
|
3845
|
+
/** Unicode-aware fallback for characters the ASCII fast path can't handle.
|
|
3846
|
+
* lastIndex is set on every use, so sharing one instance is safe. */
|
|
3847
|
+
const unicodeMatcher = new RegexMatchers({
|
|
3204
3848
|
word: ident,
|
|
3205
|
-
number:
|
|
3849
|
+
number: digits,
|
|
3206
3850
|
blankspaces,
|
|
3207
3851
|
commentStart: /\/\/|\/\*/,
|
|
3208
|
-
symbol: matchOneOf(
|
|
3852
|
+
symbol: matchOneOf("& && -> @ / ! [ ] { } :: : , == = != >>= >> >= > <<= << <= < % - -- . + ++ | || ( ) ; * ~ ^ // /* */ += -= *= /= %= &= |= ^= _"),
|
|
3209
3853
|
invalid: /[^]/
|
|
3210
3854
|
});
|
|
3855
|
+
/**
|
|
3856
|
+
* Hand-written scanner for WESL/WGSL raw tokens.
|
|
3857
|
+
*
|
|
3858
|
+
* The common case is dispatched on ASCII char codes: whitespace is skipped
|
|
3859
|
+
* without producing tokens, and idents, numbers, symbols, and comment openers
|
|
3860
|
+
* are matched directly. Any character >= 0x80 (unicode idents, unicode
|
|
3861
|
+
* blankspace) falls back to the unicode-aware regex, so there is no up-front
|
|
3862
|
+
* scan and no unicode restriction.
|
|
3863
|
+
*/
|
|
3864
|
+
var WeslLexer = class {
|
|
3865
|
+
position = 0;
|
|
3866
|
+
/** sticky number matcher (stateful via lastIndex, so kept per-instance) */
|
|
3867
|
+
numberExp = new RegExp(digits.source, "y");
|
|
3868
|
+
src;
|
|
3869
|
+
constructor(src) {
|
|
3870
|
+
this.src = src;
|
|
3871
|
+
}
|
|
3872
|
+
checkpoint() {
|
|
3873
|
+
return this.position;
|
|
3874
|
+
}
|
|
3875
|
+
reset(position) {
|
|
3876
|
+
this.position = position;
|
|
3877
|
+
}
|
|
3878
|
+
nextToken() {
|
|
3879
|
+
const { src } = this;
|
|
3880
|
+
const len = src.length;
|
|
3881
|
+
let pos = this.position;
|
|
3882
|
+
while (pos < len) {
|
|
3883
|
+
const c = src.charCodeAt(pos);
|
|
3884
|
+
if (c === 32 || c >= 9 && c <= 13) pos++;
|
|
3885
|
+
else break;
|
|
3886
|
+
}
|
|
3887
|
+
if (pos >= len) {
|
|
3888
|
+
this.position = pos;
|
|
3889
|
+
return null;
|
|
3890
|
+
}
|
|
3891
|
+
const c = src.charCodeAt(pos);
|
|
3892
|
+
if (c >= 97 && c <= 122 || c >= 65 && c <= 90) return this.word(pos);
|
|
3893
|
+
if (c >= 48 && c <= 57) return this.number(pos);
|
|
3894
|
+
const n = src.charCodeAt(pos + 1);
|
|
3895
|
+
switch (c) {
|
|
3896
|
+
case 95:
|
|
3897
|
+
if (isWordChar(n)) return this.word(pos);
|
|
3898
|
+
if (n >= 128) return this.regexToken(pos);
|
|
3899
|
+
return this.symbol(pos, "_");
|
|
3900
|
+
case 47:
|
|
3901
|
+
if (n === 47) return this.token("commentStart", pos, "//");
|
|
3902
|
+
if (n === 42) return this.token("commentStart", pos, "/*");
|
|
3903
|
+
if (n === 61) return this.symbol(pos, "/=");
|
|
3904
|
+
return this.symbol(pos, "/");
|
|
3905
|
+
case 46:
|
|
3906
|
+
if (n >= 48 && n <= 57) return this.number(pos);
|
|
3907
|
+
return this.symbol(pos, ".");
|
|
3908
|
+
case 62:
|
|
3909
|
+
if (n === 62) {
|
|
3910
|
+
if (src.charCodeAt(pos + 2) === 61) return this.symbol(pos, ">>=");
|
|
3911
|
+
return this.symbol(pos, ">>");
|
|
3912
|
+
}
|
|
3913
|
+
if (n === 61) return this.symbol(pos, ">=");
|
|
3914
|
+
return this.symbol(pos, ">");
|
|
3915
|
+
case 60:
|
|
3916
|
+
if (n === 60) {
|
|
3917
|
+
if (src.charCodeAt(pos + 2) === 61) return this.symbol(pos, "<<=");
|
|
3918
|
+
return this.symbol(pos, "<<");
|
|
3919
|
+
}
|
|
3920
|
+
if (n === 61) return this.symbol(pos, "<=");
|
|
3921
|
+
return this.symbol(pos, "<");
|
|
3922
|
+
case 38:
|
|
3923
|
+
if (n === 38) return this.symbol(pos, "&&");
|
|
3924
|
+
if (n === 61) return this.symbol(pos, "&=");
|
|
3925
|
+
return this.symbol(pos, "&");
|
|
3926
|
+
case 124:
|
|
3927
|
+
if (n === 124) return this.symbol(pos, "||");
|
|
3928
|
+
if (n === 61) return this.symbol(pos, "|=");
|
|
3929
|
+
return this.symbol(pos, "|");
|
|
3930
|
+
case 45:
|
|
3931
|
+
if (n === 62) return this.symbol(pos, "->");
|
|
3932
|
+
if (n === 45) return this.symbol(pos, "--");
|
|
3933
|
+
if (n === 61) return this.symbol(pos, "-=");
|
|
3934
|
+
return this.symbol(pos, "-");
|
|
3935
|
+
case 43:
|
|
3936
|
+
if (n === 43) return this.symbol(pos, "++");
|
|
3937
|
+
if (n === 61) return this.symbol(pos, "+=");
|
|
3938
|
+
return this.symbol(pos, "+");
|
|
3939
|
+
case 58:
|
|
3940
|
+
if (n === 58) return this.symbol(pos, "::");
|
|
3941
|
+
return this.symbol(pos, ":");
|
|
3942
|
+
case 61:
|
|
3943
|
+
if (n === 61) return this.symbol(pos, "==");
|
|
3944
|
+
return this.symbol(pos, "=");
|
|
3945
|
+
case 33:
|
|
3946
|
+
if (n === 61) return this.symbol(pos, "!=");
|
|
3947
|
+
return this.symbol(pos, "!");
|
|
3948
|
+
case 42:
|
|
3949
|
+
if (n === 47) return this.symbol(pos, "*/");
|
|
3950
|
+
if (n === 61) return this.symbol(pos, "*=");
|
|
3951
|
+
return this.symbol(pos, "*");
|
|
3952
|
+
case 37:
|
|
3953
|
+
if (n === 61) return this.symbol(pos, "%=");
|
|
3954
|
+
return this.symbol(pos, "%");
|
|
3955
|
+
case 94:
|
|
3956
|
+
if (n === 61) return this.symbol(pos, "^=");
|
|
3957
|
+
return this.symbol(pos, "^");
|
|
3958
|
+
case 64: return this.symbol(pos, "@");
|
|
3959
|
+
case 91: return this.symbol(pos, "[");
|
|
3960
|
+
case 93: return this.symbol(pos, "]");
|
|
3961
|
+
case 123: return this.symbol(pos, "{");
|
|
3962
|
+
case 125: return this.symbol(pos, "}");
|
|
3963
|
+
case 40: return this.symbol(pos, "(");
|
|
3964
|
+
case 41: return this.symbol(pos, ")");
|
|
3965
|
+
case 44: return this.symbol(pos, ",");
|
|
3966
|
+
case 59: return this.symbol(pos, ";");
|
|
3967
|
+
case 126: return this.symbol(pos, "~");
|
|
3968
|
+
}
|
|
3969
|
+
if (c >= 128) return this.regexToken(pos);
|
|
3970
|
+
this.position = pos + 1;
|
|
3971
|
+
return {
|
|
3972
|
+
kind: "invalid",
|
|
3973
|
+
span: [pos, pos + 1],
|
|
3974
|
+
text: src[pos]
|
|
3975
|
+
};
|
|
3976
|
+
}
|
|
3977
|
+
token(kind, start, text) {
|
|
3978
|
+
const end = start + text.length;
|
|
3979
|
+
this.position = end;
|
|
3980
|
+
return {
|
|
3981
|
+
kind,
|
|
3982
|
+
span: [start, end],
|
|
3983
|
+
text
|
|
3984
|
+
};
|
|
3985
|
+
}
|
|
3986
|
+
symbol(start, text) {
|
|
3987
|
+
return this.token("symbol", start, text);
|
|
3988
|
+
}
|
|
3989
|
+
word(start) {
|
|
3990
|
+
const { src } = this;
|
|
3991
|
+
const len = src.length;
|
|
3992
|
+
let pos = start + 1;
|
|
3993
|
+
while (pos < len) {
|
|
3994
|
+
const c = src.charCodeAt(pos);
|
|
3995
|
+
if (isWordChar(c)) pos++;
|
|
3996
|
+
else if (c >= 128) return this.regexToken(start);
|
|
3997
|
+
else break;
|
|
3998
|
+
}
|
|
3999
|
+
this.position = pos;
|
|
4000
|
+
return {
|
|
4001
|
+
kind: "word",
|
|
4002
|
+
span: [start, pos],
|
|
4003
|
+
text: src.slice(start, pos)
|
|
4004
|
+
};
|
|
4005
|
+
}
|
|
4006
|
+
number(start) {
|
|
4007
|
+
this.numberExp.lastIndex = start;
|
|
4008
|
+
const text = this.numberExp.exec(this.src)[0];
|
|
4009
|
+
return this.token("number", start, text);
|
|
4010
|
+
}
|
|
4011
|
+
/** match one token (or blankspace run) with the unicode-aware regex */
|
|
4012
|
+
regexToken(start) {
|
|
4013
|
+
const token = unicodeMatcher.execAt(this.src, start);
|
|
4014
|
+
if (token === null) return null;
|
|
4015
|
+
this.position = token.span[1];
|
|
4016
|
+
return token;
|
|
4017
|
+
}
|
|
4018
|
+
};
|
|
4019
|
+
function isWordChar(c) {
|
|
4020
|
+
return c >= 97 && c <= 122 || c >= 65 && c <= 90 || c >= 48 && c <= 57 || c === 95;
|
|
4021
|
+
}
|
|
4022
|
+
//#endregion
|
|
4023
|
+
//#region ../wesl/src/parse/WeslStream.ts
|
|
4024
|
+
/** One line break, treating \r\n as a single break.
|
|
4025
|
+
* Same code points as `isLineBreak` in AttachComments.ts (charCode form). */
|
|
4026
|
+
const lineBreak = String.raw`\r\n?|[\n\v\f\u{0085}\u{2028}\u{2029}]`;
|
|
4027
|
+
const keywordOrReserved = new Set(keywords.concat(reservedWords));
|
|
3211
4028
|
/** A stream that produces WESL tokens, skipping over comments and white space */
|
|
3212
4029
|
var WeslStream = class {
|
|
3213
4030
|
stream;
|
|
3214
|
-
/** New line */
|
|
3215
|
-
eolPattern =
|
|
4031
|
+
/** New line (stateful: scanned via lastIndex, so kept per-instance). */
|
|
4032
|
+
eolPattern = new RegExp(lineBreak, "gu");
|
|
3216
4033
|
blockCommentPattern = /\/\*|\*\//g;
|
|
4034
|
+
/** Comments skipped before a real token, keyed by that token's start position. */
|
|
4035
|
+
triviaByPos = /* @__PURE__ */ new Map();
|
|
4036
|
+
/** Last peeked token, so the following nextToken() skips the rescan.
|
|
4037
|
+
* Never invalidated: tokenization is deterministic per position. */
|
|
4038
|
+
peeked = null;
|
|
3217
4039
|
src;
|
|
3218
4040
|
constructor(src) {
|
|
3219
4041
|
this.src = src;
|
|
3220
|
-
this.stream = new
|
|
4042
|
+
this.stream = new WeslLexer(src);
|
|
3221
4043
|
}
|
|
3222
4044
|
checkpoint() {
|
|
3223
4045
|
return this.stream.checkpoint();
|
|
@@ -3225,27 +4047,79 @@ var WeslStream = class {
|
|
|
3225
4047
|
reset(position) {
|
|
3226
4048
|
this.stream.reset(position);
|
|
3227
4049
|
}
|
|
4050
|
+
/** All recorded comment runs (each a contiguous group of comments between two
|
|
4051
|
+
* real tokens), in source order. Consumed by the post-parse comment pass. */
|
|
4052
|
+
commentRuns() {
|
|
4053
|
+
return [...this.triviaByPos.entries()].sort((a, b) => a[0] - b[0]).map(([, run]) => run);
|
|
4054
|
+
}
|
|
4055
|
+
recordTrivia(pos, pending) {
|
|
4056
|
+
if (pending) this.triviaByPos.set(pos, pending);
|
|
4057
|
+
}
|
|
4058
|
+
/** Next real token (comments/blankspace skipped and recorded as trivia); null at EOF. */
|
|
3228
4059
|
nextToken() {
|
|
4060
|
+
const peeked = this.usePeeked();
|
|
4061
|
+
if (peeked !== null) return peeked.token;
|
|
4062
|
+
let pending;
|
|
3229
4063
|
while (true) {
|
|
3230
4064
|
const token = this.stream.nextToken();
|
|
3231
|
-
if (token === null)
|
|
4065
|
+
if (token === null) {
|
|
4066
|
+
this.recordTrivia(this.src.length, pending);
|
|
4067
|
+
return null;
|
|
4068
|
+
}
|
|
3232
4069
|
const kind = token.kind;
|
|
3233
4070
|
if (kind === "blankspaces") continue;
|
|
3234
|
-
else if (kind === "commentStart")
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
const returnToken = token;
|
|
3238
|
-
if (keywordOrReserved.has(token.text)) returnToken.kind = "keyword";
|
|
3239
|
-
return returnToken;
|
|
4071
|
+
else if (kind === "commentStart") {
|
|
4072
|
+
pending ??= [];
|
|
4073
|
+
pending.push(this.consumeComment(token));
|
|
3240
4074
|
} else if (kind === "invalid") throw new ParseError("Invalid token " + token.text, token.span);
|
|
3241
|
-
else
|
|
4075
|
+
else {
|
|
4076
|
+
this.recordTrivia(token.span[0], pending);
|
|
4077
|
+
const result = token;
|
|
4078
|
+
if (kind === "word" && keywordOrReserved.has(token.text)) result.kind = "keyword";
|
|
4079
|
+
return result;
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
/** If the last peek() was at the current position, consume and return it. */
|
|
4084
|
+
usePeeked() {
|
|
4085
|
+
const peeked = this.peeked;
|
|
4086
|
+
if (peeked !== null && peeked.pos === this.checkpoint()) {
|
|
4087
|
+
this.reset(peeked.end);
|
|
4088
|
+
return peeked;
|
|
4089
|
+
}
|
|
4090
|
+
return null;
|
|
4091
|
+
}
|
|
4092
|
+
/** Skip a comment (rejecting embedded \0), advance the stream past it,
|
|
4093
|
+
* and return it as trivia. */
|
|
4094
|
+
consumeComment(token) {
|
|
4095
|
+
const style = token.text === "//" ? "line" : "block";
|
|
4096
|
+
const start = token.span[0];
|
|
4097
|
+
const end = this.commentEnd(token);
|
|
4098
|
+
const bodyNull = this.src.slice(start, end).indexOf("\0");
|
|
4099
|
+
if (bodyNull >= 0) {
|
|
4100
|
+
const at = start + bodyNull;
|
|
4101
|
+
throw new ParseError("Invalid token \\0", [at, at + 1]);
|
|
3242
4102
|
}
|
|
4103
|
+
this.stream.reset(end);
|
|
4104
|
+
return {
|
|
4105
|
+
style,
|
|
4106
|
+
start,
|
|
4107
|
+
end
|
|
4108
|
+
};
|
|
3243
4109
|
}
|
|
3244
4110
|
/** Peek at the next token without consuming it */
|
|
3245
4111
|
peek() {
|
|
3246
4112
|
const pos = this.checkpoint();
|
|
4113
|
+
const peeked = this.peeked;
|
|
4114
|
+
if (peeked !== null && peeked.pos === pos) return peeked.token;
|
|
3247
4115
|
const token = this.nextToken();
|
|
4116
|
+
const end = this.checkpoint();
|
|
3248
4117
|
this.reset(pos);
|
|
4118
|
+
this.peeked = {
|
|
4119
|
+
pos,
|
|
4120
|
+
token,
|
|
4121
|
+
end
|
|
4122
|
+
};
|
|
3249
4123
|
return token;
|
|
3250
4124
|
}
|
|
3251
4125
|
/** Consume token if text matches, otherwise leave position unchanged */
|
|
@@ -3289,10 +4163,15 @@ var WeslStream = class {
|
|
|
3289
4163
|
}
|
|
3290
4164
|
return tokens;
|
|
3291
4165
|
}
|
|
3292
|
-
|
|
4166
|
+
/** End position of a comment opened by a commentStart token. */
|
|
4167
|
+
commentEnd(token) {
|
|
4168
|
+
return token.text === "//" ? this.lineCommentEnd(token.span[1]) : this.skipBlockComment(token.span[1]);
|
|
4169
|
+
}
|
|
4170
|
+
/** End of a line comment: the start of the next line break (or end of file). */
|
|
4171
|
+
lineCommentEnd(position) {
|
|
3293
4172
|
this.eolPattern.lastIndex = position;
|
|
3294
|
-
|
|
3295
|
-
|
|
4173
|
+
const result = this.eolPattern.exec(this.src);
|
|
4174
|
+
return result === null ? this.src.length : result.index;
|
|
3296
4175
|
}
|
|
3297
4176
|
skipBlockComment(start) {
|
|
3298
4177
|
let position = start;
|
|
@@ -3314,39 +4193,46 @@ var WeslStream = class {
|
|
|
3314
4193
|
const startPosition = this.stream.checkpoint();
|
|
3315
4194
|
const token = this.nextToken();
|
|
3316
4195
|
this.stream.reset(startPosition);
|
|
3317
|
-
if (token === null) return null;
|
|
3318
|
-
if (token.
|
|
3319
|
-
if (token.text === "<") if (this.isTemplateStart(token.span[1])) {
|
|
3320
|
-
this.stream.reset(token.span[1]);
|
|
3321
|
-
return token;
|
|
3322
|
-
} else {
|
|
4196
|
+
if (token === null || token.kind !== "symbol" || token.text !== "<") return null;
|
|
4197
|
+
if (!this.isTemplateStart(token.span[1])) {
|
|
3323
4198
|
this.stream.reset(startPosition);
|
|
3324
4199
|
return null;
|
|
3325
4200
|
}
|
|
3326
|
-
|
|
4201
|
+
this.stream.reset(token.span[1]);
|
|
4202
|
+
return token;
|
|
3327
4203
|
}
|
|
4204
|
+
/** Match a template-closing `>`, splitting it off a `>>`/`>=`/`>>=` token when needed. */
|
|
3328
4205
|
nextTemplateEndToken() {
|
|
3329
4206
|
const startPosition = this.stream.checkpoint();
|
|
3330
4207
|
const token = this.nextToken();
|
|
3331
4208
|
this.stream.reset(startPosition);
|
|
3332
4209
|
if (token === null) return null;
|
|
3333
|
-
if (token.kind
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
4210
|
+
if (token.kind !== "symbol" || token.text[0] !== ">") return null;
|
|
4211
|
+
const tokenPosition = token.span[0];
|
|
4212
|
+
this.stream.reset(tokenPosition + 1);
|
|
4213
|
+
return {
|
|
4214
|
+
kind: "symbol",
|
|
4215
|
+
span: [tokenPosition, tokenPosition + 1],
|
|
4216
|
+
text: ">"
|
|
4217
|
+
};
|
|
4218
|
+
}
|
|
4219
|
+
/** Next symbol from the raw stream, skipping comment bodies (so symbols
|
|
4220
|
+
* inside comments don't count) and non-symbol tokens; null at EOF. */
|
|
4221
|
+
nextRawSymbol() {
|
|
4222
|
+
while (true) {
|
|
4223
|
+
const token = this.stream.nextToken();
|
|
4224
|
+
if (token === null) return null;
|
|
4225
|
+
if (token.kind === "commentStart") this.stream.reset(this.commentEnd(token));
|
|
4226
|
+
else if (token.kind === "symbol") return token;
|
|
4227
|
+
}
|
|
3342
4228
|
}
|
|
4229
|
+
/** Template-list discovery: scan forward from `<` for a balanced closing `>`. */
|
|
3343
4230
|
isTemplateStart(afterToken) {
|
|
3344
4231
|
this.stream.reset(afterToken);
|
|
3345
4232
|
let pendingCounter = 1;
|
|
3346
4233
|
while (true) {
|
|
3347
|
-
const nextToken = this.
|
|
4234
|
+
const nextToken = this.nextRawSymbol();
|
|
3348
4235
|
if (nextToken === null) return false;
|
|
3349
|
-
if (nextToken.kind !== "symbol") continue;
|
|
3350
4236
|
if (nextToken.text === "<") pendingCounter += 1;
|
|
3351
4237
|
else if (nextToken.text[0] === ">") {
|
|
3352
4238
|
if (nextToken.text === ">" || nextToken.text === ">=") pendingCounter -= 1;
|
|
@@ -3364,12 +4250,11 @@ var WeslStream = class {
|
|
|
3364
4250
|
*/
|
|
3365
4251
|
skipBracketsTo(closingBracket) {
|
|
3366
4252
|
while (true) {
|
|
3367
|
-
const nextToken = this.
|
|
4253
|
+
const nextToken = this.nextRawSymbol();
|
|
3368
4254
|
if (nextToken === null) {
|
|
3369
4255
|
const after = this.stream.checkpoint();
|
|
3370
4256
|
throw new ParseError("Unclosed bracket!", [after, after]);
|
|
3371
4257
|
}
|
|
3372
|
-
if (nextToken.kind !== "symbol") continue;
|
|
3373
4258
|
if (nextToken.text === "(") this.skipBracketsTo(")");
|
|
3374
4259
|
else if (nextToken.text === "[") this.skipBracketsTo("]");
|
|
3375
4260
|
else if (nextToken.text === closingBracket) return;
|
|
@@ -3382,10 +4267,10 @@ var WeslStream = class {
|
|
|
3382
4267
|
function parseWesl(srcModule, options) {
|
|
3383
4268
|
const { ctx, state } = createParseState(srcModule, options);
|
|
3384
4269
|
try {
|
|
3385
|
-
beginElem(ctx, "module");
|
|
3386
4270
|
parseModule(ctx);
|
|
3387
|
-
const moduleElem = state.stable
|
|
3388
|
-
|
|
4271
|
+
const { moduleElem } = state.stable;
|
|
4272
|
+
attachComments(ctx, moduleElem);
|
|
4273
|
+
checkDoBlockNames(moduleElem);
|
|
3389
4274
|
return state.stable;
|
|
3390
4275
|
} catch (e) {
|
|
3391
4276
|
if (e instanceof ParseError) throw new WeslParseError({
|
|
@@ -3399,29 +4284,27 @@ function parseWesl(srcModule, options) {
|
|
|
3399
4284
|
}
|
|
3400
4285
|
}
|
|
3401
4286
|
/** Initialize parse state: token stream, root scope, and module element. */
|
|
3402
|
-
function createParseState(srcModule,
|
|
4287
|
+
function createParseState(srcModule, parseOptions) {
|
|
3403
4288
|
const stream = new WeslStream(srcModule.src);
|
|
3404
4289
|
const rootScope = emptyScope(null);
|
|
3405
4290
|
const moduleElem = {
|
|
3406
4291
|
kind: "module",
|
|
3407
|
-
|
|
4292
|
+
decls: [],
|
|
3408
4293
|
start: 0,
|
|
3409
4294
|
end: srcModule.src.length
|
|
3410
4295
|
};
|
|
3411
4296
|
const state = {
|
|
3412
|
-
context: {
|
|
3413
|
-
scope: rootScope,
|
|
3414
|
-
openElems: []
|
|
3415
|
-
},
|
|
4297
|
+
context: { scope: rootScope },
|
|
3416
4298
|
stable: {
|
|
3417
4299
|
srcModule,
|
|
3418
4300
|
moduleElem,
|
|
3419
4301
|
rootScope,
|
|
3420
|
-
imports: []
|
|
4302
|
+
imports: [],
|
|
4303
|
+
parseOptions
|
|
3421
4304
|
}
|
|
3422
4305
|
};
|
|
3423
4306
|
return {
|
|
3424
|
-
ctx: new ParsingContext(stream, state,
|
|
4307
|
+
ctx: new ParsingContext(stream, state, parseOptions),
|
|
3425
4308
|
state
|
|
3426
4309
|
};
|
|
3427
4310
|
}
|
|
@@ -3432,14 +4315,14 @@ var WeslParseError = class extends Error {
|
|
|
3432
4315
|
span;
|
|
3433
4316
|
src;
|
|
3434
4317
|
constructor(opts) {
|
|
3435
|
-
const
|
|
3436
|
-
const
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
message
|
|
3440
|
-
super(message, { cause
|
|
3441
|
-
this.span =
|
|
3442
|
-
this.src =
|
|
4318
|
+
const { cause, src } = opts;
|
|
4319
|
+
const source = src.src;
|
|
4320
|
+
const [lineNum, linePos] = offsetToLineNumber(cause.span[0], source);
|
|
4321
|
+
const highlight = errorHighlight(source, cause.span).join("\n");
|
|
4322
|
+
const message = `${src.debugFilePath}:${lineNum}:${linePos} error: ${cause.message}\n${highlight}`;
|
|
4323
|
+
super(message, { cause });
|
|
4324
|
+
this.span = cause.span;
|
|
4325
|
+
this.src = src;
|
|
3443
4326
|
}
|
|
3444
4327
|
};
|
|
3445
4328
|
/** Parse a WESL file. */
|
|
@@ -3449,8 +4332,8 @@ function parseSrcModule(srcModule, options) {
|
|
|
3449
4332
|
/** @return flattened form of import tree for binding idents. */
|
|
3450
4333
|
function flatImports(ast, conditions) {
|
|
3451
4334
|
if (ast._flatImports && !conditions) return ast._flatImports;
|
|
3452
|
-
const importElems = ast.moduleElem
|
|
3453
|
-
const flat = (conditions ? filterValidElements(importElems, conditions) : importElems).
|
|
4335
|
+
const importElems = declsOfKind(ast.moduleElem, "import");
|
|
4336
|
+
const flat = (conditions ? filterValidElements(importElems, conditions) : importElems).flatMap((elem) => flattenTreeImport(elem.imports));
|
|
3454
4337
|
if (!conditions) ast._flatImports = flat;
|
|
3455
4338
|
return flat;
|
|
3456
4339
|
}
|
|
@@ -3747,11 +4630,13 @@ var RecordResolver = class {
|
|
|
3747
4630
|
sources;
|
|
3748
4631
|
packageName;
|
|
3749
4632
|
debugWeslRoot;
|
|
4633
|
+
weslExtensions;
|
|
3750
4634
|
constructor(sources, options = {}) {
|
|
3751
|
-
const { packageName = "package", debugWeslRoot } = options;
|
|
4635
|
+
const { packageName = "package", debugWeslRoot, weslExtensions } = options;
|
|
3752
4636
|
this.sources = sources;
|
|
3753
4637
|
this.packageName = packageName;
|
|
3754
4638
|
this.debugWeslRoot = normalizeDebugRoot(debugWeslRoot);
|
|
4639
|
+
this.weslExtensions = weslExtensions;
|
|
3755
4640
|
}
|
|
3756
4641
|
resolveModule(modulePath) {
|
|
3757
4642
|
const cached = this.astCache.get(modulePath);
|
|
@@ -3762,7 +4647,7 @@ var RecordResolver = class {
|
|
|
3762
4647
|
modulePath,
|
|
3763
4648
|
debugFilePath: this.modulePathToDebugPath(modulePath),
|
|
3764
4649
|
src: source
|
|
3765
|
-
});
|
|
4650
|
+
}, { weslExtensions: this.weslExtensions });
|
|
3766
4651
|
this.astCache.set(modulePath, ast);
|
|
3767
4652
|
return ast;
|
|
3768
4653
|
}
|
|
@@ -4095,12 +4980,13 @@ var SrcMap = class SrcMap {
|
|
|
4095
4980
|
const { src, position: srcStart } = this.destToSrc(e.srcStart);
|
|
4096
4981
|
const { src: endSrc, position: srcEnd } = this.destToSrc(e.srcEnd);
|
|
4097
4982
|
if (endSrc !== src) throw new Error("NYI, need to split");
|
|
4983
|
+
const { destStart, destEnd } = e;
|
|
4098
4984
|
return {
|
|
4099
4985
|
src,
|
|
4100
4986
|
srcStart,
|
|
4101
4987
|
srcEnd,
|
|
4102
|
-
destStart
|
|
4103
|
-
destEnd
|
|
4988
|
+
destStart,
|
|
4989
|
+
destEnd
|
|
4104
4990
|
};
|
|
4105
4991
|
});
|
|
4106
4992
|
const otherSources = other.entries.filter((e) => e.src.path !== this.dest.path || e.src.text !== this.dest.text);
|
|
@@ -4121,10 +5007,6 @@ var SrcMap = class SrcMap {
|
|
|
4121
5007
|
};
|
|
4122
5008
|
}
|
|
4123
5009
|
};
|
|
4124
|
-
/** sort entries in place by src start position */
|
|
4125
|
-
function sortSrc(entries) {
|
|
4126
|
-
entries.sort((a, b) => a.srcStart - b.srcStart);
|
|
4127
|
-
}
|
|
4128
5010
|
/** Incrementally append to a string, tracking source references */
|
|
4129
5011
|
var SrcMapBuilder = class {
|
|
4130
5012
|
#fragments = [];
|
|
@@ -4178,11 +5060,6 @@ var SrcMapBuilder = class {
|
|
|
4178
5060
|
};
|
|
4179
5061
|
this.add("\n", srcStart, srcEnd);
|
|
4180
5062
|
}
|
|
4181
|
-
/** copy a string fragment from the src to the destination string */
|
|
4182
|
-
addCopy(srcStart, srcEnd) {
|
|
4183
|
-
const fragment = this.source.text.slice(srcStart, srcEnd);
|
|
4184
|
-
this.add(fragment, srcStart, srcEnd);
|
|
4185
|
-
}
|
|
4186
5063
|
/** return a SrcMap */
|
|
4187
5064
|
static build(builders) {
|
|
4188
5065
|
const map = new SrcMap({ text: builders.map((b) => b.#fragments.join("")).join("") }, builders.flatMap((b) => b.#entries));
|
|
@@ -4190,6 +5067,10 @@ var SrcMapBuilder = class {
|
|
|
4190
5067
|
return map;
|
|
4191
5068
|
}
|
|
4192
5069
|
};
|
|
5070
|
+
/** sort entries in place by src start position */
|
|
5071
|
+
function sortSrc(entries) {
|
|
5072
|
+
entries.sort((a, b) => a.srcStart - b.srcStart);
|
|
5073
|
+
}
|
|
4193
5074
|
//#endregion
|
|
4194
5075
|
//#region ../wesl/src/Linker.ts
|
|
4195
5076
|
/**
|
|
@@ -4207,10 +5088,12 @@ async function link(params) {
|
|
|
4207
5088
|
/** linker api for benchmarking */
|
|
4208
5089
|
function _linkSync(params) {
|
|
4209
5090
|
const { weslSrc, libs = [], packageName, debugWeslRoot, resolver } = params;
|
|
5091
|
+
const { weslExtensions } = params;
|
|
4210
5092
|
if (!resolver && !weslSrc) throw new Error("Either resolver or weslSrc must be provided");
|
|
4211
5093
|
const allResolvers = [resolver ?? new RecordResolver(weslSrc, {
|
|
4212
5094
|
packageName,
|
|
4213
|
-
debugWeslRoot
|
|
5095
|
+
debugWeslRoot,
|
|
5096
|
+
weslExtensions
|
|
4214
5097
|
}), ...createLibraryResolvers(libs, debugWeslRoot)];
|
|
4215
5098
|
const finalResolver = allResolvers.length === 1 ? allResolvers[0] : new CompositeResolver(allResolvers);
|
|
4216
5099
|
return linkRegistry({
|
|
@@ -4342,9 +5225,6 @@ function builderFromModule(srcModule) {
|
|
|
4342
5225
|
path: srcModule.debugFilePath
|
|
4343
5226
|
});
|
|
4344
5227
|
}
|
|
4345
|
-
matchOneOf(textureStorageTypes);
|
|
4346
|
-
matchOneOf(sampledTextureTypes);
|
|
4347
|
-
matchOneOf(multisampledTextureTypes);
|
|
4348
5228
|
//#endregion
|
|
4349
5229
|
//#region ../wesl/src/WeslDevice.ts
|
|
4350
5230
|
/**
|
|
@@ -4696,7 +5576,7 @@ async function fetchDependencies(rootModuleSource, options) {
|
|
|
4696
5576
|
const existingSources = options?.existingSources;
|
|
4697
5577
|
const fetchLibs = options?.fetchLibs ?? true;
|
|
4698
5578
|
const fetchSources = options?.fetchSources ?? true;
|
|
4699
|
-
const rootModuleName = currentPath ? urlToModulePath(currentPath, shaderRoot) : "package::main";
|
|
5579
|
+
const rootModuleName = options?.rootModuleName ?? (currentPath ? urlToModulePath(currentPath, shaderRoot) : "package::main");
|
|
4700
5580
|
const resolver = new FetchingResolver({
|
|
4701
5581
|
...existingSources,
|
|
4702
5582
|
[rootModuleName]: rootModuleSource
|
|
@@ -4850,24 +5730,33 @@ function findAnnotation(elem, name) {
|
|
|
4850
5730
|
if (attr.kind === "@attribute" && attr.name === name) return attr;
|
|
4851
5731
|
}
|
|
4852
5732
|
}
|
|
4853
|
-
/**
|
|
5733
|
+
/** The string value of each of an annotation's params. */
|
|
4854
5734
|
function annotationParams(attr) {
|
|
4855
|
-
|
|
4856
|
-
return attr.params.map((expr) => exprToString(expr.contents));
|
|
5735
|
+
return attr.params?.map((param) => exprToString(param.expression)) ?? [];
|
|
4857
5736
|
}
|
|
4858
|
-
/** Extract
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
5737
|
+
/** Extract numeric params from an annotation, parsing WGSL numeric literals
|
|
5738
|
+
* (suffixes like `256u`/`1.5f`, digit separators, and hex). */
|
|
5739
|
+
function numericParams(attr) {
|
|
5740
|
+
return annotationParams(attr).map(wgslNumber);
|
|
5741
|
+
}
|
|
5742
|
+
/** The originalName of an attribute parameter that is a bare identifier ref. */
|
|
5743
|
+
function firstRefName(param) {
|
|
5744
|
+
const expr = param?.expression;
|
|
5745
|
+
return expr?.kind === "ref" ? expr.ident.originalName : void 0;
|
|
5746
|
+
}
|
|
5747
|
+
/** Extract the string value of an attribute-parameter expression. */
|
|
5748
|
+
function exprToString(expr) {
|
|
5749
|
+
if (expr.kind === "literal") return expr.value;
|
|
5750
|
+
if (expr.kind === "ref") return expr.ident.originalName;
|
|
4866
5751
|
return "";
|
|
4867
5752
|
}
|
|
4868
|
-
/**
|
|
4869
|
-
|
|
4870
|
-
|
|
5753
|
+
/** Convert a WGSL numeric literal's text to a number. Strips the `u`/`i`/`f`/`h`
|
|
5754
|
+
* suffix (only `u`/`i` for hex, where `f`/`h` are digits) and `_` separators.
|
|
5755
|
+
* Non-numeric text (e.g. an unresolved ref name) yields NaN. */
|
|
5756
|
+
function wgslNumber(text) {
|
|
5757
|
+
const t = text.replace(/_/g, "");
|
|
5758
|
+
if (/^[+-]?0[xX]/.test(t)) return Number(t.replace(/[uiUI]$/, ""));
|
|
5759
|
+
return Number(t.replace(/[uifhUIFH]$/, ""));
|
|
4871
5760
|
}
|
|
4872
5761
|
//#endregion
|
|
4873
5762
|
//#region ../wesl-reflect/src/WeslStructs.ts
|
|
@@ -4923,7 +5812,7 @@ function structLayout(struct, conditions, structs) {
|
|
|
4923
5812
|
/** Build a StructRegistry from all top-level struct declarations in `ast`. */
|
|
4924
5813
|
function buildStructRegistry(ast) {
|
|
4925
5814
|
const reg = /* @__PURE__ */ new Map();
|
|
4926
|
-
for (const e of ast.moduleElem
|
|
5815
|
+
for (const e of declsOfKind(ast.moduleElem, "struct")) reg.set(e.name.ident.originalName, e);
|
|
4927
5816
|
return reg;
|
|
4928
5817
|
}
|
|
4929
5818
|
/** Resolve alignment and size for any host-shareable typeRef (primitive, array, or nested struct). */
|
|
@@ -5341,7 +6230,7 @@ function scalarSize(s) {
|
|
|
5341
6230
|
return s === "f16" ? 2 : 4;
|
|
5342
6231
|
}
|
|
5343
6232
|
function findGlobalVar(ast, varName) {
|
|
5344
|
-
return ast.moduleElem.
|
|
6233
|
+
return declsOfKind(ast.moduleElem, "gvar").find((g) => g.name.decl.ident.originalName === varName);
|
|
5345
6234
|
}
|
|
5346
6235
|
function parseAddressSpace(declText) {
|
|
5347
6236
|
const m = declText.match(/var\s*<\s*([a-z_]+)/);
|
|
@@ -5497,7 +6386,7 @@ function namedTypeShape(name, structs, varName) {
|
|
|
5497
6386
|
function findAnnotatedResources(ast) {
|
|
5498
6387
|
const src = ast.srcModule.src;
|
|
5499
6388
|
const structs = buildStructRegistry(ast);
|
|
5500
|
-
return ast.moduleElem
|
|
6389
|
+
return declsOfKind(ast.moduleElem, "gvar").flatMap((gvar) => discoverResource(gvar, src, structs));
|
|
5501
6390
|
}
|
|
5502
6391
|
/** Linker plugin that decorates @buffer/@test_texture/@sampler globals with
|
|
5503
6392
|
* @group(0) @binding(N) so they emit as bindable WGSL vars.
|
|
@@ -5506,8 +6395,7 @@ function findAnnotatedResources(ast) {
|
|
|
5506
6395
|
function annotatedResourcesPlugin(resources, startBinding = 1) {
|
|
5507
6396
|
const bindingByName = new Map(resources.map((r, i) => [r.varName, startBinding + i]));
|
|
5508
6397
|
return { transform: (ast) => {
|
|
5509
|
-
for (const elem of ast.moduleElem
|
|
5510
|
-
if (elem.kind !== "gvar") continue;
|
|
6398
|
+
for (const elem of declsOfKind(ast.moduleElem, "gvar")) {
|
|
5511
6399
|
const varName = elem.name.decl.ident.originalName;
|
|
5512
6400
|
const binding = bindingByName.get(varName);
|
|
5513
6401
|
if (binding === void 0) continue;
|
|
@@ -5520,11 +6408,6 @@ function annotatedResourcesPlugin(resources, startBinding = 1) {
|
|
|
5520
6408
|
groupAttr,
|
|
5521
6409
|
bindingAttr
|
|
5522
6410
|
];
|
|
5523
|
-
elem.contents = [
|
|
5524
|
-
groupAttr,
|
|
5525
|
-
bindingAttr,
|
|
5526
|
-
...elem.contents
|
|
5527
|
-
];
|
|
5528
6411
|
}
|
|
5529
6412
|
return ast;
|
|
5530
6413
|
} };
|
|
@@ -5533,7 +6416,7 @@ function annotatedResourcesPlugin(resources, startBinding = 1) {
|
|
|
5533
6416
|
function discoverResource(gvar, src, structs) {
|
|
5534
6417
|
if (findAnnotation(gvar, "buffer")) return [discoverBuffer(gvar, src, structs)];
|
|
5535
6418
|
const textureAttr = findAnnotation(gvar, "test_texture");
|
|
5536
|
-
if (textureAttr) return [discoverTexture(gvar, textureAttr
|
|
6419
|
+
if (textureAttr) return [discoverTexture(gvar, textureAttr)];
|
|
5537
6420
|
const userTextureAttr = findAnnotation(gvar, "texture");
|
|
5538
6421
|
if (userTextureAttr) return [discoverUserTexture(gvar, userTextureAttr)];
|
|
5539
6422
|
const samplerAttr = findAnnotation(gvar, "sampler");
|
|
@@ -5571,7 +6454,6 @@ function makeStandardAttr(name, value, anchor) {
|
|
|
5571
6454
|
kind: "attribute",
|
|
5572
6455
|
start,
|
|
5573
6456
|
end,
|
|
5574
|
-
contents: [],
|
|
5575
6457
|
attribute: {
|
|
5576
6458
|
kind: "@attribute",
|
|
5577
6459
|
name,
|
|
@@ -5579,12 +6461,12 @@ function makeStandardAttr(name, value, anchor) {
|
|
|
5579
6461
|
kind: "expression",
|
|
5580
6462
|
start,
|
|
5581
6463
|
end,
|
|
5582
|
-
|
|
6464
|
+
expression: {
|
|
5583
6465
|
kind: "literal",
|
|
5584
6466
|
value: String(value),
|
|
5585
6467
|
start,
|
|
5586
6468
|
end
|
|
5587
|
-
}
|
|
6469
|
+
}
|
|
5588
6470
|
}]
|
|
5589
6471
|
}
|
|
5590
6472
|
};
|
|
@@ -5600,14 +6482,12 @@ function discoverBuffer(gvar, src, structs) {
|
|
|
5600
6482
|
byteSize: typeRef ? typeShape(typeRef, structs, varName).size : 0
|
|
5601
6483
|
};
|
|
5602
6484
|
}
|
|
5603
|
-
function discoverTexture(gvar, attr
|
|
5604
|
-
const varName = gvar.name.decl.ident.originalName;
|
|
5605
|
-
const params = attr.params ?? [];
|
|
6485
|
+
function discoverTexture(gvar, attr) {
|
|
5606
6486
|
return {
|
|
5607
6487
|
kind: "test_texture",
|
|
5608
|
-
varName,
|
|
5609
|
-
source: firstRefName(params[0]) ?? "",
|
|
5610
|
-
params:
|
|
6488
|
+
varName: gvar.name.decl.ident.originalName,
|
|
6489
|
+
source: firstRefName(attr.params?.[0]) ?? "",
|
|
6490
|
+
params: numericParams(attr).slice(1).map((n) => n || 0),
|
|
5611
6491
|
typeName: gvar.name.typeRef ? originalTypeName(gvar.name.typeRef) : ""
|
|
5612
6492
|
};
|
|
5613
6493
|
}
|
|
@@ -5626,11 +6506,6 @@ function discoverSampler(gvar, attr) {
|
|
|
5626
6506
|
filter: (firstRefName(attr.params?.[0]) ?? "linear") === "nearest" ? "nearest" : "linear"
|
|
5627
6507
|
};
|
|
5628
6508
|
}
|
|
5629
|
-
/** Extract the originalName of the first `ref` expression in an attribute parameter. */
|
|
5630
|
-
function firstRefName(param) {
|
|
5631
|
-
const ref = param?.contents.find((c) => c.kind === "ref");
|
|
5632
|
-
return ref?.kind === "ref" ? ref.ident.originalName : void 0;
|
|
5633
|
-
}
|
|
5634
6509
|
//#endregion
|
|
5635
6510
|
//#region ../wesl-reflect/src/EntryPoints.ts
|
|
5636
6511
|
const stageNames = [
|
|
@@ -5640,7 +6515,7 @@ const stageNames = [
|
|
|
5640
6515
|
];
|
|
5641
6516
|
/** Classify all functions in a parsed WESL module by entry-point stage. */
|
|
5642
6517
|
function classifyEntryPoints(ast) {
|
|
5643
|
-
return ast.moduleElem
|
|
6518
|
+
return declsOfKind(ast.moduleElem, "fn").flatMap((fn) => entryPointFor(fn));
|
|
5644
6519
|
}
|
|
5645
6520
|
function entryPointFor(fn) {
|
|
5646
6521
|
const stage = stageNames.find((s) => findAnnotation(fn, s));
|
|
@@ -5704,11 +6579,11 @@ function scanUniforms(entrySource, rootModulePath = "package::main") {
|
|
|
5704
6579
|
}
|
|
5705
6580
|
/** Parse source and find a struct annotated with @uniforms. */
|
|
5706
6581
|
function findUniformsStruct(src, modulePath) {
|
|
5707
|
-
return parseSrcModule({
|
|
6582
|
+
return declsOfKind(parseSrcModule({
|
|
5708
6583
|
modulePath,
|
|
5709
6584
|
debugFilePath: "entry",
|
|
5710
6585
|
src
|
|
5711
|
-
}).moduleElem
|
|
6586
|
+
}).moduleElem, "struct").find(isUniformsStruct);
|
|
5712
6587
|
}
|
|
5713
6588
|
//#endregion
|
|
5714
6589
|
//#region ../wesl-gpu/src/LinkWeslModule.ts
|
|
@@ -5717,8 +6592,11 @@ function findUniformsStruct(src, modulePath) {
|
|
|
5717
6592
|
async function linkWeslModule(params) {
|
|
5718
6593
|
const { rootSource, scanSource, conditions, constants } = params;
|
|
5719
6594
|
const { device, resolver, libs = [], rootModuleName = "main" } = params;
|
|
5720
|
-
const { packageName, config, weslSrc, virtualLibs } = params;
|
|
5721
|
-
const resolvers = [new RecordResolver({ [rootModuleName]: rootSource }, {
|
|
6595
|
+
const { packageName, config, weslSrc, virtualLibs, weslExtensions } = params;
|
|
6596
|
+
const resolvers = [new RecordResolver({ [rootModuleName]: rootSource }, {
|
|
6597
|
+
packageName,
|
|
6598
|
+
weslExtensions
|
|
6599
|
+
})];
|
|
5722
6600
|
if (weslSrc) resolvers.push(new RecordResolver(weslSrc, { packageName }));
|
|
5723
6601
|
let finalResolver = resolvers.length === 1 ? resolvers[0] : new CompositeResolver(resolvers);
|
|
5724
6602
|
if (resolver) finalResolver = new CompositeResolver([finalResolver, resolver]);
|
|
@@ -5758,6 +6636,17 @@ async function linkComputeShader(params) {
|
|
|
5758
6636
|
async function runCompute(p) {
|
|
5759
6637
|
return withErrorScopes(p.device, () => dispatchAndReadback(p));
|
|
5760
6638
|
}
|
|
6639
|
+
/** Record one compute pass into the caller's encoder; the caller submits.
|
|
6640
|
+
* Used by interpreters that need many passes in a single command buffer. */
|
|
6641
|
+
function recordComputePass(p) {
|
|
6642
|
+
const dispatch = p.dispatchWorkgroups ?? 1;
|
|
6643
|
+
const pass = p.encoder.beginComputePass();
|
|
6644
|
+
pass.setPipeline(p.pipeline);
|
|
6645
|
+
pass.setBindGroup(0, p.bindGroup);
|
|
6646
|
+
if (typeof dispatch === "number") pass.dispatchWorkgroups(dispatch);
|
|
6647
|
+
else pass.dispatchWorkgroups(...dispatch);
|
|
6648
|
+
pass.end();
|
|
6649
|
+
}
|
|
5761
6650
|
/** Clear each buffer to zeros so re-runs / re-tests see deterministic initial state. */
|
|
5762
6651
|
function clearBuffers(device, buffers) {
|
|
5763
6652
|
const list = [...buffers];
|
|
@@ -5770,7 +6659,6 @@ function clearBuffers(device, buffers) {
|
|
|
5770
6659
|
* buffer, and return the mapped contents. */
|
|
5771
6660
|
async function dispatchAndReadback(p) {
|
|
5772
6661
|
const { device, module, entryPoint, bindGroup, pipelineLayout } = p;
|
|
5773
|
-
const dispatch = p.dispatchWorkgroups ?? 1;
|
|
5774
6662
|
const pipeline = device.createComputePipeline({
|
|
5775
6663
|
layout: pipelineLayout,
|
|
5776
6664
|
compute: {
|
|
@@ -5781,12 +6669,12 @@ async function dispatchAndReadback(p) {
|
|
|
5781
6669
|
const stagingBuffers = /* @__PURE__ */ new Map();
|
|
5782
6670
|
for (const [name, src] of p.readBuffers) stagingBuffers.set(name, createStagingBuffer(device, src.size, name));
|
|
5783
6671
|
const encoder = device.createCommandEncoder();
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
6672
|
+
recordComputePass({
|
|
6673
|
+
encoder,
|
|
6674
|
+
pipeline,
|
|
6675
|
+
bindGroup,
|
|
6676
|
+
dispatchWorkgroups: p.dispatchWorkgroups
|
|
6677
|
+
});
|
|
5790
6678
|
for (const [name, src] of p.readBuffers) {
|
|
5791
6679
|
const dst = stagingBuffers.get(name);
|
|
5792
6680
|
encoder.copyBufferToBuffer(src, 0, dst, 0, src.size);
|
|
@@ -6744,7 +7632,7 @@ async function createPipeline(state, shaderSource, resolveTexture, options) {
|
|
|
6744
7632
|
modulePath: `${pkg}::${root}`,
|
|
6745
7633
|
debugFilePath: `./${root}.wesl`,
|
|
6746
7634
|
src: shaderSource
|
|
6747
|
-
});
|
|
7635
|
+
}, { weslExtensions: options?.weslExtensions });
|
|
6748
7636
|
const resources = findAnnotatedResources(ast);
|
|
6749
7637
|
const entryPoints = classifyEntryPoints(ast);
|
|
6750
7638
|
const mode = detectMode(entryPoints);
|
|
@@ -7115,7 +8003,7 @@ var WgslPlay = class extends HTMLElement {
|
|
|
7115
8003
|
_weslSrc = {};
|
|
7116
8004
|
_rootModuleName = "package::main";
|
|
7117
8005
|
_libs;
|
|
7118
|
-
_linkOptions = {};
|
|
8006
|
+
_linkOptions = { weslExtensions: { doBlocks: true } };
|
|
7119
8007
|
_fetchSources = true;
|
|
7120
8008
|
_initPromise;
|
|
7121
8009
|
_sourceEl = null;
|
|
@@ -7261,10 +8149,11 @@ var WgslPlay = class extends HTMLElement {
|
|
|
7261
8149
|
/** Set project configuration (mirrors wesl link() API). */
|
|
7262
8150
|
set project(value) {
|
|
7263
8151
|
const { weslSrc, rootModuleName, libs } = value;
|
|
7264
|
-
const { packageName, conditions, constants } = value;
|
|
8152
|
+
const { packageName, conditions, constants, weslExtensions } = value;
|
|
7265
8153
|
if (packageName !== void 0) this._linkOptions.packageName = packageName;
|
|
7266
8154
|
if (conditions !== void 0) this._linkOptions.conditions = conditions;
|
|
7267
8155
|
if (constants !== void 0) this._linkOptions.constants = constants;
|
|
8156
|
+
if (weslExtensions !== void 0) this._linkOptions.weslExtensions = weslExtensions;
|
|
7268
8157
|
if (libs) this._libs = libs;
|
|
7269
8158
|
if (weslSrc) {
|
|
7270
8159
|
const pkg = this._linkOptions.packageName || "package";
|
|
@@ -7612,6 +8501,7 @@ var WgslPlay = class extends HTMLElement {
|
|
|
7612
8501
|
if (this._fetchSources || this._fetchLibs) {
|
|
7613
8502
|
const { weslSrc, libs } = await fetchDependencies(mainSource, {
|
|
7614
8503
|
shaderRoot: this.getConfigOverrides()?.shaderRoot,
|
|
8504
|
+
rootModuleName: this._rootModuleName,
|
|
7615
8505
|
existingSources: this._weslSrc,
|
|
7616
8506
|
fetchLibs: this._fetchLibs,
|
|
7617
8507
|
fetchSources: this._fetchSources
|