wesl 0.6.0-pre2

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 (110) hide show
  1. package/dist/index.cjs +2617 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.js +2617 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/linker/packages/linker/src/AbstractElems.d.ts +104 -0
  6. package/dist/linker/packages/linker/src/BindIdents.d.ts +16 -0
  7. package/dist/linker/packages/linker/src/CommentsGrammar.d.ts +6 -0
  8. package/dist/linker/packages/linker/src/FlattenTreeImport.d.ts +11 -0
  9. package/dist/linker/packages/linker/src/ImportGrammar.d.ts +13 -0
  10. package/dist/linker/packages/linker/src/ImportTree.d.ts +17 -0
  11. package/dist/linker/packages/linker/src/Linker.d.ts +26 -0
  12. package/dist/linker/packages/linker/src/LowerAndEmit.d.ts +25 -0
  13. package/dist/linker/packages/linker/src/ParseWESL.d.ts +36 -0
  14. package/dist/linker/packages/linker/src/ParsedRegistry.d.ts +26 -0
  15. package/dist/linker/packages/linker/src/PathUtil.d.ts +9 -0
  16. package/dist/linker/packages/linker/src/Scope.d.ts +55 -0
  17. package/dist/linker/packages/linker/src/Slicer.d.ts +26 -0
  18. package/dist/linker/packages/linker/src/StandardTypes.d.ts +6 -0
  19. package/dist/linker/packages/linker/src/Util.d.ts +26 -0
  20. package/dist/linker/packages/linker/src/WESLCollect.d.ts +29 -0
  21. package/dist/linker/packages/linker/src/WESLGrammar.d.ts +23 -0
  22. package/dist/linker/packages/linker/src/WESLTokens.d.ts +42 -0
  23. package/dist/linker/packages/linker/src/WgslBundle.d.ts +13 -0
  24. package/dist/linker/packages/linker/src/debug/ASTtoString.d.ts +3 -0
  25. package/dist/linker/packages/linker/src/debug/ImportToString.d.ts +2 -0
  26. package/dist/linker/packages/linker/src/debug/LineWrapper.d.ts +21 -0
  27. package/dist/linker/packages/linker/src/debug/ScopeToString.d.ts +4 -0
  28. package/dist/linker/packages/linker/src/index.d.ts +7 -0
  29. package/dist/linker/packages/linker/src/test/ErrorLogging.test.d.ts +1 -0
  30. package/dist/linker/packages/linker/src/test/Expression.test.d.ts +1 -0
  31. package/dist/linker/packages/linker/src/test/FlattenTreeImport.test.d.ts +1 -0
  32. package/dist/linker/packages/linker/src/test/ImportCases.test.d.ts +1 -0
  33. package/dist/linker/packages/linker/src/test/ImportSyntaxCases.test.d.ts +1 -0
  34. package/dist/linker/packages/linker/src/test/LinkGlob.test.d.ts +1 -0
  35. package/dist/linker/packages/linker/src/test/LinkPackage.test.d.ts +1 -0
  36. package/dist/linker/packages/linker/src/test/Linker.test.d.ts +1 -0
  37. package/dist/linker/packages/linker/src/test/MatchWgslD.test.d.ts +1 -0
  38. package/dist/linker/packages/linker/src/test/ParseComments.test.d.ts +1 -0
  39. package/dist/linker/packages/linker/src/test/ParseWESL.test.d.ts +1 -0
  40. package/dist/linker/packages/linker/src/test/PathUtil.test.d.ts +1 -0
  41. package/dist/linker/packages/linker/src/test/PrettyGrammar.test.d.ts +1 -0
  42. package/dist/linker/packages/linker/src/test/ScopeWESL.test.d.ts +1 -0
  43. package/dist/linker/packages/linker/src/test/Slicer.test.d.ts +1 -0
  44. package/dist/linker/packages/linker/src/test/TestSetup.d.ts +1 -0
  45. package/dist/linker/packages/linker/src/test/TestUtil.d.ts +15 -0
  46. package/dist/linker/packages/linker/src/test/Util.test.d.ts +1 -0
  47. package/dist/linker/packages/linker/src/test/WgslTests.d.ts +0 -0
  48. package/dist/linker/packages/linker/src/test/shared/StringUtil.d.ts +8 -0
  49. package/dist/linker/packages/linker/src/test/shared/test/StringUtil.test.d.ts +1 -0
  50. package/dist/minified.cjs +2 -0
  51. package/dist/minified.cjs.map +1 -0
  52. package/dist/minified.js +2617 -0
  53. package/dist/minified.js.map +1 -0
  54. package/dist/wesl-testsuite/src/test-cases/BulkTests.d.ts +4 -0
  55. package/dist/wesl-testsuite/src/test-cases/ImportCases.d.ts +3 -0
  56. package/dist/wesl-testsuite/src/test-cases/ImportSyntaxCases.d.ts +3 -0
  57. package/package.json +45 -0
  58. package/src/AbstractElems.ts +148 -0
  59. package/src/BindIdents.ts +277 -0
  60. package/src/CommentsGrammar.ts +44 -0
  61. package/src/FlattenTreeImport.ts +59 -0
  62. package/src/ImportGrammar.ts +142 -0
  63. package/src/ImportTree.ts +19 -0
  64. package/src/Linker.ts +151 -0
  65. package/src/LowerAndEmit.ts +143 -0
  66. package/src/ParseWESL.ts +106 -0
  67. package/src/ParsedRegistry.ts +97 -0
  68. package/src/PathUtil.ts +52 -0
  69. package/src/Scope.ts +100 -0
  70. package/src/Slicer.ts +127 -0
  71. package/src/StandardTypes.ts +66 -0
  72. package/src/Util.ts +112 -0
  73. package/src/WESLCollect.ts +336 -0
  74. package/src/WESLGrammar.ts +538 -0
  75. package/src/WESLTokens.ts +97 -0
  76. package/src/WgslBundle.ts +16 -0
  77. package/src/debug/ASTtoString.ts +149 -0
  78. package/src/debug/ImportToString.ts +21 -0
  79. package/src/debug/LineWrapper.ts +65 -0
  80. package/src/debug/ScopeToString.ts +51 -0
  81. package/src/index.ts +7 -0
  82. package/src/test/ErrorLogging.test.ts +14 -0
  83. package/src/test/Expression.test.ts +22 -0
  84. package/src/test/FlattenTreeImport.test.ts +56 -0
  85. package/src/test/ImportCases.test.ts +440 -0
  86. package/src/test/ImportSyntaxCases.test.ts +22 -0
  87. package/src/test/LinkGlob.test.ts +25 -0
  88. package/src/test/LinkPackage.test.ts +26 -0
  89. package/src/test/Linker.test.ts +120 -0
  90. package/src/test/MatchWgslD.test.ts +16 -0
  91. package/src/test/ParseComments.test.ts +74 -0
  92. package/src/test/ParseWESL.test.ts +902 -0
  93. package/src/test/PathUtil.test.ts +34 -0
  94. package/src/test/PrettyGrammar.test.ts +21 -0
  95. package/src/test/ScopeWESL.test.ts +272 -0
  96. package/src/test/Slicer.test.ts +103 -0
  97. package/src/test/TestSetup.ts +4 -0
  98. package/src/test/TestUtil.ts +52 -0
  99. package/src/test/Util.test.ts +22 -0
  100. package/src/test/WgslTests.ts +0 -0
  101. package/src/test/__snapshots__/ParseDirectives.test.ts.snap +25 -0
  102. package/src/test/__snapshots__/ParseWESL.test.ts.snap +119 -0
  103. package/src/test/__snapshots__/ParseWESL2.test.ts.snap +67 -0
  104. package/src/test/__snapshots__/RustDirective.test.ts.snap +359 -0
  105. package/src/test/shared/StringUtil.ts +59 -0
  106. package/src/test/shared/test/StringUtil.test.ts +32 -0
  107. package/src/test/wgsl_1/main.wgsl +3 -0
  108. package/src/test/wgsl_1/util.wgsl +1 -0
  109. package/src/test/wgsl_2/main2.wgsl +3 -0
  110. package/src/test/wgsl_2/util2.wgsl +1 -0
