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.
- package/dist/index.cjs +2617 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +2617 -0
- package/dist/index.js.map +1 -0
- package/dist/linker/packages/linker/src/AbstractElems.d.ts +104 -0
- package/dist/linker/packages/linker/src/BindIdents.d.ts +16 -0
- package/dist/linker/packages/linker/src/CommentsGrammar.d.ts +6 -0
- package/dist/linker/packages/linker/src/FlattenTreeImport.d.ts +11 -0
- package/dist/linker/packages/linker/src/ImportGrammar.d.ts +13 -0
- package/dist/linker/packages/linker/src/ImportTree.d.ts +17 -0
- package/dist/linker/packages/linker/src/Linker.d.ts +26 -0
- package/dist/linker/packages/linker/src/LowerAndEmit.d.ts +25 -0
- package/dist/linker/packages/linker/src/ParseWESL.d.ts +36 -0
- package/dist/linker/packages/linker/src/ParsedRegistry.d.ts +26 -0
- package/dist/linker/packages/linker/src/PathUtil.d.ts +9 -0
- package/dist/linker/packages/linker/src/Scope.d.ts +55 -0
- package/dist/linker/packages/linker/src/Slicer.d.ts +26 -0
- package/dist/linker/packages/linker/src/StandardTypes.d.ts +6 -0
- package/dist/linker/packages/linker/src/Util.d.ts +26 -0
- package/dist/linker/packages/linker/src/WESLCollect.d.ts +29 -0
- package/dist/linker/packages/linker/src/WESLGrammar.d.ts +23 -0
- package/dist/linker/packages/linker/src/WESLTokens.d.ts +42 -0
- package/dist/linker/packages/linker/src/WgslBundle.d.ts +13 -0
- package/dist/linker/packages/linker/src/debug/ASTtoString.d.ts +3 -0
- package/dist/linker/packages/linker/src/debug/ImportToString.d.ts +2 -0
- package/dist/linker/packages/linker/src/debug/LineWrapper.d.ts +21 -0
- package/dist/linker/packages/linker/src/debug/ScopeToString.d.ts +4 -0
- package/dist/linker/packages/linker/src/index.d.ts +7 -0
- package/dist/linker/packages/linker/src/test/ErrorLogging.test.d.ts +1 -0
- package/dist/linker/packages/linker/src/test/Expression.test.d.ts +1 -0
- package/dist/linker/packages/linker/src/test/FlattenTreeImport.test.d.ts +1 -0
- package/dist/linker/packages/linker/src/test/ImportCases.test.d.ts +1 -0
- package/dist/linker/packages/linker/src/test/ImportSyntaxCases.test.d.ts +1 -0
- package/dist/linker/packages/linker/src/test/LinkGlob.test.d.ts +1 -0
- package/dist/linker/packages/linker/src/test/LinkPackage.test.d.ts +1 -0
- package/dist/linker/packages/linker/src/test/Linker.test.d.ts +1 -0
- package/dist/linker/packages/linker/src/test/MatchWgslD.test.d.ts +1 -0
- package/dist/linker/packages/linker/src/test/ParseComments.test.d.ts +1 -0
- package/dist/linker/packages/linker/src/test/ParseWESL.test.d.ts +1 -0
- package/dist/linker/packages/linker/src/test/PathUtil.test.d.ts +1 -0
- package/dist/linker/packages/linker/src/test/PrettyGrammar.test.d.ts +1 -0
- package/dist/linker/packages/linker/src/test/ScopeWESL.test.d.ts +1 -0
- package/dist/linker/packages/linker/src/test/Slicer.test.d.ts +1 -0
- package/dist/linker/packages/linker/src/test/TestSetup.d.ts +1 -0
- package/dist/linker/packages/linker/src/test/TestUtil.d.ts +15 -0
- package/dist/linker/packages/linker/src/test/Util.test.d.ts +1 -0
- package/dist/linker/packages/linker/src/test/WgslTests.d.ts +0 -0
- package/dist/linker/packages/linker/src/test/shared/StringUtil.d.ts +8 -0
- package/dist/linker/packages/linker/src/test/shared/test/StringUtil.test.d.ts +1 -0
- package/dist/minified.cjs +2 -0
- package/dist/minified.cjs.map +1 -0
- package/dist/minified.js +2617 -0
- package/dist/minified.js.map +1 -0
- package/dist/wesl-testsuite/src/test-cases/BulkTests.d.ts +4 -0
- package/dist/wesl-testsuite/src/test-cases/ImportCases.d.ts +3 -0
- package/dist/wesl-testsuite/src/test-cases/ImportSyntaxCases.d.ts +3 -0
- package/package.json +45 -0
- package/src/AbstractElems.ts +148 -0
- package/src/BindIdents.ts +277 -0
- package/src/CommentsGrammar.ts +44 -0
- package/src/FlattenTreeImport.ts +59 -0
- package/src/ImportGrammar.ts +142 -0
- package/src/ImportTree.ts +19 -0
- package/src/Linker.ts +151 -0
- package/src/LowerAndEmit.ts +143 -0
- package/src/ParseWESL.ts +106 -0
- package/src/ParsedRegistry.ts +97 -0
- package/src/PathUtil.ts +52 -0
- package/src/Scope.ts +100 -0
- package/src/Slicer.ts +127 -0
- package/src/StandardTypes.ts +66 -0
- package/src/Util.ts +112 -0
- package/src/WESLCollect.ts +336 -0
- package/src/WESLGrammar.ts +538 -0
- package/src/WESLTokens.ts +97 -0
- package/src/WgslBundle.ts +16 -0
- package/src/debug/ASTtoString.ts +149 -0
- package/src/debug/ImportToString.ts +21 -0
- package/src/debug/LineWrapper.ts +65 -0
- package/src/debug/ScopeToString.ts +51 -0
- package/src/index.ts +7 -0
- package/src/test/ErrorLogging.test.ts +14 -0
- package/src/test/Expression.test.ts +22 -0
- package/src/test/FlattenTreeImport.test.ts +56 -0
- package/src/test/ImportCases.test.ts +440 -0
- package/src/test/ImportSyntaxCases.test.ts +22 -0
- package/src/test/LinkGlob.test.ts +25 -0
- package/src/test/LinkPackage.test.ts +26 -0
- package/src/test/Linker.test.ts +120 -0
- package/src/test/MatchWgslD.test.ts +16 -0
- package/src/test/ParseComments.test.ts +74 -0
- package/src/test/ParseWESL.test.ts +902 -0
- package/src/test/PathUtil.test.ts +34 -0
- package/src/test/PrettyGrammar.test.ts +21 -0
- package/src/test/ScopeWESL.test.ts +272 -0
- package/src/test/Slicer.test.ts +103 -0
- package/src/test/TestSetup.ts +4 -0
- package/src/test/TestUtil.ts +52 -0
- package/src/test/Util.test.ts +22 -0
- package/src/test/WgslTests.ts +0 -0
- package/src/test/__snapshots__/ParseDirectives.test.ts.snap +25 -0
- package/src/test/__snapshots__/ParseWESL.test.ts.snap +119 -0
- package/src/test/__snapshots__/ParseWESL2.test.ts.snap +67 -0
- package/src/test/__snapshots__/RustDirective.test.ts.snap +359 -0
- package/src/test/shared/StringUtil.ts +59 -0
- package/src/test/shared/test/StringUtil.test.ts +32 -0
- package/src/test/wgsl_1/main.wgsl +3 -0
- package/src/test/wgsl_1/util.wgsl +1 -0
- package/src/test/wgsl_2/main2.wgsl +3 -0
- 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
|
+
}
|