wesl 0.6.0-pre10

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.
Files changed (141) hide show
  1. package/README.md +31 -0
  2. package/dist/index.js +4468 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/minified.js +3426 -0
  5. package/dist/minified.js.map +1 -0
  6. package/dist/tools/packages/wesl/src/AbstractElems.d.ts +322 -0
  7. package/dist/tools/packages/wesl/src/Assertions.d.ts +27 -0
  8. package/dist/tools/packages/wesl/src/BindIdents.d.ts +70 -0
  9. package/dist/tools/packages/wesl/src/Conditions.d.ts +6 -0
  10. package/dist/tools/packages/wesl/src/FlattenTreeImport.d.ts +11 -0
  11. package/dist/tools/packages/wesl/src/LinkedWesl.d.ts +50 -0
  12. package/dist/tools/packages/wesl/src/Linker.d.ts +87 -0
  13. package/dist/tools/packages/wesl/src/LinkerUtil.d.ts +3 -0
  14. package/dist/tools/packages/wesl/src/LiveDeclarations.d.ts +12 -0
  15. package/dist/tools/packages/wesl/src/LowerAndEmit.d.ts +31 -0
  16. package/dist/tools/packages/wesl/src/Mangler.d.ts +39 -0
  17. package/dist/tools/packages/wesl/src/ParseWESL.d.ts +60 -0
  18. package/dist/tools/packages/wesl/src/ParsedRegistry.d.ts +29 -0
  19. package/dist/tools/packages/wesl/src/PathUtil.d.ts +6 -0
  20. package/dist/tools/packages/wesl/src/RawEmit.d.ts +6 -0
  21. package/dist/tools/packages/wesl/src/Reflection.d.ts +45 -0
  22. package/dist/tools/packages/wesl/src/Scope.d.ts +81 -0
  23. package/dist/tools/packages/wesl/src/StandardTypes.d.ts +13 -0
  24. package/dist/tools/packages/wesl/src/TransformBindingStructs.d.ts +52 -0
  25. package/dist/tools/packages/wesl/src/Util.d.ts +43 -0
  26. package/dist/tools/packages/wesl/src/WESLCollect.d.ts +94 -0
  27. package/dist/tools/packages/wesl/src/WeslBundle.d.ts +13 -0
  28. package/dist/tools/packages/wesl/src/WeslDevice.d.ts +25 -0
  29. package/dist/tools/packages/wesl/src/debug/ASTtoString.d.ts +5 -0
  30. package/dist/tools/packages/wesl/src/debug/ImportToString.d.ts +2 -0
  31. package/dist/tools/packages/wesl/src/debug/LineWrapper.d.ts +21 -0
  32. package/dist/tools/packages/wesl/src/debug/ScopeToString.d.ts +6 -0
  33. package/dist/tools/packages/wesl/src/index.d.ts +11 -0
  34. package/dist/tools/packages/wesl/src/parse/ImportGrammar.d.ts +5 -0
  35. package/dist/tools/packages/wesl/src/parse/Keywords.d.ts +4 -0
  36. package/dist/tools/packages/wesl/src/parse/WeslBaseGrammar.d.ts +5 -0
  37. package/dist/tools/packages/wesl/src/parse/WeslExpression.d.ts +13 -0
  38. package/dist/tools/packages/wesl/src/parse/WeslGrammar.d.ts +80 -0
  39. package/dist/tools/packages/wesl/src/parse/WeslStream.d.ts +44 -0
  40. package/dist/tools/packages/wesl/src/test/BindWESL.test.d.ts +1 -0
  41. package/dist/tools/packages/wesl/src/test/ConditionLinking.test.d.ts +1 -0
  42. package/dist/tools/packages/wesl/src/test/ConditionalTranslationCases.test.d.ts +1 -0
  43. package/dist/tools/packages/wesl/src/test/ErrorLogging.test.d.ts +1 -0
  44. package/dist/tools/packages/wesl/src/test/Expression.test.d.ts +1 -0
  45. package/dist/tools/packages/wesl/src/test/FlattenTreeImport.test.d.ts +1 -0
  46. package/dist/tools/packages/wesl/src/test/ImportCases.test.d.ts +1 -0
  47. package/dist/tools/packages/wesl/src/test/ImportSyntaxCases.test.d.ts +1 -0
  48. package/dist/tools/packages/wesl/src/test/LinkGlob.test.d.ts +1 -0
  49. package/dist/tools/packages/wesl/src/test/LinkPackage.test.d.ts +1 -0
  50. package/dist/tools/packages/wesl/src/test/Linker.test.d.ts +1 -0
  51. package/dist/tools/packages/wesl/src/test/Mangling.test.d.ts +1 -0
  52. package/dist/tools/packages/wesl/src/test/ParseComments.test.d.ts +1 -0
  53. package/dist/tools/packages/wesl/src/test/ParseConditions.test.d.ts +1 -0
  54. package/dist/tools/packages/wesl/src/test/ParseError.test.d.ts +1 -0
  55. package/dist/tools/packages/wesl/src/test/ParseWESL.test.d.ts +1 -0
  56. package/dist/tools/packages/wesl/src/test/PathUtil.test.d.ts +1 -0
  57. package/dist/tools/packages/wesl/src/test/PrettyGrammar.test.d.ts +1 -0
  58. package/dist/tools/packages/wesl/src/test/Reflection.test.d.ts +1 -0
  59. package/dist/tools/packages/wesl/src/test/ScopeWESL.test.d.ts +1 -0
  60. package/dist/tools/packages/wesl/src/test/TestLink.d.ts +21 -0
  61. package/dist/tools/packages/wesl/src/test/TestSetup.d.ts +1 -0
  62. package/dist/tools/packages/wesl/src/test/TestUtil.d.ts +40 -0
  63. package/dist/tools/packages/wesl/src/test/Tokenizer.test.d.ts +1 -0
  64. package/dist/tools/packages/wesl/src/test/TransformBindingStructs.test.d.ts +1 -0
  65. package/dist/tools/packages/wesl/src/test/Util.test.d.ts +1 -0
  66. package/dist/tools/packages/wesl/src/test/VirtualModules.test.d.ts +1 -0
  67. package/dist/tools/packages/wesl/src/test/WeslDevice.test.d.ts +1 -0
  68. package/dist/tools/packages/wesl/src/test/WgslTests.d.ts +0 -0
  69. package/dist/tools/packages/wesl/src/vlq/vlq.d.ts +11 -0
  70. package/package.json +46 -0
  71. package/src/AbstractElems.ts +446 -0
  72. package/src/Assertions.ts +51 -0
  73. package/src/BindIdents.ts +523 -0
  74. package/src/Conditions.ts +74 -0
  75. package/src/FlattenTreeImport.ts +55 -0
  76. package/src/LinkedWesl.ts +184 -0
  77. package/src/Linker.ts +284 -0
  78. package/src/LinkerUtil.ts +29 -0
  79. package/src/LiveDeclarations.ts +31 -0
  80. package/src/LowerAndEmit.ts +413 -0
  81. package/src/Mangler.ts +94 -0
  82. package/src/ParseWESL.ts +157 -0
  83. package/src/ParsedRegistry.ts +120 -0
  84. package/src/PathUtil.ts +31 -0
  85. package/src/RawEmit.ts +102 -0
  86. package/src/Reflection.ts +334 -0
  87. package/src/Scope.ts +162 -0
  88. package/src/StandardTypes.ts +97 -0
  89. package/src/TransformBindingStructs.ts +319 -0
  90. package/src/Util.ts +194 -0
  91. package/src/WESLCollect.ts +614 -0
  92. package/src/WeslBundle.ts +16 -0
  93. package/src/WeslDevice.ts +209 -0
  94. package/src/debug/ASTtoString.ts +290 -0
  95. package/src/debug/ImportToString.ts +29 -0
  96. package/src/debug/LineWrapper.ts +70 -0
  97. package/src/debug/ScopeToString.ts +79 -0
  98. package/src/index.ts +11 -0
  99. package/src/parse/ImportGrammar.ts +157 -0
  100. package/src/parse/Keywords.ts +26 -0
  101. package/src/parse/WeslBaseGrammar.ts +8 -0
  102. package/src/parse/WeslExpression.ts +207 -0
  103. package/src/parse/WeslGrammar.ts +856 -0
  104. package/src/parse/WeslStream.ts +279 -0
  105. package/src/test/BindWESL.test.ts +57 -0
  106. package/src/test/ConditionLinking.test.ts +91 -0
  107. package/src/test/ConditionalTranslationCases.test.ts +56 -0
  108. package/src/test/ErrorLogging.test.ts +30 -0
  109. package/src/test/Expression.test.ts +22 -0
  110. package/src/test/FlattenTreeImport.test.ts +74 -0
  111. package/src/test/ImportCases.test.ts +56 -0
  112. package/src/test/ImportSyntaxCases.test.ts +24 -0
  113. package/src/test/LinkGlob.test.ts +25 -0
  114. package/src/test/LinkPackage.test.ts +26 -0
  115. package/src/test/Linker.test.ts +125 -0
  116. package/src/test/Mangling.test.ts +45 -0
  117. package/src/test/ParseComments.test.ts +36 -0
  118. package/src/test/ParseConditions.test.ts +183 -0
  119. package/src/test/ParseError.test.ts +36 -0
  120. package/src/test/ParseWESL.test.ts +1572 -0
  121. package/src/test/PathUtil.test.ts +34 -0
  122. package/src/test/PrettyGrammar.test.ts +20 -0
  123. package/src/test/Reflection.test.ts +172 -0
  124. package/src/test/ScopeWESL.test.ts +462 -0
  125. package/src/test/TestLink.ts +82 -0
  126. package/src/test/TestSetup.ts +4 -0
  127. package/src/test/TestUtil.ts +126 -0
  128. package/src/test/Tokenizer.test.ts +135 -0
  129. package/src/test/TransformBindingStructs.test.ts +230 -0
  130. package/src/test/Util.test.ts +22 -0
  131. package/src/test/VirtualModules.test.ts +37 -0
  132. package/src/test/WeslDevice.test.ts +265 -0
  133. package/src/test/WgslTests.ts +0 -0
  134. package/src/test/__snapshots__/ParseDirectives.test.ts.snap +25 -0
  135. package/src/test/__snapshots__/ParseWESL.test.ts.snap +119 -0
  136. package/src/test/__snapshots__/RustDirective.test.ts.snap +359 -0
  137. package/src/test/wgsl_1/main.wgsl +3 -0
  138. package/src/test/wgsl_1/util.wgsl +1 -0
  139. package/src/test/wgsl_2/main2.wgsl +3 -0
  140. package/src/test/wgsl_2/util2.wgsl +1 -0
  141. package/src/vlq/vlq.ts +94 -0