package/src/Slicer.ts ADDED
@@ -0,0 +1,127 @@
1
+ import { SrcMap, SrcMapEntry } from "mini-parse";
2
+ import { last, scan } from "./Util.js";
3
+
4
+ /** specify a start,end portion of a string to be replaced */
5
+ export interface SliceReplace {
6
+ start: number;
7
+ end: number;
8
+ replacement: string;
9
+ }
10
+
11
+ interface SlicingProgress {
12
+ srcPos: number;
13
+ destPos: number;
14
+ results: string[];
15
+ entries: SrcMapEntry[];
16
+ }
17
+
18
+ /**
19
+ * Rewrite a string by replacing segments them with provided texts.
20
+ *
21
+ * example:
22
+ * src:
23
+ * aaabbbbbc
24
+ * ^ ^
25
+ * St End Repl='XXX'
26
+ *
27
+ * returns a srcMap with the new text and mappings from the original text to the new text
28
+ * aaaXXXc
29
+ */
30
+ export function sliceReplace(
31
+ src: string,
32
+ slices: SliceReplace[],
33
+ start = 0,
34
+ end = src.length,
35
+ ): SrcMap {
36
+ const sorted = [...slices].sort((a, b) => a.start - b.start);
37
+ const initProgress = { srcPos: start, destPos: 0, results: [], entries: [] };
38
+ const slicePogress = scan(sorted, oneSlice, initProgress);
39
+ const lastProgress = finalProgress2(slicePogress);
40
+
41
+ const { results, entries } = lastProgress;
42
+ const text = results.join("");
43
+ const srcMap = new SrcMap(text, entries);
44
+ return srcMap;
45
+
46
+ /** visit one slice, return progress */
47
+ function oneSlice(
48
+ slice: SliceReplace,
49
+ progress: SlicingProgress,
50
+ ): SlicingProgress {
51
+ // dlog({ slice });
52
+ // update text with copy and replacement
53
+ const copyText = src.slice(progress.srcPos, slice.start);
54
+ const copied = replaceOne(copyText, slice.start, progress);
55
+ const replaced = replaceOne(slice.replacement, slice.end, copied);
56
+
57
+ return replaced;
58
+ }
59
+
60
+ /** add provided text to the result, advance src position, and add a srcMap entry
61
+ * @return the accumulated progress */
62
+ function replaceOne(
63
+ replacement: string,
64
+ newSrcPos: number,
65
+ progress: SlicingProgress,
66
+ ): SlicingProgress {
67
+ const { destPos, entries } = progress;
68
+ const newDestPos = destPos + replacement.length;
69
+
70
+ // new srcMap entry if there is a replacement text (otherwise there's nothing to map dest to src)
71
+ let newEntries = entries;
72
+ if (replacement) {
73
+ const { srcPos } = progress;
74
+ newEntries = entries.concat({
75
+ src,
76
+ srcStart: srcPos,
77
+ srcEnd: newSrcPos,
78
+ destStart: destPos,
79
+ destEnd: newDestPos,
80
+ });
81
+ }
82
+
83
+ // update results text and progress
84
+ const { results } = progress;
85
+ const newResults = replacement ? results.concat(replacement) : results;
86
+ return {
87
+ srcPos: newSrcPos,
88
+ destPos: newDestPos,
89
+ results: newResults,
90
+ entries: newEntries,
91
+ };
92
+ }
93
+
94
+ /**
95
+ * If there's any trailing text uncovered by the slices before the end,
96
+ * add a result and srcMap entry
97
+ *
98
+ * @return the accumulated progress
99
+ */
100
+ function finalProgress2(progress: SlicingProgress[]): SlicingProgress {
101
+ const lastProgress = last(progress) ?? initProgress;
102
+ const { srcPos } = lastProgress;
103
+ return replaceOne(src.slice(srcPos, end), end, lastProgress);
104
+ }
105
+ }
106
+
107
+ const tokenRegex = /\b(\w+)\b/gi;
108
+ /** find strings in a text -
109
+ * found strings must be 'tokens', surrounded by spaces or punctuation
110
+ *
111
+ * @return SliceReplace elements
112
+ */
113
+ export function sliceWords(
114
+ text: string,
115
+ replace: Record<string, string>,
116
+ ): SliceReplace[] {
117
+ const tokens = [...text.matchAll(tokenRegex)];
118
+ const find = Object.keys(replace);
119
+ const matches = tokens.filter(m => find.includes(m[0]));
120
+ const slices = matches.map(m => {
121
+ const start = m.index;
122
+ const end = start + m[0].length;
123
+ const replacement = replace[m[0]];
124
+ return { start, end, replacement };
125
+ });
126
+ return slices;
127
+ }
@@ -0,0 +1,66 @@
1
+ export const stdFns = `bitcast all any select arrayLength
2
+ abs acos acosh asin asinh atan atanh atan2 ceil clamp cos cosh
3
+ countLeadingZeros countOneBits countTrailingZeros cross
4
+ degrees determinant distance dot dot4UI8Packed dot4I8Packed
5
+ exp exp2 extractBits faceForward firstLeadingBit firstTrailingBit
6
+ floor fma fract frexp inserBits inverseSqrt ldexp length log log2
7
+ max min mix modf normalize pow quantizeToF16 radians reflect refract
8
+ reverseBits round saturate sign sin sinh smoothstep sqrt step tan tanh
9
+ transpose trunc
10
+ dpdx dpdxCoarse dpdxFine dpdy dpdyCoarse dpdyFine fwidth
11
+ fwdithCoarse fwidthFine
12
+ textureDimensions textureGather textureGatherCompare textureLoad
13
+ textureNumLayers textureNumLevels textureNumSamples
14
+ textureSample textureSampleBias textureSampleCompare textureSampleCompareLevel
15
+ textureSampleGrad textureSampleLevel textureSampleBaseClampToEdge
16
+ textureStore
17
+ atomicLoad atomicStore atomicAdd atomicSub atomicMax atomicMin
18
+ atomicOr atomicXor atomicExchange atomicCompareExchangeWeak
19
+ pack4x8snorm pack4x8unorm pack4xI8 pack4xU8 pack4xI8Clamp pack4xU8Clamp
20
+ pack2x16snorm pack2x16unorm pack2x16float
21
+ unpack4x8snorm unpack4x8unorm unpack4xI8 unpack4xU8
22
+ unpack2x16snorm unpack2x16unorm unpack2x16float
23
+ storageBarrier textureBarrier workgroupBarrier workgroupUniformLoad
24
+ `.split(/\s+/);
25
+
26
+ export const stdTypes = `array atomic bool f16 f32 i32
27
+ mat2x2 mat2x3 mat2x4 mat3x2 mat3x3 mat3x4 mat4x2 mat4x3 mat4x4
28
+ mat2x2f mat2x3f mat2x4f mat3x2f mat3x3f mat3x4f
29
+ mat4x2f mat4x3f mat4x4f
30
+ mat2x2h mat2x3h mat2x4h mat3x2h mat3x3h mat3x4h
31
+ mat4x2h mat4x3h mat4x4h
32
+ u32 vec2 vec3 vec4 ptr
33
+ vec2i vec3i vec4i vec2u vec3u vec4u
34
+ vec2f vec3f vec4f vec2h vec3h vec4h
35
+ texture_1d texture_2d texture_2d_array texture_3d
36
+ texture_cube texture_cube_array
37
+ texture_multisampled_2d texture_depth_multisampled_2d
38
+ texture_external
39
+ texture_storage_1d texture_storage_2d texture_storage_2d_array
40
+ texture_storage_3d
41
+ texture_depth_2d texture_depth_2d_array texture_depth_cube
42
+ texture_depth_cube_array
43
+ sampler sampler_comparison
44
+ rgba8unorm rgba8snorm rgba8uint rgba8sint
45
+ rgba16uint rgba16sint rgba16float
46
+ r32uint r32sint r32float rg32uint rg32sint rg32float
47
+ rgba32uint rgba32sint rgba32float
48
+ bgra8unorm
49
+ function uniform
50
+ `.split(/\s+/); // LATER handle 'function' in template parser?
51
+
52
+ /* Note the texel formats like rgba8unorm are here because they appear in type position
53
+ in <templates> for texture_storage_* types.
54
+ (We could parse texture_storage types specially, but user code is unlikely to alias
55
+ the texture format names with e.g. a 'struct rbga8unorm .)
56
+ */
57
+
58
+ /** return true if the name is for a built in type (not a user struct) */
59
+ export function stdType(name: string): boolean {
60
+ return stdTypes.includes(name);
61
+ }
62
+
63
+ /** return true if the name is for a built in fn (not a user function) */
64
+ export function stdFn(name: string): boolean {
65
+ return stdFns.includes(name) || stdType(name);
66
+ }
package/src/Util.ts ADDED
@@ -0,0 +1,112 @@
1
+ export function multiKeySet<A, B, V>(
2
+ m: Map<A, Map<B, V>>,
3
+ a: A,
4
+ b: B,
5
+ v: V,
6
+ ): void {
7
+ const bMap = m.get(a) || new Map();
8
+ m.set(a, bMap);
9
+ bMap.set(b, v);
10
+ }
11
+
12
+ const tokenRegex = /\b(\w+)\b/gi;
13
+ /** replace strings in a text according to a relacement map
14
+ * replaced strings must be 'tokens', surrounded by spaces or punctuation
15
+ */
16
+ export function replaceWords(
17
+ text: string,
18
+ replace: Record<string, string>,
19
+ ): string {
20
+ return text.replaceAll(tokenRegex, s => (s in replace ? replace[s] : s));
21
+ }
22
+
23
+ /** return an array partitioned into possibly overlapping groups */
24
+ export function grouped<T>(a: T[], size: number, stride = size): T[][] {
25
+ const groups = [];
26
+ for (let i = 0; i < a.length; i += stride) {
27
+ groups.push(a.slice(i, i + size));
28
+ }
29
+ return groups;
30
+ }
31
+
32
+ /** group an array into subarrays by a key function */
33
+ export function groupBy<T, K>(a: T[], key: (t: T) => K): Map<K, T[]> {
34
+ const groups = new Map<K, T[]>();
35
+ for (const t of a) {
36
+ const k = key(t);
37
+ const group = groups.get(k) || [];
38
+ group.push(t);
39
+ groups.set(k, group);
40
+ }
41
+ return groups;
42
+ }
43
+
44
+ /** partition an array into two parts by a discriminator function */
45
+ export function partition<T>(a: T[], partFn: (t: T) => boolean): [T[], T[]] {
46
+ const yesPart: T[] = [];
47
+ const noPart: T[] = [];
48
+ for (const t of a) {
49
+ if (partFn(t)) yesPart.push(t);
50
+ else noPart.push(t);
51
+ }
52
+ return [yesPart, noPart];
53
+ }
54
+
55
+ /** run an carrying function over every element in an array,
56
+ * i.e. an inclusive prefix scan */
57
+ export function scan<T, U>(array: T[], fn: (a: T, b: U) => U, zero: U): U[] {
58
+ const result = [zero];
59
+
60
+ let current = zero;
61
+ for (let i = 0; i < array.length; i++) {
62
+ current = fn(array[i], current);
63
+ result.push(current);
64
+ }
65
+ return result;
66
+ }
67
+
68
+ /** return a new record by replacing values in 'a' with 'b' as a map.
69
+ * values in 'a' that are not in 'b' are unchanged.
70
+ * e.g. {a: "b", x: 9}, {b: 1} yields {a: 1, x: 9}
71
+ */
72
+ export function mapForward(
73
+ a: Record<string, string>,
74
+ b: Record<string, any>,
75
+ ): Record<string, any> {
76
+ const combined = Object.entries(a).map(([key, value]) => {
77
+ const mappedValue = value in b ? b[value] : value;
78
+ return [key, mappedValue];
79
+ });
80
+ return Object.fromEntries(combined);
81
+ }
82
+
83
+ /** return the last element of an array or undefined */
84
+ export function last<T>(a: T[]): T | undefined {
85
+ return a[a.length - 1];
86
+ }
87
+
88
+ /**
89
+ * Overlap two arrays, returning the tail of b if a is a prefix of b.
90
+ * Otherwise, return undefined.
91
+ */
92
+ export function overlapTail<T>(a: T[], b: T[]): T[] | undefined {
93
+ let overlapSize = Math.min(a.length, b.length);
94
+
95
+ while (overlapSize > 0) {
96
+ const suffix = a.slice(-overlapSize);
97
+ const prefix = b.slice(0, overlapSize);
98
+ if (arrayEquals(suffix, prefix)) {
99
+ break;
100
+ } else {
101
+ overlapSize--;
102
+ }
103
+ }
104
+
105
+ if (overlapSize) {
106
+ return b.slice(overlapSize);
107
+ }
108
+ }
109
+
110
+ function arrayEquals(a: any[], b: any[]): boolean {
111
+ return a.length === b.length && a.every((val, index) => val === b[index]);
112
+ }
@@ -0,0 +1,336 @@
1
+ import { CollectContext, CollectPair, tracing } from "mini-parse";
2
+ import {
3
+ AbstractElem,
4
+ AliasElem,
5
+ ConstElem,
6
+ DeclarationElem,
7
+ DeclIdentElem,
8
+ ElemWithContents,
9
+ FnElem,
10
+ GlobalVarElem,
11
+ ImportElem,
12
+ ModuleElem,
13
+ NameElem,
14
+ OverrideElem,
15
+ ParamElem,
16
+ RefIdentElem,
17
+ StructElem,
18
+ StructMemberElem,
19
+ TextElem,
20
+ VarElem,
21
+ } from "./AbstractElems.ts";
22
+ import {
23
+ ImportTree,
24
+ PathSegment,
25
+ SegmentList,
26
+ SimpleSegment,
27
+ } from "./ImportTree.ts";
28
+ import {
29
+ StableState,
30
+ WeslAST,
31
+ WeslParseContext,
32
+ WeslParseState,
33
+ } from "./ParseWESL.ts";
34
+ import { DeclIdent, emptyBodyScope, RefIdent, Scope } from "./Scope.ts";
35
+
36
+ /** add an elem to the .contents array of the currently containing element */
37
+ function addToOpenElem(cc: CollectContext, elem: AbstractElem): void {
38
+ const weslContext: WeslParseContext = cc.app.context;
39
+ const { openElems } = weslContext;
40
+ if (openElems && openElems.length) {
41
+ const open = openElems[openElems.length - 1];
42
+ open.contents.push(elem);
43
+ }
44
+ }
45
+
46
+ /** create reference Ident and add to context */
47
+ export function refIdent(cc: CollectContext): RefIdentElem {
48
+ const { src, start, end } = cc;
49
+ const app = cc.app as WeslParseState;
50
+ const { scope } = app.context;
51
+ const { srcModule } = app.stable;
52
+ const originalName = src.slice(start, end);
53
+
54
+ const kind = "ref";
55
+ const ident: RefIdent = { kind, originalName, ast: cc.app.stable, scope };
56
+ const identElem: RefIdentElem = { kind, start, end, srcModule, ident };
57
+ ident.refIdentElem = identElem;
58
+
59
+ saveIdent(cc, identElem);
60
+ return identElem;
61
+ }
62
+
63
+ /** create declaration Ident and add to context */
64
+ export function declIdentElem(cc: CollectContext): DeclIdentElem {
65
+ const { src, start, end } = cc;
66
+ const app = cc.app as WeslParseState;
67
+ const { srcModule } = app.stable;
68
+ const originalName = src.slice(start, end);
69
+
70
+ const kind = "decl";
71
+ const declElem = null as any; // we'll set declElem later
72
+ const ident: DeclIdent = { kind, originalName, scope: null as any, declElem }; // we'll set declElem later
73
+ const identElem: DeclIdentElem = { kind, start, end, srcModule, ident };
74
+
75
+ saveIdent(cc, identElem);
76
+ return identElem;
77
+ }
78
+
79
+ let identId = 0;
80
+ /** add Ident to current open scope, add IdentElem to current open element */
81
+ function saveIdent(
82
+ cc: CollectContext,
83
+ identElem: RefIdentElem | DeclIdentElem,
84
+ ) {
85
+ const { ident } = identElem;
86
+ ident.id = identId++;
87
+ const weslContext: WeslParseContext = cc.app.context;
88
+ weslContext.scope.idents.push(ident);
89
+ addToOpenElem(cc, identElem);
90
+ }
91
+
92
+ /** start a new child Scope */
93
+ function startScope(cc: CollectContext) {
94
+ const { scope } = cc.app.context as WeslParseContext;
95
+ const newScope = emptyBodyScope(scope);
96
+ scope.children.push(newScope);
97
+ cc.app.context.scope = newScope;
98
+ // srcLog(cc.src, cc.start, "startScope", newScope.id);
99
+ }
100
+
101
+ /* close current Scope and set current scope to parent */
102
+ function completeScope(cc: CollectContext): Scope {
103
+ const weslContext = cc.app.context as WeslParseContext;
104
+ const completedScope = weslContext.scope;
105
+ // srcLog(cc.src, cc.start, "completeScope", completedScope.id);
106
+ // console.log(scopeIdentTree(completedScope));
107
+ const { parent } = completedScope;
108
+ if (parent) {
109
+ weslContext.scope = parent;
110
+ } else if (tracing) {
111
+ const { idents, kind } = completedScope;
112
+ console.log("ERR: completeScope, no parent scope", { kind, idents });
113
+ }
114
+ return completedScope;
115
+ }
116
+
117
+ // prettier-ignore
118
+ export type OpenElem<T extends AbstractElem = AbstractElem> =
119
+ Pick< T, "kind" > & { contents: AbstractElem[] };
120
+
121
+ // prettier-ignore
122
+ export type PartElem<T extends AbstractElem = AbstractElem> =
123
+ Pick< T, "kind" | "start" | "end" > & { contents: AbstractElem[] };
124
+
125
+ type VarLikeElem =
126
+ | GlobalVarElem
127
+ | VarElem
128
+ | ConstElem
129
+ | OverrideElem
130
+ | AliasElem;
131
+
132
+ export function collectVarLike<E extends VarLikeElem>(
133
+ kind: E["kind"],
134
+ ): CollectPair<E> {
135
+ return collectElem(kind, (cc: CollectContext, openElem: PartElem<E>) => {
136
+ // dlog({ tags: [...Object.keys(cc.tags)] });
137
+ const name = cc.tags.declIdent?.[0] as DeclIdentElem;
138
+ const typeRef = cc.tags.typeRef?.[0] as RefIdentElem;
139
+ const decl_scope = cc.tags.decl_scope?.[0] as Scope;
140
+ const partElem = { ...openElem, name, typeRef };
141
+ const varElem = withTextCover(partElem, cc) as E;
142
+ (name.ident as DeclIdent).declElem = varElem as DeclarationElem;
143
+ name.ident.scope = decl_scope;
144
+ return varElem;
145
+ });
146
+ }
147
+
148
+ export function collectFn(): CollectPair<FnElem> {
149
+ return collectElem("fn", (cc: CollectContext, openElem: PartElem<FnElem>) => {
150
+ const name = cc.tags.fnName?.[0] as DeclIdentElem;
151
+ const body_scope = cc.tags.body_scope?.[0] as Scope;
152
+ const params: ParamElem[] = cc.tags.fnParam?.flat(3) ?? [];
153
+ const returnType: RefIdentElem | undefined = cc.tags.returnType?.flat(3)[0];
154
+ const partElem: FnElem = { ...openElem, name, params, returnType };
155
+ const fnElem = withTextCover(partElem, cc);
156
+ (name.ident as DeclIdent).declElem = fnElem;
157
+ name.ident.scope = body_scope;
158
+
159
+ return fnElem;
160
+ });
161
+ }
162
+
163
+ export function collectFnParam(): CollectPair<ParamElem> {
164
+ return collectElem(
165
+ "param",
166
+ (cc: CollectContext, openElem: PartElem<ParamElem>) => {
167
+ const name = cc.tags.paramName?.[0]! as DeclIdentElem;
168
+ const typeRef = cc.tags.typeRef?.[0]! as RefIdentElem;
169
+ const elem: ParamElem = { ...openElem, name, typeRef };
170
+ const paramElem = withTextCover(elem, cc);
171
+ name.ident.declElem = paramElem;
172
+
173
+ // name.ident.scope = pseudoScope(typeRef); // TODO set scope for param?
174
+ return paramElem;
175
+ },
176
+ );
177
+ }
178
+
179
+ export function collectStruct(): CollectPair<StructElem> {
180
+ return collectElem(
181
+ "struct",
182
+ (cc: CollectContext, openElem: PartElem<StructElem>) => {
183
+ const name = cc.tags.typeName?.[0] as DeclIdentElem;
184
+ const members = cc.tags.members as StructMemberElem[];
185
+ const structElem = { ...openElem, name, members };
186
+ const elem = withTextCover(structElem, cc);
187
+ (name.ident as DeclIdent).declElem = elem as DeclarationElem;
188
+ name.ident.scope = cc.tags.struct_scope?.[0] as Scope;
189
+
190
+ return elem;
191
+ },
192
+ );
193
+ }
194
+
195
+ export function collectStructMember(): CollectPair<StructMemberElem> {
196
+ return collectElem(
197
+ "member",
198
+ (cc: CollectContext, openElem: PartElem<StructMemberElem>) => {
199
+ const name = cc.tags.nameElem?.[0]!;
200
+ const typeRef = cc.tags.typeRef?.[0];
201
+ const partElem = { ...openElem, name, typeRef };
202
+ return withTextCover(partElem, cc);
203
+ },
204
+ );
205
+ }
206
+
207
+ export function collectNameElem(cc: CollectContext): NameElem {
208
+ const { start, end, src, app } = cc;
209
+ const { srcModule } = app.stable as WeslAST;
210
+ const name = src.slice(start, end);
211
+ const elem: NameElem = { kind: "name", srcModule, start, end, name };
212
+ addToOpenElem(cc, elem);
213
+ return elem;
214
+ }
215
+
216
+ // prettier-ignore
217
+ export function collectModule():
218
+ CollectPair<ModuleElem > {
219
+ return collectElem(
220
+ "module",
221
+ (cc: CollectContext, openElem: PartElem<ModuleElem>) => {
222
+ const ccComplete = { ...cc, start: 0, end: cc.src.length }; // force module to cover entire source despite ws skipping
223
+ const moduleElem: ModuleElem = withTextCover(openElem, ccComplete);
224
+ const weslState: StableState = cc.app.stable;
225
+ weslState.moduleElem = moduleElem;
226
+ return moduleElem;
227
+ },
228
+ );
229
+ }
230
+
231
+ export function importList(cc: CollectContext): SegmentList {
232
+ const list = cc.tags.list as PathSegment[];
233
+ return new SegmentList(list);
234
+ }
235
+
236
+ export function importSegment(cc: CollectContext): SimpleSegment {
237
+ const segOrig = cc.tags.segment?.[0] as string;
238
+ const seg = segOrig === "." ? "package" : segOrig; // TODO convert legacy syntax for now
239
+ return new SimpleSegment(seg, cc.tags.as?.[0]);
240
+ }
241
+
242
+ export function importTree(cc: CollectContext): ImportTree {
243
+ const path = cc.tags.p?.flat() as PathSegment[]; // LATER fix typing
244
+ return new ImportTree(path);
245
+ }
246
+
247
+ export function importElem(): CollectPair<ImportElem> {
248
+ return collectElem(
249
+ "import",
250
+ (cc: CollectContext, openElem: PartElem<ImportElem>) => {
251
+ const path = cc.tags.p as PathSegment[]; // LATER ts typing
252
+ const imports = new ImportTree(path);
253
+ const partialElem: ImportElem = { ...openElem, imports };
254
+ const importElem = withTextCover(partialElem, cc);
255
+ (cc.app.stable as StableState).imports.push(imports);
256
+ return importElem;
257
+ },
258
+ );
259
+ }
260
+
261
+ /** collect a scope start starts before and ends after a parser */
262
+ export function scopeCollect(): CollectPair<void> {
263
+ return {
264
+ before: startScope,
265
+ after: completeScope,
266
+ };
267
+ }
268
+
269
+ export function collectSimpleElem<V extends AbstractElem & ElemWithContents>(
270
+ kind: V["kind"],
271
+ ): CollectPair<V> {
272
+ return collectElem(kind, (cc, part) => withTextCover(part, cc) as V);
273
+ }
274
+
275
+ function collectElem<V extends AbstractElem>(
276
+ kind: V["kind"],
277
+ fn: (cc: CollectContext, partialElem: PartElem<V>) => V,
278
+ ): CollectPair<V> {
279
+ return {
280
+ before: (cc: CollectContext) => {
281
+ const partialElem = { kind, contents: [] };
282
+ const weslContext: WeslParseContext = cc.app.context;
283
+ weslContext.openElems.push(partialElem);
284
+ },
285
+ after: (cc: CollectContext) => {
286
+ // TODO refine start?
287
+ const weslContext: WeslParseContext = cc.app.context;
288
+ const partialElem = weslContext.openElems.pop()!;
289
+ console.assert(partialElem && partialElem.kind === kind);
290
+ const elem = fn(cc, { ...partialElem, start: cc.start, end: cc.end });
291
+ addToOpenElem(cc, elem);
292
+ return elem;
293
+ },
294
+ };
295
+ }
296
+
297
+ /**
298
+ * @return a copy of the element with contents extended
299
+ * to include TextElems to cover the entire range.
300
+ */
301
+ function withTextCover<T extends ElemWithContents>(
302
+ elem: T,
303
+ cc: CollectContext,
304
+ ): T {
305
+ const contents = coverWithText(cc, elem.contents);
306
+ return { ...elem, contents };
307
+ }
308
+
309
+ /** cover the entire source range with Elems by creating TextElems to
310
+ * cover any parts of the source that are not covered by other elems
311
+ * @returns the existing elems combined with any new TextElems, in src order */
312
+ function coverWithText(
313
+ cc: CollectContext,
314
+ existing: AbstractElem[],
315
+ ): AbstractElem[] {
316
+ let { start: pos } = cc;
317
+ const { end, app } = cc;
318
+ const ast: WeslAST = app.stable;
319
+ const sorted = existing.sort((a, b) => a.start - b.start);
320
+
321
+ const elems = sorted.flatMap(elem => {
322
+ const result = pos < elem.start ? [makeTextElem(elem.start), elem] : [elem];
323
+ pos = elem.end;
324
+ return result;
325
+ });
326
+
327
+ if (pos < end) {
328
+ elems.push(makeTextElem(end));
329
+ }
330
+
331
+ return elems;
332
+
333
+ function makeTextElem(end: number): TextElem {
334
+ return { kind: "text", start: pos, end, srcModule: ast.srcModule };
335
+ }
336
+ }