package/src/Scope.ts ADDED
@@ -0,0 +1,162 @@
1
+ import { DeclarationElem, IfAttribute, RefIdentElem } from "./AbstractElems.ts";
2
+ import { assertThatDebug } from "./Assertions.ts";
3
+ import { scopeValid } from "./Conditions.ts";
4
+ import { WeslAST } from "./ParseWESL.ts";
5
+
6
+ export interface SrcModule {
7
+ /** module path "rand_pkg::sub::foo", or "package::main" */
8
+ modulePath: string;
9
+
10
+ /** file path to the module for user error reporting e.g "rand_pkg:sub/foo.wesl", or "./sub/foo.wesl" */
11
+ debugFilePath: string;
12
+
13
+ /** original src for module */
14
+ src: string;
15
+ }
16
+
17
+ /** a src declaration or reference to an ident */
18
+ export type Ident = DeclIdent | RefIdent;
19
+
20
+ /** LATER change this to a Map, so that `toString` isn't accidentally a condition */
21
+ export type Conditions = Record<string, boolean>;
22
+
23
+ interface IdentBase {
24
+ originalName: string; // name in the source code for ident matching (may be mangled in the output)
25
+ id?: number; // for debugging
26
+ }
27
+
28
+ export interface RefIdent extends IdentBase {
29
+ kind: "ref";
30
+
31
+ // LATER these fields are set during binding, not parsing. Make a naming scheme _refersTo or a separate interface (BindingRefIdent) to make that clear
32
+ refersTo?: Ident; // import or decl ident in scope to which this ident refers. undefined before binding
33
+ std?: true; // true if this is a standard wgsl identifier (like sin, or u32)
34
+
35
+ // TODO consider tracking the current ast in BindIdents so that this field is unnecessary
36
+ ast: WeslAST; // AST from module that contains this ident (to find imports during decl binding)
37
+
38
+ refIdentElem: RefIdentElem; // for error reporting and mangling
39
+ }
40
+
41
+ export interface DeclIdent extends IdentBase {
42
+ kind: "decl";
43
+ mangledName?: string; // name in the output code
44
+ declElem?: DeclarationElem; // link to AST so that we can traverse scopes and know what elems to emit // LATER make separate GlobalDecl kind with this required
45
+ scope: Scope; // scope for the references within this declaration
46
+ isGlobal: boolean; // true if this is a global declaration (e.g. not a local variable)
47
+ srcModule: SrcModule; // To figure out which module this declaration is from.
48
+ }
49
+
50
+ /** tree of ident references, organized by lexical scope and partialScope . */
51
+ export type Scope = LexicalScope | PartialScope;
52
+
53
+ /** A wgsl scope */
54
+ export interface LexicalScope extends ScopeBase {
55
+ kind: "scope";
56
+
57
+ /** @if condition for conditionally translating this scope */
58
+ ifAttribute?: IfAttribute;
59
+
60
+ /**
61
+ * Efficient access to declarations in this scope.
62
+ * constructed on demand, for module root scopes only */ // LATER consider make a special kind for root scopes
63
+ scopeDecls?: Map<string, DeclIdent>;
64
+ }
65
+
66
+ /** A synthetic partial scope to contain @if conditioned idents.
67
+ * PartialScope idents are considered to be in the wgsl lexical scope of their parent. */
68
+ export interface PartialScope extends ScopeBase {
69
+ kind: "partial";
70
+
71
+ /** @if condition for conditionally translating this scope */
72
+ ifAttribute?: IfAttribute; // LATER this is required, consider changing type to reflect that
73
+ }
74
+
75
+ /** common scope elements */
76
+ interface ScopeBase {
77
+ /** id for debugging */
78
+ id: number;
79
+
80
+ /** null for root scope in a module */
81
+ parent: Scope | null;
82
+
83
+ /* Child scopes and idents in lexical order */
84
+ contents: (Ident | Scope)[];
85
+
86
+ /** @if conditions for conditionally translating this scope */
87
+ ifAttribute?: IfAttribute;
88
+ }
89
+
90
+ /** Combine two scope siblings.
91
+ * The first scope is mutated to append the contents of the second. */
92
+ export function mergeScope(a: Scope, b: Scope): void {
93
+ assertThatDebug(a.kind === b.kind);
94
+ assertThatDebug(a.parent === b.parent);
95
+ assertThatDebug(!b.ifAttribute);
96
+ a.contents = a.contents.concat(b.contents);
97
+ }
98
+
99
+ /** reset scope and ident debugging ids */
100
+ export function resetScopeIds() {
101
+ scopeId = 0;
102
+ identId = 0;
103
+ }
104
+
105
+ let scopeId = 0;
106
+ let identId = 0;
107
+
108
+ export function nextIdentId(): number {
109
+ return identId++;
110
+ }
111
+
112
+ /** make a new Scope object */
113
+ export function emptyScope(
114
+ parent: Scope | null,
115
+ kind: Scope["kind"] = "scope",
116
+ ): Omit<Scope, "ifAttribute"> {
117
+ const id = scopeId++;
118
+ return { id, kind, parent, contents: [] };
119
+ }
120
+
121
+ /** For debugging,
122
+ * @return true if a scope is in the rootScope tree somewhere */
123
+ export function containsScope(rootScope: Scope, scope: Scope): boolean {
124
+ if (scope === rootScope) {
125
+ return true;
126
+ }
127
+ for (const child of rootScope.contents) {
128
+ if (childScope(child) && containsScope(child, scope)) {
129
+ return true;
130
+ }
131
+ }
132
+ return false;
133
+ }
134
+
135
+ /** @returns true if the provided element of a Scope
136
+ * is itself a Scope (and not an Ident) */
137
+ export function childScope(child: Scope | Ident): child is Scope {
138
+ const { kind } = child;
139
+ return kind === "partial" || kind === "scope";
140
+ }
141
+
142
+ /** @returns true if the provided element of a Scope
143
+ * is an Ident (and not a child Scope) */
144
+ export function childIdent(child: Scope | Ident): child is Ident {
145
+ return !childScope(child);
146
+ }
147
+
148
+ /** find a public declaration with the given original name */
149
+ export function publicDecl(
150
+ scope: Scope,
151
+ name: string,
152
+ conditions: Conditions,
153
+ ): DeclIdent | undefined {
154
+ for (const elem of scope.contents) {
155
+ if (elem.kind === "decl" && elem.originalName === name) {
156
+ return elem;
157
+ } else if (elem.kind === "partial" && scopeValid(elem, conditions)) {
158
+ const found = publicDecl(elem, name, conditions);
159
+ if (found) return found;
160
+ }
161
+ }
162
+ }
@@ -0,0 +1,97 @@
1
+ // From https://www.w3.org/TR/WGSL/#predeclared
2
+ // Use https://github.com/wgsl-tooling-wg/wgsl-spec to regenerate these list in the future
3
+
4
+ export const stdFns = `bitcast all any select arrayLength
5
+ abs acos acosh asin asinh atan atanh atan2 ceil clamp cos cosh
6
+ countLeadingZeros countOneBits countTrailingZeros cross
7
+ degrees determinant distance dot dot4U8Packed dot4I8Packed
8
+ exp exp2 extractBits faceForward firstLeadingBit firstTrailingBit
9
+ floor fma fract frexp insertBits inverseSqrt ldexp length log log2
10
+ max min mix modf normalize pow quantizeToF16 radians reflect refract
11
+ reverseBits round saturate sign sin sinh smoothstep sqrt step tan tanh
12
+ transpose trunc
13
+ dpdx dpdxCoarse dpdxFine dpdy dpdyCoarse dpdyFine fwidth
14
+ fwidthCoarse fwidthFine
15
+ textureDimensions textureGather textureGatherCompare textureLoad
16
+ textureNumLayers textureNumLevels textureNumSamples
17
+ textureSample textureSampleBias textureSampleCompare textureSampleCompareLevel
18
+ textureSampleGrad textureSampleLevel textureSampleBaseClampToEdge
19
+ textureStore
20
+ atomicLoad atomicStore atomicAdd atomicSub atomicMax atomicMin
21
+ atomicAnd atomicOr atomicXor atomicExchange atomicCompareExchangeWeak
22
+ pack4x8snorm pack4x8unorm pack4xI8 pack4xU8 pack4xI8Clamp pack4xU8Clamp
23
+ pack2x16snorm pack2x16unorm pack2x16float
24
+ unpack4x8snorm unpack4x8unorm unpack4xI8 unpack4xU8
25
+ unpack2x16snorm unpack2x16unorm unpack2x16float
26
+ storageBarrier textureBarrier workgroupBarrier workgroupUniformLoad
27
+ subgroupAdd subgroupAll subgroupAnd subgroupAny subgroupBallot
28
+ subgroupBroadcast subgroupBroadcastFirst subgroupElect
29
+ subgroupExclusiveAdd subgroupExclusiveMul subgroupInclusiveAdd
30
+ subgroupInclusiveMul subgroupMax subgroupMin subgroupMul subgroupOr
31
+ subgroupShuffle subgroupShuffleUp subgroupShuffleXor subgroupXor
32
+ quadBroadcast quadSwapDiagonal quadSwapX quadSwapY`.split(/\s+/);
33
+
34
+ export const sampledTextureTypes = `
35
+ texture_1d texture_2d texture_2d_array texture_3d
36
+ texture_cube texture_cube_array
37
+ `;
38
+
39
+ export const multisampledTextureTypes = `
40
+ texture_multisampled_2d texture_depth_multisampled_2d
41
+ `;
42
+
43
+ export const textureStorageTypes = `
44
+ texture_storage_1d texture_storage_2d texture_storage_2d_array
45
+ texture_storage_3d
46
+ `;
47
+
48
+ export const stdTypes = `array atomic bool f16 f32 i32
49
+ mat2x2 mat2x3 mat2x4 mat3x2 mat3x3 mat3x4 mat4x2 mat4x3 mat4x4
50
+ mat2x2f mat2x3f mat2x4f mat3x2f mat3x3f mat3x4f
51
+ mat4x2f mat4x3f mat4x4f
52
+ mat2x2h mat2x3h mat2x4h mat3x2h mat3x3h mat3x4h
53
+ mat4x2h mat4x3h mat4x4h
54
+ u32 vec2 vec3 vec4 ptr
55
+ vec2i vec3i vec4i vec2u vec3u vec4u
56
+ vec2f vec3f vec4f vec2h vec3h vec4h
57
+ ${sampledTextureTypes}
58
+ ${multisampledTextureTypes}
59
+ texture_external
60
+ ${textureStorageTypes}
61
+ texture_depth_2d texture_depth_2d_array texture_depth_cube
62
+ texture_depth_cube_array
63
+ sampler sampler_comparison
64
+ rgba8unorm rgba8snorm rgba8uint rgba8sint
65
+ rgba16uint rgba16sint rgba16float
66
+ r32uint r32sint r32float rg32uint rg32sint rg32float
67
+ rgba32uint rgba32sint rgba32float
68
+ bgra8unorm`.split(/\s+/);
69
+
70
+ /** https://www.w3.org/TR/WGSL/#predeclared-enumerants */
71
+ export const stdEnumerants = `read write read_write
72
+ function private workgroup uniform storage
73
+ rgba8unorm rgba8snorm rgba8uint rgba8sint
74
+ rgba16uint rgba16sint rgba16float
75
+ r32uint r32sint r32float rg32uint rg32sint rg32float
76
+ rgba32uint rgba32sint rgba32float bgra8unorm`.split(/\s+/);
77
+
78
+ /* Note the texel formats like rgba8unorm are here because they appear in type position
79
+ in <templates> for texture_storage_* types.
80
+ (We could parse texture_storage types specially, but user code is unlikely to alias
81
+ the texture format names with e.g. a 'struct rbga8unorm .)
82
+ */
83
+
84
+ /** return true if the name is for a built in type (not a user struct) */
85
+ export function stdType(name: string): boolean {
86
+ return stdTypes.includes(name);
87
+ }
88
+
89
+ /** return true if the name is for a built in fn (not a user function) */
90
+ export function stdFn(name: string): boolean {
91
+ return stdFns.includes(name) || stdType(name);
92
+ }
93
+
94
+ /** return true if the name is for a built in enumerant */
95
+ export function stdEnumerant(name: string): boolean {
96
+ return stdEnumerants.includes(name);
97
+ }
@@ -0,0 +1,319 @@
1
+ import { tracing } from "mini-parse";
2
+ import {
3
+ AbstractElem,
4
+ AttributeElem,
5
+ BindingStructElem,
6
+ DeclarationElem,
7
+ FnElem,
8
+ ModuleElem,
9
+ SimpleMemberRef,
10
+ StructElem,
11
+ StructMemberElem,
12
+ SyntheticElem,
13
+ TypeTemplateParameter,
14
+ } from "./AbstractElems.ts";
15
+ import { TransformedAST, WeslJsPlugin } from "./Linker.ts";
16
+ import { visitAst } from "./LinkerUtil.ts";
17
+ import { findDecl } from "./LowerAndEmit.ts";
18
+ import { minimallyMangledName } from "./Mangler.ts";
19
+ import {
20
+ attributeToString,
21
+ contentsToString,
22
+ typeListToString,
23
+ typeParamToString,
24
+ } from "./RawEmit.ts";
25
+ import { textureStorage } from "./Reflection.ts";
26
+ import { DeclIdent, RefIdent } from "./Scope.ts";
27
+ import { filterMap } from "./Util.ts";
28
+
29
+ export function bindingStructsPlugin(): WeslJsPlugin {
30
+ return {
31
+ transform: lowerBindingStructs,
32
+ };
33
+ }
34
+
35
+ /**
36
+ * Transform binding structures into binding variables by mutating the AST.
37
+ *
38
+ * First we find all the binding structs:
39
+ * . find all the structs in the module by filtering the moduleElem.contents
40
+ * . for each struct:
41
+ * . mark any structs with that contain @group or @binding annotations as 'binding structs' and save them in a list
42
+ * . (later) create reverse links from structs to struct members
43
+ * . (later) visit all the binding structs and traverse to referencing structs, marking the referencing structs as binding structs too
44
+ * Generate synethic AST nodes for binding variables
45
+ *
46
+ * Find all references to binding struct members
47
+ * . find the componound idents by traversing moduleElem.contents
48
+ * . filter to find the compound idents that refer to 'binding structs'
49
+ * . go from each ident to its declaration,
50
+ * . declaration to typeRef reference
51
+ * . typeRef to type declaration
52
+ * . check type declaration to see if it's a binding struct
53
+ * . record the intermediate declaration (e.g. a fn param b:Bindings from 'fn(b:Bindings)' )
54
+ * rewrite references to binding struct members as synthetic elements
55
+ *
56
+ * Remove the binding structs from the AST
57
+ * Remove the intermediate fn param declarations from the AST
58
+ * Add the new binding variables to the AST
59
+ *
60
+ * @return the binding structs and the mutated AST
61
+ */
62
+ export function lowerBindingStructs(ast: TransformedAST): TransformedAST {
63
+ const clonedAst = structuredClone(ast);
64
+ const { moduleElem, globalNames, notableElems } = clonedAst;
65
+ const bindingStructs = markBindingStructs(moduleElem); // CONSIDER should we only mark bining structs referenced from the entry point?
66
+ markEntryTypes(moduleElem, bindingStructs);
67
+ const newVars = bindingStructs.flatMap(s =>
68
+ transformBindingStruct(s, globalNames),
69
+ );
70
+ const bindingRefs = findRefsToBindingStructs(moduleElem);
71
+
72
+ // convert references 'b.particles' to references to the synthetic var 'particles'
73
+ bindingRefs.forEach(({ memberRef, struct }) =>
74
+ transformBindingReference(memberRef, struct),
75
+ );
76
+ // remove intermediate fn param declaration b:Bindings from 'fn(b:Bindings)'
77
+ bindingRefs.forEach(({ intermediates }) =>
78
+ intermediates.forEach(e => (e.contents = [])),
79
+ );
80
+ const contents = removeBindingStructs(moduleElem);
81
+ moduleElem.contents = [...newVars, ...contents];
82
+ notableElems.bindingStructs = bindingStructs;
83
+ return { ...clonedAst, moduleElem };
84
+ }
85
+
86
+ export function markEntryTypes(
87
+ moduleElem: ModuleElem,
88
+ bindingStructs: BindingStructElem[],
89
+ ): void {
90
+ const fns = moduleElem.contents.filter(e => e.kind === "fn");
91
+ const fnFound = fnReferencesBindingStruct(fns, bindingStructs);
92
+ if (fnFound) {
93
+ const { fn, struct } = fnFound;
94
+ struct.entryFn = fn;
95
+ }
96
+ }
97
+
98
+ function fnReferencesBindingStruct(
99
+ fns: FnElem[],
100
+ bindingStructs: BindingStructElem[],
101
+ ): { fn: FnElem; struct: BindingStructElem } | undefined {
102
+ for (const fn of fns) {
103
+ const { params } = fn;
104
+ for (const p of params) {
105
+ const ref = p.name?.typeRef?.name as RefIdent | undefined;
106
+ const referencedElem = (ref?.refersTo as DeclIdent)
107
+ ?.declElem as StructElem;
108
+ const struct = bindingStructs.find(s => s === referencedElem);
109
+ if (struct) {
110
+ return { fn, struct };
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ function removeBindingStructs(moduleElem: ModuleElem): AbstractElem[] {
117
+ return moduleElem.contents.filter(
118
+ elem => elem.kind !== "struct" || !elem.bindingStruct,
119
+ );
120
+ }
121
+
122
+ /** mutate the AST, marking StructElems as bindingStructs
123
+ * (if they contain ptrs with @group @binding annotations)
124
+ * @return the binding structs
125
+ */
126
+ export function markBindingStructs(
127
+ moduleElem: ModuleElem,
128
+ ): BindingStructElem[] {
129
+ const structs = moduleElem.contents.filter(elem => elem.kind === "struct");
130
+ const bindingStructs = structs.filter(containsBinding);
131
+ bindingStructs.forEach(struct => (struct.bindingStruct = true));
132
+ // LATER also mark structs that reference a binding struct..
133
+ return bindingStructs as BindingStructElem[];
134
+ }
135
+
136
+ /** @return true if this struct contains a member with marked with @binding or @group */
137
+ function containsBinding(struct: StructElem): boolean {
138
+ return struct.members.some(({ attributes }) => bindingAttribute(attributes));
139
+ }
140
+
141
+ function bindingAttribute(attributes?: AttributeElem[]): boolean {
142
+ if (!attributes) return false;
143
+ return attributes.some(
144
+ ({ attribute }) =>
145
+ attribute.kind === "@attribute" &&
146
+ (attribute.name === "binding" || attribute.name === "group"),
147
+ );
148
+ }
149
+
150
+ /** convert each member of the binding struct into a synthetic global variable */
151
+ export function transformBindingStruct(
152
+ s: StructElem,
153
+ globalNames: Set<string>,
154
+ ): SyntheticElem[] {
155
+ return s.members.map(member => {
156
+ const { typeRef, name: memberName } = member;
157
+ const { name: typeName } = typeRef!; // members should always have a typeRef.. TODO fix typing to show this
158
+ const typeParameters = typeRef?.templateParams;
159
+
160
+ const varName = minimallyMangledName(memberName.name, globalNames);
161
+ member.mangledVarName = varName; // save new name so we can rewrite references to this member later
162
+ globalNames.add(varName);
163
+
164
+ const attributes =
165
+ member.attributes?.map(attributeToString).join(" ") ?? "";
166
+ const varTypes =
167
+ lowerPtrMember(member, typeName, typeParameters, varName) ??
168
+ lowerStdTypeMember(typeName, typeParameters) ??
169
+ lowerStorageTextureMember(typeName, typeParameters);
170
+ if (!varTypes) {
171
+ console.log("unhandled case transforming member", typeName);
172
+ return syntheticVar(attributes, varName, "", "??");
173
+ }
174
+
175
+ const { storage: storageType, varType } = varTypes;
176
+ return syntheticVar(attributes, varName, storageType, varType);
177
+ });
178
+ }
179
+
180
+ interface LoweredVarTypes {
181
+ storage: string;
182
+ varType: string;
183
+ }
184
+
185
+ function lowerPtrMember(
186
+ member: StructMemberElem,
187
+ typeName: RefIdent,
188
+ typeParameters: TypeTemplateParameter[] | undefined,
189
+ varName: string,
190
+ ): LoweredVarTypes | undefined {
191
+ if (typeName.originalName === "ptr") {
192
+ const origParams = typeParameters ?? [];
193
+ const newParams = [origParams[0]];
194
+ if (origParams[2]) newParams.push(origParams[2]);
195
+ const storage = typeListToString(newParams);
196
+
197
+ const varType = typeParamToString(origParams?.[1]);
198
+ return { storage, varType };
199
+ }
200
+ }
201
+
202
+ function lowerStdTypeMember(
203
+ typeName: RefIdent,
204
+ typeParameters: TypeTemplateParameter[] | undefined,
205
+ ): LoweredVarTypes | undefined {
206
+ if (typeof typeName !== "string") {
207
+ const varBaseType = typeName.std ? typeName.originalName : "??";
208
+ const params = typeParameters ? typeListToString(typeParameters) : "";
209
+ const varType = varBaseType + params;
210
+
211
+ return { varType, storage: "" };
212
+ }
213
+ }
214
+
215
+ function lowerStorageTextureMember(
216
+ typeName: RefIdent,
217
+ typeParameters: TypeTemplateParameter[] | undefined,
218
+ ): LoweredVarTypes | undefined {
219
+ if (textureStorage.test(typeName.originalName)) {
220
+ const params = typeParameters ? typeListToString(typeParameters) : "";
221
+ const varType = typeName + params;
222
+ return { varType, storage: "" };
223
+ }
224
+ }
225
+
226
+ function syntheticVar(
227
+ attributes: string,
228
+ varName: string,
229
+ storageTemplate: string,
230
+ varType: string,
231
+ ): SyntheticElem {
232
+ const varText = `${attributes} var${storageTemplate} ${varName} : ${varType};\n`;
233
+
234
+ const elem: SyntheticElem = {
235
+ kind: "synthetic",
236
+ text: varText,
237
+ };
238
+ return elem;
239
+ }
240
+
241
+ interface MemberRefToStruct extends StructTrace {
242
+ memberRef: SimpleMemberRef; // e.g. the memberRef 'b.particles'
243
+ }
244
+
245
+ interface StructTrace {
246
+ struct: StructElem; // e.g. the struct Bindings
247
+ intermediates: DeclarationElem[]; // e.g. the fn param b:Bindings from 'fn(b:Bindings)'
248
+ }
249
+
250
+ /** find all simple member references in the module that refer to binding structs */
251
+ export function findRefsToBindingStructs(
252
+ moduleElem: ModuleElem,
253
+ ): MemberRefToStruct[] {
254
+ const members: SimpleMemberRef[] = [];
255
+ visitAst(moduleElem, elem => {
256
+ if (elem.kind === "memberRef") members.push(elem);
257
+ });
258
+ return filterMap(members, refersToBindingStruct);
259
+ }
260
+
261
+ /** @return true if this memberRef refers to a binding struct */
262
+ function refersToBindingStruct(
263
+ memberRef: SimpleMemberRef,
264
+ ): MemberRefToStruct | undefined {
265
+ const found = traceToStruct(memberRef.name.ident);
266
+
267
+ if (found && found.struct.bindingStruct) {
268
+ return { memberRef, ...found };
269
+ }
270
+ }
271
+
272
+ /** If this identifier ultimately refers to a struct type, return the struct declaration */
273
+ function traceToStruct(ident: RefIdent): StructTrace | undefined {
274
+ const decl = findDecl(ident);
275
+ const declElem = decl.declElem;
276
+ // for now only handle the case where the reference points at a fn parameter
277
+ if (declElem && declElem.kind === "param") {
278
+ const name = declElem.name.typeRef!.name;
279
+ if (typeof name !== "string") {
280
+ if (name.std) {
281
+ return undefined;
282
+ }
283
+
284
+ const paramDecl = findDecl(name);
285
+ const structElem = paramDecl.declElem;
286
+ if (structElem?.kind === "struct") {
287
+ return { struct: structElem, intermediates: [declElem] };
288
+ }
289
+ return undefined;
290
+ }
291
+ } else {
292
+ // LATER presumably handle other cases? Should this be more general, e.g. traceToType()?
293
+ // elemLog(
294
+ // ident.refIdentElem!,
295
+ // `unhandled case in traceToStruct: decl ${declElem.kind} not yet implemented`,
296
+ // );
297
+ }
298
+ }
299
+
300
+ /** Mutate the member reference elem to instead contain synthetic elem text.
301
+ * The new text is the mangled var name of the struct member that the memberRef refers to. */
302
+ export function transformBindingReference(
303
+ memberRef: SimpleMemberRef,
304
+ struct: StructElem,
305
+ ): SyntheticElem {
306
+ const refName = memberRef.member.name;
307
+ const structMember = struct.members.find(m => m.name.name === refName)!;
308
+ if (!structMember || !structMember.mangledVarName) {
309
+ if (tracing) console.log(`missing mangledVarName for ${refName}`);
310
+ return { kind: "synthetic", text: refName };
311
+ }
312
+ const { extraComponents } = memberRef;
313
+ const extraText = extraComponents ? contentsToString(extraComponents) : "";
314
+
315
+ const text = structMember.mangledVarName + extraText;
316
+ const synthElem: SyntheticElem = { kind: "synthetic", text };
317
+ memberRef.contents = [synthElem];
318
+ return synthElem;
319
+ }