wesl-link 0.6.6 → 0.6.7
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/README.md +4 -21
- package/bin/wesl-link +1402 -10112
- package/bin/wesl-packager +1510 -0
- package/package.json +6 -4
|
@@ -0,0 +1,1510 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { hideBin } from "yargs/helpers";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import "diff";
|
|
5
|
+
import { CachingStream, MatchersStream, ParseError, RegexMatchers, collectArray, delimited, enableTracing, eof, fn, kind, log, matchOneOf, not, opt, or, preceded, repeat, repeatPlus, req, separated_pair, seq, seqObj, span, tagScope, terminated, text, token, tokenKind, tokenOf, tracing, withSep, withSepPlus, withStreamAction, yes } from "mini-parse";
|
|
6
|
+
import { astToString, link, scopeToString } from "wesl";
|
|
7
|
+
import { loadModules, versionFromPackageJson } from "wesl-tooling";
|
|
8
|
+
import yargs from "yargs";
|
|
9
|
+
|
|
10
|
+
//#region ../wesl/src/Util.ts
|
|
11
|
+
/** filter an array, returning the truthy results of the filter function */
|
|
12
|
+
function filterMap(arr, fn$1) {
|
|
13
|
+
const out = [];
|
|
14
|
+
for (const t of arr) {
|
|
15
|
+
const u = fn$1(t);
|
|
16
|
+
if (u) out.push(u);
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Maps an character position in a string to a 1-indexed line number, and 1-indexed column.
|
|
22
|
+
*/
|
|
23
|
+
function offsetToLineNumber(offset, text$1) {
|
|
24
|
+
const safeOffset = Math.min(text$1.length, Math.max(0, offset));
|
|
25
|
+
let lineStartOffset = 0;
|
|
26
|
+
let lineNum = 1;
|
|
27
|
+
while (true) {
|
|
28
|
+
const lineEnd = text$1.indexOf("\n", lineStartOffset);
|
|
29
|
+
if (lineEnd === -1 || safeOffset <= lineEnd) {
|
|
30
|
+
const linePos = 1 + (safeOffset - lineStartOffset);
|
|
31
|
+
return [lineNum, linePos];
|
|
32
|
+
} else {
|
|
33
|
+
lineStartOffset = lineEnd + 1;
|
|
34
|
+
lineNum += 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Highlights an error.
|
|
39
|
+
*
|
|
40
|
+
* Returns a string with the line, and a string with the ^^^^ carets
|
|
41
|
+
*/
|
|
42
|
+
function errorHighlight(source, span$1) {
|
|
43
|
+
let lineStartOffset = source.lastIndexOf("\n", span$1[0]);
|
|
44
|
+
if (lineStartOffset === -1) lineStartOffset = 0;
|
|
45
|
+
let lineEndOffset = source.indexOf("\n", span$1[0]);
|
|
46
|
+
if (lineEndOffset === -1) lineEndOffset = source.length;
|
|
47
|
+
const errorLength = span$1[1] - span$1[0];
|
|
48
|
+
const caretCount = Math.max(1, errorLength);
|
|
49
|
+
const linePos = span$1[0] - lineStartOffset;
|
|
50
|
+
return [source.slice(lineStartOffset, lineEndOffset), " ".repeat(linePos) + "^".repeat(caretCount)];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region ../wesl/src/vlq/vlq.ts
|
|
55
|
+
/*!
|
|
56
|
+
Copyright (c) 2017-2021 [these people](https://github.com/Rich-Harris/vlq/graphs/contributors)
|
|
57
|
+
|
|
58
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
59
|
+
|
|
60
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
61
|
+
|
|
62
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
63
|
+
*/
|
|
64
|
+
const char_to_integer = {};
|
|
65
|
+
const integer_to_char = {};
|
|
66
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split("").forEach((char, i) => {
|
|
67
|
+
char_to_integer[char] = i;
|
|
68
|
+
integer_to_char[i] = char;
|
|
69
|
+
});
|
|
70
|
+
function encodeVlq(value) {
|
|
71
|
+
if (typeof value === "number") return encode_integer(value);
|
|
72
|
+
let result = "";
|
|
73
|
+
for (let i = 0; i < value.length; i += 1) result += encode_integer(value[i]);
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
function encode_integer(num) {
|
|
77
|
+
let result = "";
|
|
78
|
+
let enc;
|
|
79
|
+
if (num < 0) enc = -num << 1 | 1;
|
|
80
|
+
else enc = num << 1;
|
|
81
|
+
do {
|
|
82
|
+
let clamped = enc & 31;
|
|
83
|
+
enc >>>= 5;
|
|
84
|
+
if (enc > 0) clamped |= 32;
|
|
85
|
+
result += integer_to_char[clamped];
|
|
86
|
+
} while (enc > 0);
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region ../wesl/src/ClickableError.ts
|
|
92
|
+
/** Throw an error with an embedded source map so that browser users can
|
|
93
|
+
* click on the error in the browser debug console and see the wesl source code. */
|
|
94
|
+
function throwClickableError(params) {
|
|
95
|
+
const { url, text: text$1, lineNumber, lineColumn, length, error } = params;
|
|
96
|
+
const mappings = encodeVlq([
|
|
97
|
+
0,
|
|
98
|
+
0,
|
|
99
|
+
Math.max(0, lineNumber - 1),
|
|
100
|
+
Math.max(0, lineColumn - 1)
|
|
101
|
+
]) + "," + encodeVlq([
|
|
102
|
+
18,
|
|
103
|
+
0,
|
|
104
|
+
Math.max(0, lineNumber - 1),
|
|
105
|
+
Math.max(0, lineColumn - 1) + length
|
|
106
|
+
]);
|
|
107
|
+
const sourceMap = {
|
|
108
|
+
version: 3,
|
|
109
|
+
file: null,
|
|
110
|
+
sources: [url],
|
|
111
|
+
sourcesContent: [text$1 ?? null],
|
|
112
|
+
names: [],
|
|
113
|
+
mappings
|
|
114
|
+
};
|
|
115
|
+
let generatedCode = `throw new Error(${JSON.stringify(error.message + "")})`;
|
|
116
|
+
generatedCode += "\n//# sourceMappingURL=data:application/json;base64," + btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))));
|
|
117
|
+
generatedCode += "\n//# sourceURL=" + sourceMap.sources[0];
|
|
118
|
+
let oldLimit = 0;
|
|
119
|
+
if ("stackTraceLimit" in Error) {
|
|
120
|
+
oldLimit = Error.stackTraceLimit;
|
|
121
|
+
Error.stackTraceLimit = 1;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
(0, eval)(generatedCode);
|
|
125
|
+
} catch (e) {
|
|
126
|
+
if ("stackTraceLimit" in Error) Error.stackTraceLimit = oldLimit;
|
|
127
|
+
error.message = "";
|
|
128
|
+
if (tracing) e.cause = error;
|
|
129
|
+
throw e;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region ../wesl/src/Assertions.ts
|
|
135
|
+
/** checks whether a condition is true, otherwise throws */
|
|
136
|
+
function assertThat(condition, msg) {
|
|
137
|
+
if (!condition) throw new Error(msg);
|
|
138
|
+
}
|
|
139
|
+
/** when debug testing is enabled, checks whether a condition is true, otherwise throws */
|
|
140
|
+
function assertThatDebug(condition, msg) {
|
|
141
|
+
tracing && assertThat(condition, msg);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Useful to validate that all cases are handled,
|
|
145
|
+
* TypeScript should complain if this statement could possibly be executed.
|
|
146
|
+
*
|
|
147
|
+
* If this is somehow executed at runtime, throw an exception.
|
|
148
|
+
*/
|
|
149
|
+
function assertUnreachable(value) {
|
|
150
|
+
throw new ErrorWithData("Unreachable value", { data: value });
|
|
151
|
+
}
|
|
152
|
+
var ErrorWithData = class extends Error {
|
|
153
|
+
data;
|
|
154
|
+
constructor(message, options) {
|
|
155
|
+
super(message, options);
|
|
156
|
+
this.data = options?.data;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region ../wesl/src/Scope.ts
|
|
162
|
+
/** Combine two scope siblings.
|
|
163
|
+
* The first scope is mutated to append the contents of the second. */
|
|
164
|
+
function mergeScope(a, b) {
|
|
165
|
+
if (!b) return;
|
|
166
|
+
assertThatDebug(a.kind === b.kind);
|
|
167
|
+
assertThatDebug(a.parent === b.parent);
|
|
168
|
+
assertThatDebug(!b.ifAttribute);
|
|
169
|
+
a.contents = a.contents.concat(b.contents);
|
|
170
|
+
}
|
|
171
|
+
/** reset scope and ident debugging ids */
|
|
172
|
+
function resetScopeIds() {
|
|
173
|
+
scopeId = 0;
|
|
174
|
+
identId = 0;
|
|
175
|
+
}
|
|
176
|
+
let scopeId = 0;
|
|
177
|
+
let identId = 0;
|
|
178
|
+
function nextIdentId() {
|
|
179
|
+
return identId++;
|
|
180
|
+
}
|
|
181
|
+
/** make a new Scope object */
|
|
182
|
+
function emptyScope(parent, kind$1 = "scope") {
|
|
183
|
+
const id = scopeId++;
|
|
184
|
+
return {
|
|
185
|
+
id,
|
|
186
|
+
kind: kind$1,
|
|
187
|
+
parent,
|
|
188
|
+
contents: []
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region ../../node_modules/.pnpm/berry-pretty@0.0.5/node_modules/berry-pretty/dist/index.js
|
|
194
|
+
var spaces = memoize((nesting) => {
|
|
195
|
+
return " ".repeat(nesting);
|
|
196
|
+
});
|
|
197
|
+
var defaultCallerSize = 20;
|
|
198
|
+
var multiLinePad = "\n" + spaces(defaultCallerSize + 3);
|
|
199
|
+
function memoize(fn$1) {
|
|
200
|
+
const cache = /* @__PURE__ */ new Map();
|
|
201
|
+
return function(...args) {
|
|
202
|
+
const key = JSON.stringify(args);
|
|
203
|
+
if (cache.has(key)) return cache.get(key);
|
|
204
|
+
else {
|
|
205
|
+
const value = fn$1(...args);
|
|
206
|
+
cache.set(key, value);
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (typeof DOMRect === "undefined") globalThis.DOMRect = function() {};
|
|
212
|
+
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region ../wesl/src/WESLCollect.ts
|
|
215
|
+
function importElem(cc) {
|
|
216
|
+
const importElems = cc.tags.owo?.[0];
|
|
217
|
+
for (const importElem$1 of importElems) {
|
|
218
|
+
cc.app.stable.imports.push(importElem$1.imports);
|
|
219
|
+
addToOpenElem(cc, importElem$1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/** add an elem to the .contents array of the currently containing element */
|
|
223
|
+
function addToOpenElem(cc, elem) {
|
|
224
|
+
const weslContext = cc.app.context;
|
|
225
|
+
const { openElems } = weslContext;
|
|
226
|
+
if (openElems?.length) {
|
|
227
|
+
const open = openElems[openElems.length - 1];
|
|
228
|
+
open.contents.push(elem);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/** create reference Ident and add to context */
|
|
232
|
+
function refIdent(cc) {
|
|
233
|
+
const { src, start, end } = cc;
|
|
234
|
+
const app = cc.app;
|
|
235
|
+
const { srcModule } = app.stable;
|
|
236
|
+
const originalName = src.slice(start, end);
|
|
237
|
+
const kind$1 = "ref";
|
|
238
|
+
const ident$1 = {
|
|
239
|
+
kind: kind$1,
|
|
240
|
+
originalName,
|
|
241
|
+
ast: cc.app.stable,
|
|
242
|
+
id: nextIdentId(),
|
|
243
|
+
refIdentElem: null
|
|
244
|
+
};
|
|
245
|
+
const identElem = {
|
|
246
|
+
kind: kind$1,
|
|
247
|
+
start,
|
|
248
|
+
end,
|
|
249
|
+
srcModule,
|
|
250
|
+
ident: ident$1
|
|
251
|
+
};
|
|
252
|
+
ident$1.refIdentElem = identElem;
|
|
253
|
+
saveIdent(cc, identElem);
|
|
254
|
+
addToOpenElem(cc, identElem);
|
|
255
|
+
return identElem;
|
|
256
|
+
}
|
|
257
|
+
/** create declaration Ident and add to context */
|
|
258
|
+
function declCollect(cc) {
|
|
259
|
+
return declCollectInternal(cc, false);
|
|
260
|
+
}
|
|
261
|
+
/** create global declaration Ident and add to context */
|
|
262
|
+
function globalDeclCollect(cc) {
|
|
263
|
+
return declCollectInternal(cc, true);
|
|
264
|
+
}
|
|
265
|
+
function declCollectInternal(cc, isGlobal) {
|
|
266
|
+
const { src, start, end } = cc;
|
|
267
|
+
const app = cc.app;
|
|
268
|
+
const { scope: containingScope } = app.context;
|
|
269
|
+
const { srcModule } = app.stable;
|
|
270
|
+
const originalName = src.slice(start, end);
|
|
271
|
+
const kind$1 = "decl";
|
|
272
|
+
const declElem = null;
|
|
273
|
+
const ident$1 = {
|
|
274
|
+
declElem,
|
|
275
|
+
kind: kind$1,
|
|
276
|
+
originalName,
|
|
277
|
+
containingScope,
|
|
278
|
+
isGlobal,
|
|
279
|
+
id: nextIdentId(),
|
|
280
|
+
srcModule
|
|
281
|
+
};
|
|
282
|
+
const identElem = {
|
|
283
|
+
kind: kind$1,
|
|
284
|
+
start,
|
|
285
|
+
end,
|
|
286
|
+
srcModule,
|
|
287
|
+
ident: ident$1
|
|
288
|
+
};
|
|
289
|
+
saveIdent(cc, identElem);
|
|
290
|
+
addToOpenElem(cc, identElem);
|
|
291
|
+
return identElem;
|
|
292
|
+
}
|
|
293
|
+
const typedDecl = collectElem("typeDecl", (cc, openElem) => {
|
|
294
|
+
const decl = cc.tags.decl_elem?.[0];
|
|
295
|
+
const typeRef = cc.tags.typeRefElem?.[0];
|
|
296
|
+
const typeScope = cc.tags.decl_type?.[0];
|
|
297
|
+
const partial = {
|
|
298
|
+
...openElem,
|
|
299
|
+
decl,
|
|
300
|
+
typeScope,
|
|
301
|
+
typeRef
|
|
302
|
+
};
|
|
303
|
+
const elem = withTextCover(partial, cc);
|
|
304
|
+
return elem;
|
|
305
|
+
});
|
|
306
|
+
/** add Ident to current open scope, add IdentElem to current open element */
|
|
307
|
+
function saveIdent(cc, identElem) {
|
|
308
|
+
const { ident: ident$1 } = identElem;
|
|
309
|
+
ident$1.id = nextIdentId();
|
|
310
|
+
const weslContext = cc.app.context;
|
|
311
|
+
weslContext.scope.contents.push(ident$1);
|
|
312
|
+
}
|
|
313
|
+
/** start a new child lexical Scope */
|
|
314
|
+
function startScope(cc) {
|
|
315
|
+
startSomeScope("scope", cc);
|
|
316
|
+
}
|
|
317
|
+
/** start a new child partial Scope */
|
|
318
|
+
function startPartialScope(cc) {
|
|
319
|
+
startSomeScope("partial", cc);
|
|
320
|
+
}
|
|
321
|
+
/** start a new lexical or partial scope */
|
|
322
|
+
function startSomeScope(kind$1, cc) {
|
|
323
|
+
const { scope } = cc.app.context;
|
|
324
|
+
const newScope = emptyScope(scope, kind$1);
|
|
325
|
+
scope.contents.push(newScope);
|
|
326
|
+
cc.app.context.scope = newScope;
|
|
327
|
+
}
|
|
328
|
+
function completeScope(cc) {
|
|
329
|
+
return completeScopeInternal(cc, true);
|
|
330
|
+
}
|
|
331
|
+
function completeScopeNoIf(cc) {
|
|
332
|
+
return completeScopeInternal(cc, false);
|
|
333
|
+
}
|
|
334
|
+
function completeScopeInternal(cc, attachIfs) {
|
|
335
|
+
const weslContext = cc.app.context;
|
|
336
|
+
const completedScope = weslContext.scope;
|
|
337
|
+
const { parent } = completedScope;
|
|
338
|
+
if (parent) weslContext.scope = parent;
|
|
339
|
+
else if (tracing) console.log("ERR: completeScope, no parent scope", completedScope.contents);
|
|
340
|
+
if (attachIfs) {
|
|
341
|
+
const ifAttributes = collectIfAttributes(cc);
|
|
342
|
+
completedScope.ifAttribute = ifAttributes?.[0];
|
|
343
|
+
}
|
|
344
|
+
return completedScope;
|
|
345
|
+
}
|
|
346
|
+
/** return @if attributes from the 'attribute' tag */
|
|
347
|
+
function collectIfAttributes(cc) {
|
|
348
|
+
const attributes = cc.tags.attribute;
|
|
349
|
+
return filterIfAttributes(attributes);
|
|
350
|
+
}
|
|
351
|
+
function filterIfAttributes(attributes) {
|
|
352
|
+
if (!attributes) return;
|
|
353
|
+
return filterMap(attributes, (a) => a.attribute.kind === "@if" ? a.attribute : void 0);
|
|
354
|
+
}
|
|
355
|
+
function collectVarLike(kind$1) {
|
|
356
|
+
return collectElem(kind$1, (cc, openElem) => {
|
|
357
|
+
const name$1 = cc.tags.var_name?.[0];
|
|
358
|
+
const decl_scope = cc.tags.decl_scope?.[0];
|
|
359
|
+
const attributes = cc.tags.attribute;
|
|
360
|
+
const partElem = {
|
|
361
|
+
...openElem,
|
|
362
|
+
name: name$1,
|
|
363
|
+
attributes
|
|
364
|
+
};
|
|
365
|
+
const varElem = withTextCover(partElem, cc);
|
|
366
|
+
const declIdent = name$1.decl.ident;
|
|
367
|
+
declIdent.declElem = varElem;
|
|
368
|
+
if (name$1.typeScope) {
|
|
369
|
+
mergeScope(name$1.typeScope, decl_scope);
|
|
370
|
+
declIdent.dependentScope = name$1.typeScope;
|
|
371
|
+
} else declIdent.dependentScope = decl_scope;
|
|
372
|
+
return varElem;
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
const aliasCollect = collectElem("alias", (cc, openElem) => {
|
|
376
|
+
const name$1 = cc.tags.alias_name?.[0];
|
|
377
|
+
const alias_scope = cc.tags.alias_scope?.[0];
|
|
378
|
+
const typeRef = cc.tags.typeRefElem?.[0];
|
|
379
|
+
const attributes = cc.tags.attributes?.flat() ?? [];
|
|
380
|
+
const partElem = {
|
|
381
|
+
...openElem,
|
|
382
|
+
name: name$1,
|
|
383
|
+
attributes,
|
|
384
|
+
typeRef
|
|
385
|
+
};
|
|
386
|
+
const aliasElem = withTextCover(partElem, cc);
|
|
387
|
+
name$1.ident.dependentScope = alias_scope;
|
|
388
|
+
name$1.ident.declElem = aliasElem;
|
|
389
|
+
return aliasElem;
|
|
390
|
+
});
|
|
391
|
+
/**
|
|
392
|
+
* Collect a FnElem and associated scopes.
|
|
393
|
+
*
|
|
394
|
+
* Scope definition is a bit complicated in wgsl and wesl for fns.
|
|
395
|
+
* Here's what we collect for scopes for this example function:
|
|
396
|
+
* @if(true) fn foo(a: u32) -> @location(x) R { let y = a; }
|
|
397
|
+
*
|
|
398
|
+
* -{ // partial scope in case the whole shebang is prefixed by an `@if`
|
|
399
|
+
* %foo
|
|
400
|
+
*
|
|
401
|
+
* {<=%foo // foo decl references this header+returnType+body scope (for tracing dependencies from decls)
|
|
402
|
+
* x // for @location(x) (contains no decls, so ok to merge for tracing)
|
|
403
|
+
* %a u32 // merged from header scope
|
|
404
|
+
* R // merged from return type (contains no decls, so ok to merge for tracing)
|
|
405
|
+
* %y a // merged body scope
|
|
406
|
+
* }
|
|
407
|
+
* }
|
|
408
|
+
*/
|
|
409
|
+
const fnCollect = collectElem("fn", (cc, openElem) => {
|
|
410
|
+
const ourTags = fnTags(cc);
|
|
411
|
+
const { name: name$1, headerScope, returnScope, bodyScope, body, params } = ourTags;
|
|
412
|
+
const { attributes, returnAttributes, returnType, fnScope } = ourTags;
|
|
413
|
+
const fnElem = {
|
|
414
|
+
...openElem,
|
|
415
|
+
name: name$1,
|
|
416
|
+
attributes,
|
|
417
|
+
params,
|
|
418
|
+
returnAttributes,
|
|
419
|
+
body,
|
|
420
|
+
returnType
|
|
421
|
+
};
|
|
422
|
+
fnScope.ifAttribute = filterIfAttributes(attributes)?.[0];
|
|
423
|
+
const mergedScope = headerScope;
|
|
424
|
+
returnScope && mergeScope(mergedScope, returnScope);
|
|
425
|
+
mergeScope(mergedScope, bodyScope);
|
|
426
|
+
const filtered = [];
|
|
427
|
+
for (const e of fnScope.contents) if (e === headerScope || e === returnScope) continue;
|
|
428
|
+
else if (e === bodyScope) filtered.push(mergedScope);
|
|
429
|
+
else filtered.push(e);
|
|
430
|
+
fnScope.contents = filtered;
|
|
431
|
+
name$1.ident.declElem = fnElem;
|
|
432
|
+
name$1.ident.dependentScope = mergedScope;
|
|
433
|
+
return fnElem;
|
|
434
|
+
});
|
|
435
|
+
/** Fetch and cast the collection tags for fnCollect
|
|
436
|
+
* LATER typechecking for collect! */
|
|
437
|
+
function fnTags(cc) {
|
|
438
|
+
const { fn_attributes, fn_name, fn_param, return_attributes } = cc.tags;
|
|
439
|
+
const { return_type } = cc.tags;
|
|
440
|
+
const { header_scope, return_scope, body_scope, body_statement } = cc.tags;
|
|
441
|
+
const { fn_partial_scope } = cc.tags;
|
|
442
|
+
const name$1 = fn_name?.[0];
|
|
443
|
+
const headerScope = header_scope?.[0];
|
|
444
|
+
const returnScope = return_scope?.[0];
|
|
445
|
+
const bodyScope = body_scope?.[0];
|
|
446
|
+
const body = body_statement?.[0];
|
|
447
|
+
const params = fn_param?.flat(3) ?? [];
|
|
448
|
+
const attributes = fn_attributes?.flat();
|
|
449
|
+
const returnAttributes = return_attributes?.flat();
|
|
450
|
+
const returnType = return_type?.flat(3)[0];
|
|
451
|
+
const fnScope = fn_partial_scope?.[0];
|
|
452
|
+
return {
|
|
453
|
+
name: name$1,
|
|
454
|
+
headerScope,
|
|
455
|
+
returnScope,
|
|
456
|
+
bodyScope,
|
|
457
|
+
body,
|
|
458
|
+
params,
|
|
459
|
+
attributes,
|
|
460
|
+
returnAttributes,
|
|
461
|
+
returnType,
|
|
462
|
+
fnScope
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
const collectFnParam = collectElem("param", (cc, openElem) => {
|
|
466
|
+
const name$1 = cc.tags.param_name?.[0];
|
|
467
|
+
const attributes = cc.tags.attributes?.flat() ?? [];
|
|
468
|
+
const elem = {
|
|
469
|
+
...openElem,
|
|
470
|
+
name: name$1,
|
|
471
|
+
attributes
|
|
472
|
+
};
|
|
473
|
+
const paramElem = withTextCover(elem, cc);
|
|
474
|
+
name$1.decl.ident.declElem = paramElem;
|
|
475
|
+
return paramElem;
|
|
476
|
+
});
|
|
477
|
+
const collectStruct = collectElem("struct", (cc, openElem) => {
|
|
478
|
+
const name$1 = cc.tags.type_name?.[0];
|
|
479
|
+
const members = cc.tags.members;
|
|
480
|
+
const attributes = cc.tags.attributes?.flat() ?? [];
|
|
481
|
+
name$1.ident.dependentScope = cc.tags.struct_scope?.[0];
|
|
482
|
+
const structElem = {
|
|
483
|
+
...openElem,
|
|
484
|
+
name: name$1,
|
|
485
|
+
attributes,
|
|
486
|
+
members
|
|
487
|
+
};
|
|
488
|
+
const elem = withTextCover(structElem, cc);
|
|
489
|
+
name$1.ident.declElem = elem;
|
|
490
|
+
return elem;
|
|
491
|
+
});
|
|
492
|
+
const collectStructMember = collectElem("member", (cc, openElem) => {
|
|
493
|
+
const name$1 = cc.tags.nameElem?.[0];
|
|
494
|
+
const typeRef = cc.tags.typeRefElem?.[0];
|
|
495
|
+
const attributes = cc.tags.attribute?.flat(3);
|
|
496
|
+
const partElem = {
|
|
497
|
+
...openElem,
|
|
498
|
+
name: name$1,
|
|
499
|
+
attributes,
|
|
500
|
+
typeRef
|
|
501
|
+
};
|
|
502
|
+
return withTextCover(partElem, cc);
|
|
503
|
+
});
|
|
504
|
+
const specialAttribute = collectElem("attribute", (cc, openElem) => {
|
|
505
|
+
const attribute = cc.tags.attr_variant?.[0];
|
|
506
|
+
const attrElem = {
|
|
507
|
+
...openElem,
|
|
508
|
+
attribute
|
|
509
|
+
};
|
|
510
|
+
return attrElem;
|
|
511
|
+
});
|
|
512
|
+
const assertCollect = attrElemCollect("assert");
|
|
513
|
+
const statementCollect = attrElemCollect("statement");
|
|
514
|
+
const switchClauseCollect = attrElemCollect("switch-clause");
|
|
515
|
+
/** @return a collector for container elem types that have only an attributes field */
|
|
516
|
+
function attrElemCollect(kind$1) {
|
|
517
|
+
return collectElem(kind$1, (cc, openElem) => {
|
|
518
|
+
const attributes = cc.tags.attribute?.flat(3);
|
|
519
|
+
const partElem = {
|
|
520
|
+
...openElem,
|
|
521
|
+
attributes
|
|
522
|
+
};
|
|
523
|
+
return withTextCover(partElem, cc);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
const collectAttribute = collectElem("attribute", (cc, openElem) => {
|
|
527
|
+
const params = cc.tags.attrParam;
|
|
528
|
+
const name$1 = cc.tags.name?.[0];
|
|
529
|
+
const kind$1 = "@attribute";
|
|
530
|
+
const stdAttribute = {
|
|
531
|
+
kind: kind$1,
|
|
532
|
+
name: name$1,
|
|
533
|
+
params
|
|
534
|
+
};
|
|
535
|
+
const attrElem = {
|
|
536
|
+
...openElem,
|
|
537
|
+
attribute: stdAttribute
|
|
538
|
+
};
|
|
539
|
+
return attrElem;
|
|
540
|
+
});
|
|
541
|
+
const typeRefCollect = collectElem("type", (cc, openElem) => {
|
|
542
|
+
const templateParamsTemp = cc.tags.templateParam?.flat(3);
|
|
543
|
+
const typeRef = cc.tags.typeRefName?.[0];
|
|
544
|
+
const name$1 = typeof typeRef === "string" ? typeRef : typeRef.ident;
|
|
545
|
+
const partElem = {
|
|
546
|
+
...openElem,
|
|
547
|
+
name: name$1,
|
|
548
|
+
templateParams: templateParamsTemp
|
|
549
|
+
};
|
|
550
|
+
return withTextCover(partElem, cc);
|
|
551
|
+
});
|
|
552
|
+
const expressionCollect = collectElem("expression", (cc, openElem) => {
|
|
553
|
+
const partElem = { ...openElem };
|
|
554
|
+
return withTextCover(partElem, cc);
|
|
555
|
+
});
|
|
556
|
+
function globalAssertCollect(cc) {
|
|
557
|
+
const globalAssert = cc.tags.const_assert?.flat()[0];
|
|
558
|
+
const ast = cc.app.stable;
|
|
559
|
+
if (!ast.moduleAsserts) ast.moduleAsserts = [];
|
|
560
|
+
ast.moduleAsserts.push(globalAssert);
|
|
561
|
+
}
|
|
562
|
+
const stuffCollect = collectElem("stuff", (cc, openElem) => {
|
|
563
|
+
const partElem = { ...openElem };
|
|
564
|
+
return withTextCover(partElem, cc);
|
|
565
|
+
});
|
|
566
|
+
const memberRefCollect = collectElem("memberRef", (cc, openElem) => {
|
|
567
|
+
const { component, structRef, extra_components } = cc.tags;
|
|
568
|
+
const member = component?.[0];
|
|
569
|
+
const name$1 = structRef?.flat()[0];
|
|
570
|
+
const extraComponents = extra_components?.flat()[0];
|
|
571
|
+
const partElem = {
|
|
572
|
+
...openElem,
|
|
573
|
+
name: name$1,
|
|
574
|
+
member,
|
|
575
|
+
extraComponents
|
|
576
|
+
};
|
|
577
|
+
return withTextCover(partElem, cc);
|
|
578
|
+
});
|
|
579
|
+
function nameCollect(cc) {
|
|
580
|
+
const { start, end, src } = cc;
|
|
581
|
+
const name$1 = src.slice(start, end);
|
|
582
|
+
const elem = {
|
|
583
|
+
kind: "name",
|
|
584
|
+
start,
|
|
585
|
+
end,
|
|
586
|
+
name: name$1
|
|
587
|
+
};
|
|
588
|
+
addToOpenElem(cc, elem);
|
|
589
|
+
return elem;
|
|
590
|
+
}
|
|
591
|
+
const collectModule = collectElem("module", (cc, openElem) => {
|
|
592
|
+
const ccComplete = {
|
|
593
|
+
...cc,
|
|
594
|
+
start: 0,
|
|
595
|
+
end: cc.src.length
|
|
596
|
+
};
|
|
597
|
+
const moduleElem = withTextCover(openElem, ccComplete);
|
|
598
|
+
const weslState = cc.app.stable;
|
|
599
|
+
weslState.moduleElem = moduleElem;
|
|
600
|
+
return moduleElem;
|
|
601
|
+
});
|
|
602
|
+
function directiveCollect(cc) {
|
|
603
|
+
const { start, end } = cc;
|
|
604
|
+
const directive = cc.tags.directive?.flat()[0];
|
|
605
|
+
const attributes = cc.tags.attribute?.flat();
|
|
606
|
+
const kind$1 = "directive";
|
|
607
|
+
const elem = {
|
|
608
|
+
kind: kind$1,
|
|
609
|
+
attributes,
|
|
610
|
+
start,
|
|
611
|
+
end,
|
|
612
|
+
directive
|
|
613
|
+
};
|
|
614
|
+
addToOpenElem(cc, elem);
|
|
615
|
+
return elem;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Collect a LexicalScope.
|
|
619
|
+
*
|
|
620
|
+
* The scope starts encloses all idents and subscopes inside the parser to which
|
|
621
|
+
* .collect is attached
|
|
622
|
+
*/
|
|
623
|
+
const scopeCollect = {
|
|
624
|
+
before: startScope,
|
|
625
|
+
after: completeScope
|
|
626
|
+
};
|
|
627
|
+
/**
|
|
628
|
+
* Collect a LexicalScope.
|
|
629
|
+
*
|
|
630
|
+
* The scope starts encloses all idents and subscopes inside the parser to which
|
|
631
|
+
* .collect is attached
|
|
632
|
+
*
|
|
633
|
+
* '@if' attributes are not attached to the scope.
|
|
634
|
+
*/
|
|
635
|
+
const scopeCollectNoIf = {
|
|
636
|
+
before: startScope,
|
|
637
|
+
after: completeScopeNoIf
|
|
638
|
+
};
|
|
639
|
+
/**
|
|
640
|
+
* Collect a PartialScope.
|
|
641
|
+
*
|
|
642
|
+
* The scope starts encloses all idents and subscopes inside the parser to which
|
|
643
|
+
* .collect is attached
|
|
644
|
+
*/
|
|
645
|
+
const partialScopeCollect = {
|
|
646
|
+
before: startPartialScope,
|
|
647
|
+
after: completeScope
|
|
648
|
+
};
|
|
649
|
+
/** utility to collect an ElemWithContents
|
|
650
|
+
* starts the new element as the collection point corresponding
|
|
651
|
+
* to the start of the attached grammar and completes
|
|
652
|
+
* the element in the at the end of the grammar.
|
|
653
|
+
*
|
|
654
|
+
* In between the start and the end, the new element is available
|
|
655
|
+
* as an 'open' element in the collection context. While this element
|
|
656
|
+
* is 'open', other collected are added to the 'contents' field of this
|
|
657
|
+
* open element.
|
|
658
|
+
*/
|
|
659
|
+
function collectElem(kind$1, fn$1) {
|
|
660
|
+
return {
|
|
661
|
+
before: (cc) => {
|
|
662
|
+
const partialElem = {
|
|
663
|
+
kind: kind$1,
|
|
664
|
+
contents: []
|
|
665
|
+
};
|
|
666
|
+
const weslContext = cc.app.context;
|
|
667
|
+
weslContext.openElems.push(partialElem);
|
|
668
|
+
},
|
|
669
|
+
after: (cc) => {
|
|
670
|
+
const weslContext = cc.app.context;
|
|
671
|
+
const partialElem = weslContext.openElems.pop();
|
|
672
|
+
console.assert(partialElem && partialElem.kind === kind$1);
|
|
673
|
+
const elem = fn$1(cc, {
|
|
674
|
+
...partialElem,
|
|
675
|
+
start: cc.start,
|
|
676
|
+
end: cc.end
|
|
677
|
+
});
|
|
678
|
+
if (elem) addToOpenElem(cc, elem);
|
|
679
|
+
return elem;
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* @return a copy of the element with contents extended
|
|
685
|
+
* to include TextElems to cover the entire range.
|
|
686
|
+
*/
|
|
687
|
+
function withTextCover(elem, cc) {
|
|
688
|
+
const contents = coverWithText(cc, elem);
|
|
689
|
+
return {
|
|
690
|
+
...elem,
|
|
691
|
+
contents
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
/** cover the entire source range with Elems by creating TextElems to
|
|
695
|
+
* cover any parts of the source that are not covered by other elems
|
|
696
|
+
* @returns the existing elems combined with any new TextElems, in src order */
|
|
697
|
+
function coverWithText(cc, elem) {
|
|
698
|
+
let { start: pos } = cc;
|
|
699
|
+
const ast = cc.app.stable;
|
|
700
|
+
const { contents, end } = elem;
|
|
701
|
+
const sorted = contents.sort((a, b) => a.start - b.start);
|
|
702
|
+
const elems = [];
|
|
703
|
+
for (const elem$1 of sorted) {
|
|
704
|
+
if (pos < elem$1.start) elems.push(makeTextElem(elem$1.start));
|
|
705
|
+
elems.push(elem$1);
|
|
706
|
+
pos = elem$1.end;
|
|
707
|
+
}
|
|
708
|
+
if (pos < end) elems.push(makeTextElem(end));
|
|
709
|
+
return elems;
|
|
710
|
+
function makeTextElem(end$1) {
|
|
711
|
+
return {
|
|
712
|
+
kind: "text",
|
|
713
|
+
start: pos,
|
|
714
|
+
end: end$1,
|
|
715
|
+
srcModule: ast.srcModule
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
//#endregion
|
|
721
|
+
//#region ../wesl/src/parse/WeslBaseGrammar.ts
|
|
722
|
+
const word = kind("word");
|
|
723
|
+
const keyword = kind("keyword");
|
|
724
|
+
const qualified_ident = withSepPlus("::", or(word, keyword, "package", "super"));
|
|
725
|
+
const number = kind("number");
|
|
726
|
+
|
|
727
|
+
//#endregion
|
|
728
|
+
//#region ../wesl/src/parse/ImportGrammar.ts
|
|
729
|
+
function makeStatement(segments, finalSegment) {
|
|
730
|
+
return {
|
|
731
|
+
kind: "import-statement",
|
|
732
|
+
segments,
|
|
733
|
+
finalSegment
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
function makeSegment(name$1) {
|
|
737
|
+
return {
|
|
738
|
+
kind: "import-segment",
|
|
739
|
+
name: name$1
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
function makeCollection(subtrees) {
|
|
743
|
+
return {
|
|
744
|
+
kind: "import-collection",
|
|
745
|
+
subtrees
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function makeItem(name$1, as) {
|
|
749
|
+
return {
|
|
750
|
+
kind: "import-item",
|
|
751
|
+
name: name$1,
|
|
752
|
+
as
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function prependSegments(segments, statement$1) {
|
|
756
|
+
statement$1.segments = segments.concat(statement$1.segments);
|
|
757
|
+
return statement$1;
|
|
758
|
+
}
|
|
759
|
+
let import_collection = null;
|
|
760
|
+
const segment_blacklist = or("super", "package", "import", "as");
|
|
761
|
+
/** words allowed in an import segment (after the package:: or super::super:: part if any) */
|
|
762
|
+
const packageWord = preceded(not(segment_blacklist), or(word, keyword));
|
|
763
|
+
const import_path_or_item = seq(packageWord, or(preceded("::", req(or(fn(() => import_collection), fn(() => import_path_or_item)), "invalid import, expected '{' or name")), preceded("as", req(word, "invalid alias, expected name")).map((v) => makeItem("", v)), yes().map(() => makeItem("")))).map(([name$1, next]) => {
|
|
764
|
+
if (next.kind === "import-collection") return makeStatement([makeSegment(name$1)], next);
|
|
765
|
+
else if (next.kind === "import-statement") return prependSegments([makeSegment(name$1)], next);
|
|
766
|
+
else if (next.kind === "import-item") {
|
|
767
|
+
next.name = name$1;
|
|
768
|
+
return makeStatement([], next);
|
|
769
|
+
} else assertUnreachable(next);
|
|
770
|
+
});
|
|
771
|
+
import_collection = delimited("{", withSepPlus(",", () => import_path_or_item).map(makeCollection), req("}", "invalid import collection, expected }"));
|
|
772
|
+
const import_relative = or(terminated("package", req("::", "invalid import, expected '::'")).map((v) => [makeSegment(v)]), repeatPlus(terminated("super", req("::", "invalid import, expected '::'")).map(makeSegment)));
|
|
773
|
+
const import_statement = span(delimited("import", seqObj({
|
|
774
|
+
relative: opt(import_relative),
|
|
775
|
+
collection_or_statement: req(or(import_collection, import_path_or_item), "invalid import, expected { or name")
|
|
776
|
+
}).map(({ relative, collection_or_statement }) => {
|
|
777
|
+
if (collection_or_statement.kind === "import-statement") return prependSegments(relative ?? [], collection_or_statement);
|
|
778
|
+
else return makeStatement(relative ?? [], collection_or_statement);
|
|
779
|
+
}), req(";", "invalid import, expected ';'"))).map((v) => ({
|
|
780
|
+
kind: "import",
|
|
781
|
+
imports: v.value,
|
|
782
|
+
start: v.span[0],
|
|
783
|
+
end: v.span[1]
|
|
784
|
+
}));
|
|
785
|
+
/** parse a WESL style wgsl import statement. */
|
|
786
|
+
const weslImports = tagScope(repeat(import_statement).ptag("owo").collect(importElem));
|
|
787
|
+
if (tracing) {
|
|
788
|
+
const names = {
|
|
789
|
+
import_collection,
|
|
790
|
+
import_path_or_item,
|
|
791
|
+
import_relative,
|
|
792
|
+
import_statement,
|
|
793
|
+
weslImports
|
|
794
|
+
};
|
|
795
|
+
Object.entries(names).forEach(([name$1, parser]) => {
|
|
796
|
+
parser.setTraceName(name$1);
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
//#endregion
|
|
801
|
+
//#region ../wesl/src/parse/Keywords.ts
|
|
802
|
+
/** https://www.w3.org/TR/WGSL/#keyword-summary */
|
|
803
|
+
const keywords = `alias break case const const_assert continue continuing
|
|
804
|
+
default diagnostic discard else enable false fn for if
|
|
805
|
+
let loop override requires return struct switch true var while`.split(/\s+/);
|
|
806
|
+
/** https://www.w3.org/TR/WGSL/#reserved-words */
|
|
807
|
+
const reservedWords = `NULL Self abstract active alignas alignof as asm asm_fragment async attribute auto await
|
|
808
|
+
become binding_array cast catch class co_await co_return co_yield coherent column_major
|
|
809
|
+
common compile compile_fragment concept const_cast consteval constexpr constinit crate
|
|
810
|
+
debugger decltype delete demote demote_to_helper do dynamic_cast
|
|
811
|
+
enum explicit export extends extern external fallthrough filter final finally friend from fxgroup
|
|
812
|
+
get goto groupshared highp impl implements import inline instanceof interface layout lowp
|
|
813
|
+
macro macro_rules match mediump meta mod module move mut mutable
|
|
814
|
+
namespace new nil noexcept noinline nointerpolation non_coherent noncoherent noperspective null nullptr
|
|
815
|
+
of operator package packoffset partition pass patch pixelfragment precise precision premerge
|
|
816
|
+
priv protected pub public readonly ref regardless register reinterpret_cast require resource restrict
|
|
817
|
+
self set shared sizeof smooth snorm static static_assert static_cast std subroutine super
|
|
818
|
+
target template this thread_local throw trait try type typedef typeid typename typeof
|
|
819
|
+
union unless unorm unsafe unsized use using varying virtual volatile wgsl where with writeonly yield`.split(/\s+/);
|
|
820
|
+
|
|
821
|
+
//#endregion
|
|
822
|
+
//#region ../wesl/src/parse/WeslStream.ts
|
|
823
|
+
/** Whitespaces including new lines */
|
|
824
|
+
const blankspaces = /[ \t\n\v\f\r\u{0085}\u{200E}\u{200F}\u{2028}\u{2029}]+/u;
|
|
825
|
+
const symbolSet = "& && -> @ / ! [ ] { } :: : , == = != >>= >> >= > <<= << <= < % - -- . + ++ | || ( ) ; * ~ ^ // /* */ += -= *= /= %= &= |= ^= _";
|
|
826
|
+
const ident = /(?:(?:[_\p{XID_Start}][\p{XID_Continue}]+)|(?:[\p{XID_Start}]))/u;
|
|
827
|
+
const keywordOrReserved = new Set(keywords.concat(reservedWords));
|
|
828
|
+
const digits = new RegExp(/(?:0[fh])|(?:[1-9][0-9]*[fh])/.source + /|(?:[0-9]*\.[0-9]+(?:[eE][+-]?[0-9]+)?[fh]?)/.source + /|(?:[0-9]+\.[0-9]*(?:[eE][+-]?[0-9]+)?[fh]?)/.source + /|(?:[0-9]+[eE][+-]?[0-9]+[fh]?)/.source + /|(?:0[xX][0-9a-fA-F]*\.[0-9a-fA-F]+(?:[pP][+-]?[0-9]+[fh]?)?)/.source + /|(?:0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*(?:[pP][+-]?[0-9]+[fh]?)?)/.source + /|(?:0[xX][0-9a-fA-F]+[pP][+-]?[0-9]+[fh]?)/.source + /|(?:0[xX][0-9a-fA-F]+[iu]?)/.source + /|(?:0[iu]?)|(?:[1-9][0-9]*[iu]?)/.source);
|
|
829
|
+
const commentStart = /\/\/|\/\*/;
|
|
830
|
+
const weslMatcher = new RegexMatchers({
|
|
831
|
+
word: ident,
|
|
832
|
+
number: digits,
|
|
833
|
+
blankspaces,
|
|
834
|
+
commentStart,
|
|
835
|
+
symbol: matchOneOf(symbolSet),
|
|
836
|
+
invalid: /[^]/
|
|
837
|
+
});
|
|
838
|
+
/** To mark parts of the grammar implementation that are WESL specific extensions */
|
|
839
|
+
function weslExtension(combinator) {
|
|
840
|
+
return combinator;
|
|
841
|
+
}
|
|
842
|
+
/** A stream that produces WESL tokens, skipping over comments and white space */
|
|
843
|
+
var WeslStream = class {
|
|
844
|
+
stream;
|
|
845
|
+
/** New line */
|
|
846
|
+
eolPattern = /[\n\v\f\u{0085}\u{2028}\u{2029}]|\r\n?/gu;
|
|
847
|
+
/** Block comments */
|
|
848
|
+
blockCommentPattern = /\/\*|\*\//g;
|
|
849
|
+
constructor(src) {
|
|
850
|
+
this.src = src;
|
|
851
|
+
this.stream = new CachingStream(new MatchersStream(src, weslMatcher));
|
|
852
|
+
}
|
|
853
|
+
checkpoint() {
|
|
854
|
+
return this.stream.checkpoint();
|
|
855
|
+
}
|
|
856
|
+
reset(position) {
|
|
857
|
+
this.stream.reset(position);
|
|
858
|
+
}
|
|
859
|
+
nextToken() {
|
|
860
|
+
while (true) {
|
|
861
|
+
const token$1 = this.stream.nextToken();
|
|
862
|
+
if (token$1 === null) return null;
|
|
863
|
+
const kind$1 = token$1.kind;
|
|
864
|
+
if (kind$1 === "blankspaces") continue;
|
|
865
|
+
else if (kind$1 === "commentStart") if (token$1.text === "//") this.stream.reset(this.skipToEol(token$1.span[1]));
|
|
866
|
+
else this.stream.reset(this.skipBlockComment(token$1.span[1]));
|
|
867
|
+
else if (kind$1 === "word") {
|
|
868
|
+
const returnToken = token$1;
|
|
869
|
+
if (keywordOrReserved.has(token$1.text)) returnToken.kind = "keyword";
|
|
870
|
+
return returnToken;
|
|
871
|
+
} else if (kind$1 === "invalid") throw new ParseError("Invalid token " + token$1.text, token$1.span);
|
|
872
|
+
else return token$1;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
skipToEol(position) {
|
|
876
|
+
this.eolPattern.lastIndex = position;
|
|
877
|
+
const result = this.eolPattern.exec(this.src);
|
|
878
|
+
if (result === null) return this.src.length;
|
|
879
|
+
else return this.eolPattern.lastIndex;
|
|
880
|
+
}
|
|
881
|
+
skipBlockComment(start) {
|
|
882
|
+
let position = start;
|
|
883
|
+
while (true) {
|
|
884
|
+
this.blockCommentPattern.lastIndex = position;
|
|
885
|
+
const result = this.blockCommentPattern.exec(this.src);
|
|
886
|
+
if (result === null) throw new ParseError("Unclosed block comment!", [position, position]);
|
|
887
|
+
else if (result[0] === "*/") return this.blockCommentPattern.lastIndex;
|
|
888
|
+
else if (result[0] === "/*") position = this.skipBlockComment(this.blockCommentPattern.lastIndex);
|
|
889
|
+
else throw new Error("Unreachable, invalid block comment pattern");
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Only matches the `<` token if it is a template
|
|
894
|
+
* Precondition: An ident was parsed right before this.
|
|
895
|
+
* Runs the [template list discovery algorithm](https://www.w3.org/TR/WGSL/#template-list-discovery).
|
|
896
|
+
*/
|
|
897
|
+
nextTemplateStartToken() {
|
|
898
|
+
const startPosition = this.stream.checkpoint();
|
|
899
|
+
const token$1 = this.nextToken();
|
|
900
|
+
this.stream.reset(startPosition);
|
|
901
|
+
if (token$1 === null) return null;
|
|
902
|
+
if (token$1.kind !== "symbol") return null;
|
|
903
|
+
if (token$1.text === "<") if (this.isTemplateStart(token$1.span[1])) {
|
|
904
|
+
this.stream.reset(token$1.span[1]);
|
|
905
|
+
return token$1;
|
|
906
|
+
} else {
|
|
907
|
+
this.stream.reset(startPosition);
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
else return null;
|
|
911
|
+
}
|
|
912
|
+
nextTemplateEndToken() {
|
|
913
|
+
const startPosition = this.stream.checkpoint();
|
|
914
|
+
const token$1 = this.nextToken();
|
|
915
|
+
this.stream.reset(startPosition);
|
|
916
|
+
if (token$1 === null) return null;
|
|
917
|
+
if (token$1.kind === "symbol" && token$1.text[0] === ">") {
|
|
918
|
+
const tokenPosition = token$1.span[0];
|
|
919
|
+
this.stream.reset(tokenPosition + 1);
|
|
920
|
+
return {
|
|
921
|
+
kind: "symbol",
|
|
922
|
+
span: [tokenPosition, tokenPosition + 1],
|
|
923
|
+
text: ">"
|
|
924
|
+
};
|
|
925
|
+
} else return null;
|
|
926
|
+
}
|
|
927
|
+
isTemplateStart(afterToken) {
|
|
928
|
+
this.stream.reset(afterToken);
|
|
929
|
+
let pendingCounter = 1;
|
|
930
|
+
while (true) {
|
|
931
|
+
const nextToken = this.stream.nextToken();
|
|
932
|
+
if (nextToken === null) return false;
|
|
933
|
+
if (nextToken.kind !== "symbol") continue;
|
|
934
|
+
if (nextToken.text === "<") pendingCounter += 1;
|
|
935
|
+
else if (nextToken.text[0] === ">") {
|
|
936
|
+
if (nextToken.text === ">" || nextToken.text === ">=") pendingCounter -= 1;
|
|
937
|
+
else if (nextToken.text === ">>=" || nextToken.text === ">>") pendingCounter -= 2;
|
|
938
|
+
else throw new Error("This case should never be reached, looks like we forgot one of the tokens that start with >");
|
|
939
|
+
if (pendingCounter <= 0) return true;
|
|
940
|
+
} else if (nextToken.text === "(") this.skipBracketsTo(")");
|
|
941
|
+
else if (nextToken.text === "[") this.skipBracketsTo("]");
|
|
942
|
+
else if (nextToken.text === "==" || nextToken.text === "!=" || nextToken.text === ";" || nextToken.text === "{" || nextToken.text === ":" || nextToken.text === "&&" || nextToken.text === "||") return false;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Call this after consuming an opening bracket.
|
|
947
|
+
* Skips until a closing bracket. This also consumes the closing bracket.
|
|
948
|
+
*/
|
|
949
|
+
skipBracketsTo(closingBracket) {
|
|
950
|
+
while (true) {
|
|
951
|
+
const nextToken = this.stream.nextToken();
|
|
952
|
+
if (nextToken === null) {
|
|
953
|
+
const after = this.stream.checkpoint();
|
|
954
|
+
throw new ParseError("Unclosed bracket!", [after, after]);
|
|
955
|
+
}
|
|
956
|
+
if (nextToken.kind !== "symbol") continue;
|
|
957
|
+
if (nextToken.text === "(") this.skipBracketsTo(")");
|
|
958
|
+
else if (nextToken.text === "[") this.skipBracketsTo("]");
|
|
959
|
+
else if (nextToken.text === closingBracket) return;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
const templateOpen = withStreamAction((stream) => {
|
|
964
|
+
return stream.nextTemplateStartToken();
|
|
965
|
+
});
|
|
966
|
+
const templateClose = withStreamAction((stream) => {
|
|
967
|
+
return stream.nextTemplateEndToken();
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
//#endregion
|
|
971
|
+
//#region ../wesl/src/parse/WeslExpression.ts
|
|
972
|
+
const opt_template_list = opt(seq(templateOpen, withSepPlus(",", () => template_parameter), req(templateClose, "invalid template, expected '>'")));
|
|
973
|
+
const other_address_space = or("private", "workgroup", "uniform", "function");
|
|
974
|
+
const storage_address_space = seq("storage", opt(seq(",", or("read", "read_write"))));
|
|
975
|
+
const var_template_list = opt(seq(templateOpen, or(storage_address_space, other_address_space), req(templateClose, "invalid template, expected '>'")));
|
|
976
|
+
const template_elaborated_ident = seq(qualified_ident.collect(refIdent), opt_template_list);
|
|
977
|
+
const literal = or("true", "false", number);
|
|
978
|
+
const paren_expression = seq("(", () => expression, req(")", "invalid expression, expected ')'"));
|
|
979
|
+
const primary_expression = or(literal, paren_expression, seq(template_elaborated_ident, opt(fn(() => argument_expression_list))));
|
|
980
|
+
const component_or_swizzle = repeatPlus(or(preceded(".", word), collectArray(delimited("[", () => expression, req("]", "invalid expression, expected ']'")))));
|
|
981
|
+
/** parse simple struct.member style references specially, for binding struct lowering */
|
|
982
|
+
const simple_component_reference = tagScope(seq(qualified_ident.collect(refIdent, "structRef"), seq(".", word.collect(nameCollect, "component")), opt(component_or_swizzle.collect(stuffCollect, "extra_components"))).collect(memberRefCollect));
|
|
983
|
+
const unary_expression = or(seq(tokenOf("symbol", [
|
|
984
|
+
"!",
|
|
985
|
+
"&",
|
|
986
|
+
"*",
|
|
987
|
+
"-",
|
|
988
|
+
"~"
|
|
989
|
+
]), () => unary_expression), or(simple_component_reference, seq(primary_expression, opt(component_or_swizzle))));
|
|
990
|
+
const bitwise_post_unary = or(repeatPlus(seq("&", unary_expression)), repeatPlus(seq("^", unary_expression)), repeatPlus(seq("|", unary_expression)));
|
|
991
|
+
const multiplicative_operator = or("%", "*", "/");
|
|
992
|
+
const additive_operator = or("+", "-");
|
|
993
|
+
const shift_post_unary = (inTemplate) => {
|
|
994
|
+
const shift_left = seq("<<", unary_expression);
|
|
995
|
+
const shift_right = seq(">>", unary_expression);
|
|
996
|
+
const mul_add = seq(repeat(seq(multiplicative_operator, unary_expression)), repeat(seq(additive_operator, unary_expression, repeat(seq(multiplicative_operator, unary_expression)))));
|
|
997
|
+
return inTemplate ? or(shift_left, mul_add) : or(shift_left, shift_right, mul_add);
|
|
998
|
+
};
|
|
999
|
+
const relational_post_unary = (inTemplate) => {
|
|
1000
|
+
return seq(shift_post_unary(inTemplate), opt(seq(inTemplate ? tokenOf("symbol", [
|
|
1001
|
+
"<",
|
|
1002
|
+
"<=",
|
|
1003
|
+
"!=",
|
|
1004
|
+
"=="
|
|
1005
|
+
]) : tokenOf("symbol", [
|
|
1006
|
+
">",
|
|
1007
|
+
">=",
|
|
1008
|
+
"<",
|
|
1009
|
+
"<=",
|
|
1010
|
+
"!=",
|
|
1011
|
+
"=="
|
|
1012
|
+
]), unary_expression, shift_post_unary(inTemplate))));
|
|
1013
|
+
};
|
|
1014
|
+
/** The expression parser exists in two variants
|
|
1015
|
+
* `true` is template-expression: Refuses to parse parse symbols like `&&` and `||`.
|
|
1016
|
+
* `false` is maybe-template-expression: Does the template disambiguation.
|
|
1017
|
+
*/
|
|
1018
|
+
const expressionParser = (inTemplate) => {
|
|
1019
|
+
return seq(unary_expression, or(bitwise_post_unary, seq(relational_post_unary(inTemplate), inTemplate ? yes() : or(repeatPlus(seq("||", seq(unary_expression, relational_post_unary(false)))), repeatPlus(seq("&&", seq(unary_expression, relational_post_unary(false)))), yes().map(() => [])))));
|
|
1020
|
+
};
|
|
1021
|
+
const maybe_template = false;
|
|
1022
|
+
const expression = expressionParser(maybe_template);
|
|
1023
|
+
const is_template = true;
|
|
1024
|
+
const template_arg_expression = expressionParser(is_template);
|
|
1025
|
+
const std_type_specifier = seq(qualified_ident.collect(refIdent, "typeRefName"), () => opt_template_list).collect(typeRefCollect);
|
|
1026
|
+
const type_specifier = tagScope(std_type_specifier).ctag("typeRefElem");
|
|
1027
|
+
/** a template_arg_expression with additional collection for parameters
|
|
1028
|
+
* that are types like array<f32> vs. expressions like 1+2 */
|
|
1029
|
+
const template_parameter = or(type_specifier.ctag("templateParam"), template_arg_expression.collect(expressionCollect, "templateParam"));
|
|
1030
|
+
const argument_expression_list = seq("(", withSep(",", expression), req(")", "invalid fn arguments, expected ')'"));
|
|
1031
|
+
if (tracing) {
|
|
1032
|
+
const names = {
|
|
1033
|
+
opt_template_list,
|
|
1034
|
+
other_address_space,
|
|
1035
|
+
storage_address_space,
|
|
1036
|
+
var_template_list,
|
|
1037
|
+
template_elaborated_ident,
|
|
1038
|
+
primary_expression,
|
|
1039
|
+
literal,
|
|
1040
|
+
paren_expression,
|
|
1041
|
+
component_or_swizzle,
|
|
1042
|
+
simple_component_reference,
|
|
1043
|
+
unary_expression,
|
|
1044
|
+
bitwise_post_unary,
|
|
1045
|
+
multiplicative_operator,
|
|
1046
|
+
additive_operator,
|
|
1047
|
+
expression,
|
|
1048
|
+
template_arg_expression,
|
|
1049
|
+
std_type_specifier,
|
|
1050
|
+
type_specifier,
|
|
1051
|
+
template_parameter,
|
|
1052
|
+
argument_expression_list
|
|
1053
|
+
};
|
|
1054
|
+
Object.entries(names).forEach(([name$1, parser]) => {
|
|
1055
|
+
parser.setTraceName(name$1);
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
//#endregion
|
|
1060
|
+
//#region ../wesl/src/parse/WeslGrammar.ts
|
|
1061
|
+
const name = tokenKind("word").map(makeName);
|
|
1062
|
+
const diagnostic_rule_name = seq(name, opt(preceded(".", req(name, "invalid diagnostic rule name, expected name"))));
|
|
1063
|
+
const diagnostic_control = delimited("(", req(separated_pair(name, ",", diagnostic_rule_name), "invalid diagnostic control, expected rule name"), seq(opt(","), req(")", "invalid diagnostic control, expected ')'")));
|
|
1064
|
+
/** list of words that aren't identifiers (e.g. for @interpolate) */
|
|
1065
|
+
const name_list = withSep(",", name, { requireOne: true });
|
|
1066
|
+
const special_attribute = tagScope(preceded("@", or(or("compute", "const", "fragment", "invariant", "must_use", "vertex").map((name$1) => makeStandardAttribute([name$1, []])), preceded("interpolate", req(delimited("(", name_list, ")"), "invalid @interpolate, expected '('")).map(makeInterpolateAttribute), preceded("builtin", req(delimited("(", name, ")"), "invalid @builtin, expected '('")).map(makeBuiltinAttribute), preceded("diagnostic", req(diagnostic_control, "invalid @diagnostic, expected '('")).map(makeDiagnosticAttribute)).ptag("attr_variant")).collect(specialAttribute));
|
|
1067
|
+
const if_attribute = tagScope(preceded(seq("@", weslExtension("if")), span(delimited("(", fn(() => attribute_if_expression), seq(opt(","), ")"))).map(makeTranslateTimeExpressionElem)).map(makeIfAttribute).ptag("attr_variant").collect(specialAttribute));
|
|
1068
|
+
const normal_attribute = tagScope(preceded("@", or(seq(or("workgroup_size", "align", "binding", "blend_src", "group", "id", "location", "size").ptag("name"), req(() => attribute_argument_list, "invalid attribute, expected '('")), seq(word.ptag("name"), opt(() => attribute_argument_list)))).collect(collectAttribute));
|
|
1069
|
+
const attribute_argument_list = delimited("(", withSep(",", span(fn(() => expression)).collect(expressionCollect, "attrParam")), req(")", "invalid attribute arguments, expected ')'"));
|
|
1070
|
+
const attribute_no_if = or(special_attribute, normal_attribute).ctag("attribute");
|
|
1071
|
+
const attribute_incl_if = or(if_attribute, special_attribute, normal_attribute).ctag("attribute");
|
|
1072
|
+
const opt_attributes = repeat(attribute_incl_if);
|
|
1073
|
+
const opt_attributes_no_if = repeat(attribute_no_if);
|
|
1074
|
+
const globalTypeNameDecl = req(word.collect(globalDeclCollect, "type_name"), "invalid type name, expected a name");
|
|
1075
|
+
const fnNameDecl = req(word.collect(globalDeclCollect, "fn_name"), "missing fn name");
|
|
1076
|
+
const optionally_typed_ident = tagScope(seq(word.collect(declCollect, "decl_elem"), opt(seq(":", type_specifier))).collect(typedDecl)).ctag("var_name");
|
|
1077
|
+
const req_optionally_typed_ident = req(optionally_typed_ident, "invalid ident");
|
|
1078
|
+
const global_ident = tagScope(req(seq(word.collect(globalDeclCollect, "decl_elem"), opt(seq(":", type_specifier.collect(scopeCollect, "decl_type")))).collect(typedDecl), "expected identifier")).ctag("var_name");
|
|
1079
|
+
const struct_member = tagScope(seq(opt_attributes, word.collect(nameCollect, "nameElem"), req(":", "invalid struct member, expected ':'"), req(type_specifier, "invalid struct member, expected type specifier")).collect(collectStructMember)).ctag("members");
|
|
1080
|
+
const struct_decl = seq(weslExtension(opt_attributes).collect((cc) => cc.tags.attribute, "attributes"), "struct", req(globalTypeNameDecl, "invalid struct, expected name"), seq(req("{", "invalid struct, expected '{'"), withSepPlus(",", struct_member), req("}", "invalid struct, expected '}'")).collect(scopeCollect, "struct_scope")).collect(collectStruct);
|
|
1081
|
+
/** Also covers func_call_statement.post.ident */
|
|
1082
|
+
const fn_call = seq(qualified_ident.collect(refIdent), () => opt_template_list, argument_expression_list);
|
|
1083
|
+
const fnParam = tagScope(seq(opt_attributes.collect((cc) => cc.tags.attribute, "attributes"), word.collect(declCollect, "decl_elem"), opt(seq(":", req(type_specifier, "invalid fn parameter, expected type specifier"))).collect(typedDecl, "param_name")).collect(collectFnParam)).ctag("fn_param");
|
|
1084
|
+
const fnParamList = seq("(", withSep(",", fnParam), ")");
|
|
1085
|
+
const local_variable_decl = seq("var", () => var_template_list, req_optionally_typed_ident, opt(seq("=", () => expression))).collect(collectVarLike("var"));
|
|
1086
|
+
const global_variable_decl = seq(opt_attributes, "var", () => var_template_list, global_ident, opt(seq("=", () => expression.collect(scopeCollect, "decl_scope"))), ";").collect(collectVarLike("gvar")).collect(partialScopeCollect);
|
|
1087
|
+
const attribute_if_primary_expression = or(tokenOf("keyword", ["true", "false"]).map(makeLiteral), delimited(token("symbol", "("), fn(() => attribute_if_expression), token("symbol", ")")).map(makeParenthesizedExpression), tokenKind("word").map(makeTranslateTimeFeature));
|
|
1088
|
+
const attribute_if_unary_expression = or(seq(token("symbol", "!").map(makeUnaryOperator), fn(() => attribute_if_unary_expression)).map(makeUnaryExpression), attribute_if_primary_expression);
|
|
1089
|
+
const attribute_if_expression = weslExtension(seq(attribute_if_unary_expression, or(repeatPlus(seq(token("symbol", "||").map(makeBinaryOperator), req(attribute_if_unary_expression, "invalid expression, expected expression"))), repeatPlus(seq(token("symbol", "&&").map(makeBinaryOperator), req(attribute_if_unary_expression, "invalid expression, expected expression"))), yes().map(() => []))).map(makeRepeatingBinaryExpression));
|
|
1090
|
+
const unscoped_compound_statement = seq(opt_attributes, text("{"), repeat(() => statement), req("}", "invalid block, expected }")).collect(statementCollect);
|
|
1091
|
+
const compound_statement = tagScope(seq(opt_attributes, seq(text("{"), repeat(() => statement), req("}", "invalid block, expected '}'")).collect(scopeCollect)).collect(statementCollect));
|
|
1092
|
+
const for_init = seq(opt_attributes, or(fn_call, () => variable_or_value_statement, () => variable_updating_statement));
|
|
1093
|
+
const for_update = seq(opt_attributes, or(fn_call, () => variable_updating_statement));
|
|
1094
|
+
const for_statement = seq("for", seq(req("(", "invalid for loop, expected '('"), opt(for_init), req(";", "invalid for loop, expected ';'"), opt(expression), req(";", "invalid for loop, expected ';'"), opt(for_update), req(")", "invalid for loop, expected ')'"), unscoped_compound_statement).collect(scopeCollect));
|
|
1095
|
+
const if_statement = seq("if", req(seq(expression, compound_statement), "invalid if statement"), repeat(seq("else", "if", req(seq(expression, compound_statement), "invalid else if branch"))), opt(seq("else", req(compound_statement, "invalid else branch, expected '{'"))));
|
|
1096
|
+
const loop_statement = seq("loop", opt_attributes_no_if, req(seq("{", repeat(() => statement), opt(tagScope(seq(opt_attributes, "continuing", opt_attributes_no_if, "{", repeat(() => statement), tagScope(opt(seq(opt_attributes, seq("break", "if", expression, ";")).collect(statementCollect))), "}").collect(statementCollect).collect(scopeCollect))), "}"), "invalid loop statement")).collect(scopeCollect);
|
|
1097
|
+
const case_selector = or("default", expression);
|
|
1098
|
+
const switch_clause = tagScope(seq(opt_attributes, or(seq("case", withSep(",", case_selector, { requireOne: true }), opt(":"), compound_statement), seq("default", opt(":"), compound_statement)).collect(switchClauseCollect)));
|
|
1099
|
+
const switch_body = seq(opt_attributes, "{", repeatPlus(switch_clause), "}");
|
|
1100
|
+
const switch_statement = seq("switch", expression, switch_body);
|
|
1101
|
+
const while_statement = seq("while", expression, compound_statement);
|
|
1102
|
+
const regular_statement = or(for_statement, if_statement, loop_statement, switch_statement, while_statement, seq("break", ";"), seq("continue", req(";", "invalid statement, expected ';'")), seq(";"), () => const_assert, seq("discard", req(";", "invalid statement, expected ';'")), seq("return", opt(expression), req(";", "invalid statement, expected ';'")), seq(fn_call, req(";", "invalid statement, expected ';'")), seq(() => variable_or_value_statement, req(";", "invalid statement, expected ';'")), seq(() => variable_updating_statement, req(";", "invalid statement, expected ';'")));
|
|
1103
|
+
const conditional_statement = tagScope(seq(opt_attributes, regular_statement).collect(statementCollect).collect(partialScopeCollect));
|
|
1104
|
+
const unconditional_statement = tagScope(seq(opt_attributes_no_if, regular_statement));
|
|
1105
|
+
const statement = or(compound_statement, unconditional_statement, conditional_statement);
|
|
1106
|
+
const lhs_expression = or(simple_component_reference, seq(qualified_ident.collect(refIdent), opt(component_or_swizzle)), seq("(", () => lhs_expression, ")", opt(component_or_swizzle)), seq("&", () => lhs_expression), seq("*", () => lhs_expression));
|
|
1107
|
+
const variable_or_value_statement = tagScope(or(local_variable_decl, seq("const", req_optionally_typed_ident, req("=", "invalid const declaration, expected '='"), expression), seq("let", req_optionally_typed_ident, req("=", "invalid let declaration, expected '='"), expression)));
|
|
1108
|
+
const variable_updating_statement = or(seq(lhs_expression, or("=", "<<=", ">>=", "%=", "&=", "*=", "+=", "-=", "/=", "^=", "|="), expression), seq(lhs_expression, or("++", "--")), seq("_", "=", expression));
|
|
1109
|
+
const fn_decl = seq(tagScope(opt_attributes.collect((cc) => cc.tags.attribute || [])).ctag("fn_attributes"), text("fn"), req(fnNameDecl, "invalid fn, expected function name"), seq(req(fnParamList, "invalid fn, expected function parameters").collect(scopeCollect, "header_scope"), opt(seq("->", opt_attributes.collect((cc) => cc.tags.attribute, "return_attributes"), type_specifier.ctag("return_type").collect(scopeCollect, "return_scope"))), req(unscoped_compound_statement, "invalid fn, expected function body").ctag("body_statement").collect(scopeCollect, "body_scope"))).collect(partialScopeCollect, "fn_partial_scope").collect(fnCollect);
|
|
1110
|
+
const global_value_decl = or(seq(opt_attributes, "override", global_ident, seq(opt(seq("=", expression.collect(scopeCollectNoIf, "decl_scope")))), ";").collect(collectVarLike("override")), seq(opt_attributes, "const", global_ident, "=", seq(expression).collect(scopeCollectNoIf, "decl_scope"), ";").collect(collectVarLike("const"))).collect(partialScopeCollect);
|
|
1111
|
+
const global_alias = seq(weslExtension(opt_attributes).collect((cc) => cc.tags.attribute, "attributes"), "alias", req(word, "invalid alias, expected name").collect(globalDeclCollect, "alias_name"), req("=", "invalid alias, expected '='"), req(type_specifier, "invalid alias, expected type").collect(scopeCollect, "alias_scope"), req(";", "invalid alias, expected ';'")).collect(aliasCollect);
|
|
1112
|
+
const const_assert = tagScope(seq(opt_attributes, "const_assert", req(expression, "invalid const_assert, expected expression"), req(";", "invalid statement, expected ';'")).collect(assertCollect)).ctag("const_assert");
|
|
1113
|
+
const global_directive = tagScope(seq(opt_attributes, terminated(or(preceded("diagnostic", diagnostic_control).map(makeDiagnosticDirective), preceded("enable", name_list).map(makeEnableDirective), preceded("requires", name_list).map(makeRequiresDirective)).ptag("directive"), ";")).collect(directiveCollect));
|
|
1114
|
+
const global_decl = tagScope(or(fn_decl, global_variable_decl, global_value_decl, ";", global_alias, const_assert.collect(globalAssertCollect), struct_decl));
|
|
1115
|
+
const weslRoot = seq(weslExtension(weslImports), repeat(global_directive), repeat(global_decl), req(eof(), "invalid WESL, expected EOF")).collect(collectModule, "collectModule");
|
|
1116
|
+
function makeDiagnosticDirective([severity, rule]) {
|
|
1117
|
+
return {
|
|
1118
|
+
kind: "diagnostic",
|
|
1119
|
+
severity,
|
|
1120
|
+
rule
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
function makeEnableDirective(extensions) {
|
|
1124
|
+
return {
|
|
1125
|
+
kind: "enable",
|
|
1126
|
+
extensions
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
function makeRequiresDirective(extensions) {
|
|
1130
|
+
return {
|
|
1131
|
+
kind: "requires",
|
|
1132
|
+
extensions
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
function makeStandardAttribute([name$1, params]) {
|
|
1136
|
+
return {
|
|
1137
|
+
kind: "@attribute",
|
|
1138
|
+
name: name$1,
|
|
1139
|
+
params
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
function makeInterpolateAttribute(params) {
|
|
1143
|
+
return {
|
|
1144
|
+
kind: "@interpolate",
|
|
1145
|
+
params
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
function makeBuiltinAttribute(param) {
|
|
1149
|
+
return {
|
|
1150
|
+
kind: "@builtin",
|
|
1151
|
+
param
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
function makeDiagnosticAttribute([severity, rule]) {
|
|
1155
|
+
return {
|
|
1156
|
+
kind: "@diagnostic",
|
|
1157
|
+
severity,
|
|
1158
|
+
rule
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
function makeIfAttribute(param) {
|
|
1162
|
+
return {
|
|
1163
|
+
kind: "@if",
|
|
1164
|
+
param
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
function makeTranslateTimeExpressionElem(args) {
|
|
1168
|
+
return {
|
|
1169
|
+
kind: "translate-time-expression",
|
|
1170
|
+
expression: args.value,
|
|
1171
|
+
span: args.span
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
function makeName(token$1) {
|
|
1175
|
+
return {
|
|
1176
|
+
kind: "name",
|
|
1177
|
+
name: token$1.text,
|
|
1178
|
+
start: token$1.span[0],
|
|
1179
|
+
end: token$1.span[1]
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
function makeLiteral(token$1) {
|
|
1183
|
+
return {
|
|
1184
|
+
kind: "literal",
|
|
1185
|
+
value: token$1.text,
|
|
1186
|
+
span: token$1.span
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
function makeTranslateTimeFeature(token$1) {
|
|
1190
|
+
return {
|
|
1191
|
+
kind: "translate-time-feature",
|
|
1192
|
+
name: token$1.text,
|
|
1193
|
+
span: token$1.span
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
function makeParenthesizedExpression(expression$1) {
|
|
1197
|
+
return {
|
|
1198
|
+
kind: "parenthesized-expression",
|
|
1199
|
+
expression: expression$1
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
function makeUnaryOperator(token$1) {
|
|
1203
|
+
return {
|
|
1204
|
+
value: token$1.text,
|
|
1205
|
+
span: token$1.span
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
function makeBinaryOperator(token$1) {
|
|
1209
|
+
return {
|
|
1210
|
+
value: token$1.text,
|
|
1211
|
+
span: token$1.span
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
function makeUnaryExpression([operator, expression$1]) {
|
|
1215
|
+
return {
|
|
1216
|
+
kind: "unary-expression",
|
|
1217
|
+
operator,
|
|
1218
|
+
expression: expression$1
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
/** A list of left-to-right associative binary expressions */
|
|
1222
|
+
function makeRepeatingBinaryExpression([start, repeating]) {
|
|
1223
|
+
let result = start;
|
|
1224
|
+
for (const [op, left] of repeating) result = makeBinaryExpression([
|
|
1225
|
+
result,
|
|
1226
|
+
op,
|
|
1227
|
+
left
|
|
1228
|
+
]);
|
|
1229
|
+
return result;
|
|
1230
|
+
}
|
|
1231
|
+
function makeBinaryExpression([left, operator, right]) {
|
|
1232
|
+
return {
|
|
1233
|
+
kind: "binary-expression",
|
|
1234
|
+
operator,
|
|
1235
|
+
left,
|
|
1236
|
+
right
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
if (tracing) {
|
|
1240
|
+
const names = {
|
|
1241
|
+
name,
|
|
1242
|
+
diagnostic_rule_name,
|
|
1243
|
+
diagnostic_control,
|
|
1244
|
+
name_list,
|
|
1245
|
+
special_attribute,
|
|
1246
|
+
if_attribute,
|
|
1247
|
+
normal_attribute,
|
|
1248
|
+
attribute_argument_list,
|
|
1249
|
+
attribute_no_if,
|
|
1250
|
+
attribute_incl_if,
|
|
1251
|
+
opt_attributes,
|
|
1252
|
+
opt_attributes_no_if,
|
|
1253
|
+
globalTypeNameDecl,
|
|
1254
|
+
fnNameDecl,
|
|
1255
|
+
optionally_typed_ident,
|
|
1256
|
+
req_optionally_typed_ident,
|
|
1257
|
+
global_ident,
|
|
1258
|
+
struct_member,
|
|
1259
|
+
struct_decl,
|
|
1260
|
+
fn_call,
|
|
1261
|
+
fnParam,
|
|
1262
|
+
fnParamList,
|
|
1263
|
+
local_variable_decl,
|
|
1264
|
+
global_variable_decl,
|
|
1265
|
+
attribute_if_primary_expression,
|
|
1266
|
+
attribute_if_unary_expression,
|
|
1267
|
+
attribute_if_expression,
|
|
1268
|
+
unscoped_compound_statement,
|
|
1269
|
+
compound_statement,
|
|
1270
|
+
for_init,
|
|
1271
|
+
for_update,
|
|
1272
|
+
for_statement,
|
|
1273
|
+
if_statement,
|
|
1274
|
+
loop_statement,
|
|
1275
|
+
case_selector,
|
|
1276
|
+
switch_clause,
|
|
1277
|
+
switch_body,
|
|
1278
|
+
switch_statement,
|
|
1279
|
+
while_statement,
|
|
1280
|
+
regular_statement,
|
|
1281
|
+
conditional_statement,
|
|
1282
|
+
statement,
|
|
1283
|
+
lhs_expression,
|
|
1284
|
+
variable_or_value_statement,
|
|
1285
|
+
variable_updating_statement,
|
|
1286
|
+
fn_decl,
|
|
1287
|
+
global_value_decl,
|
|
1288
|
+
global_alias,
|
|
1289
|
+
const_assert,
|
|
1290
|
+
global_directive,
|
|
1291
|
+
global_decl,
|
|
1292
|
+
weslRoot,
|
|
1293
|
+
qualified_ident,
|
|
1294
|
+
word
|
|
1295
|
+
};
|
|
1296
|
+
Object.entries(names).forEach(([name$1, parser]) => {
|
|
1297
|
+
parser.setTraceName(name$1);
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
//#endregion
|
|
1302
|
+
//#region ../wesl/src/ParseWESL.ts
|
|
1303
|
+
/**
|
|
1304
|
+
* An error when parsing WESL fails. Designed to be human-readable.
|
|
1305
|
+
*/
|
|
1306
|
+
var WeslParseError = class extends Error {
|
|
1307
|
+
span;
|
|
1308
|
+
src;
|
|
1309
|
+
constructor(opts) {
|
|
1310
|
+
const source = opts.src.src;
|
|
1311
|
+
const [lineNum, linePos] = offsetToLineNumber(opts.cause.span[0], source);
|
|
1312
|
+
let message = `${opts.src.debugFilePath}:${lineNum}:${linePos}`;
|
|
1313
|
+
message += ` error: ${opts.cause.message}\n`;
|
|
1314
|
+
message += errorHighlight(source, opts.cause.span).join("\n");
|
|
1315
|
+
super(message, { cause: opts.cause });
|
|
1316
|
+
this.span = opts.cause.span;
|
|
1317
|
+
this.src = opts.src;
|
|
1318
|
+
}
|
|
1319
|
+
};
|
|
1320
|
+
/** Parse a WESL file. Throws on error. */
|
|
1321
|
+
function parseSrcModule(srcModule) {
|
|
1322
|
+
const stream = new WeslStream(srcModule.src);
|
|
1323
|
+
const appState = blankWeslParseState(srcModule);
|
|
1324
|
+
const init = {
|
|
1325
|
+
stream,
|
|
1326
|
+
appState
|
|
1327
|
+
};
|
|
1328
|
+
try {
|
|
1329
|
+
const parseResult = weslRoot.parse(init);
|
|
1330
|
+
if (parseResult === null) throw new Error("parseWESL failed");
|
|
1331
|
+
} catch (e) {
|
|
1332
|
+
if (e instanceof ParseError) {
|
|
1333
|
+
const [lineNumber, lineColumn] = offsetToLineNumber(e.span[0], srcModule.src);
|
|
1334
|
+
const error = new WeslParseError({
|
|
1335
|
+
cause: e,
|
|
1336
|
+
src: srcModule
|
|
1337
|
+
});
|
|
1338
|
+
throwClickableError({
|
|
1339
|
+
url: srcModule.debugFilePath,
|
|
1340
|
+
text: srcModule.src,
|
|
1341
|
+
error,
|
|
1342
|
+
lineNumber,
|
|
1343
|
+
lineColumn,
|
|
1344
|
+
length: e.span[1] - e.span[0]
|
|
1345
|
+
});
|
|
1346
|
+
} else throw e;
|
|
1347
|
+
}
|
|
1348
|
+
return appState.stable;
|
|
1349
|
+
}
|
|
1350
|
+
function blankWeslParseState(srcModule) {
|
|
1351
|
+
const rootScope = emptyScope(null);
|
|
1352
|
+
const moduleElem = null;
|
|
1353
|
+
return {
|
|
1354
|
+
context: {
|
|
1355
|
+
scope: rootScope,
|
|
1356
|
+
openElems: []
|
|
1357
|
+
},
|
|
1358
|
+
stable: {
|
|
1359
|
+
srcModule,
|
|
1360
|
+
imports: [],
|
|
1361
|
+
rootScope,
|
|
1362
|
+
moduleElem
|
|
1363
|
+
}
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
//#endregion
|
|
1368
|
+
//#region ../wesl/src/PathUtil.ts
|
|
1369
|
+
/** simplistic path manipulation utilities */
|
|
1370
|
+
/** return path with ./ and foo/.. elements removed */
|
|
1371
|
+
function normalize(path$1) {
|
|
1372
|
+
const segments = path$1.split("/");
|
|
1373
|
+
const noDots = segments.filter((s) => s !== ".");
|
|
1374
|
+
const noDbl = [];
|
|
1375
|
+
noDots.forEach((s) => {
|
|
1376
|
+
if (s !== "") if (s === ".." && noDbl.length && noDbl[noDbl.length - 1] !== "..") noDbl.pop();
|
|
1377
|
+
else noDbl.push(s);
|
|
1378
|
+
});
|
|
1379
|
+
return noDbl.join("/");
|
|
1380
|
+
}
|
|
1381
|
+
/** return path w/o a suffix.
|
|
1382
|
+
* e.g. /foo/bar.wgsl => /foo/bar */
|
|
1383
|
+
function noSuffix(path$1) {
|
|
1384
|
+
const lastSlash = path$1.lastIndexOf("/");
|
|
1385
|
+
const lastStart = lastSlash === -1 ? 0 : lastSlash + 1;
|
|
1386
|
+
const suffix = path$1.indexOf(".", lastStart);
|
|
1387
|
+
const suffixStart = suffix === -1 ? path$1.length : suffix;
|
|
1388
|
+
return path$1.slice(0, suffixStart);
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
//#endregion
|
|
1392
|
+
//#region ../wesl/src/ParsedRegistry.ts
|
|
1393
|
+
function parsedRegistry() {
|
|
1394
|
+
resetScopeIds();
|
|
1395
|
+
return { modules: {} };
|
|
1396
|
+
}
|
|
1397
|
+
/**
|
|
1398
|
+
* @param srcFiles map of source strings by file path
|
|
1399
|
+
* key is '/' separated relative path (relative to srcRoot, not absolute file path )
|
|
1400
|
+
* value is wesl source string
|
|
1401
|
+
* @param registry add parsed modules to this registry
|
|
1402
|
+
* @param packageName name of package
|
|
1403
|
+
*/
|
|
1404
|
+
function parseIntoRegistry(srcFiles, registry, packageName = "package", debugWeslRoot) {
|
|
1405
|
+
let weslRoot$1 = debugWeslRoot;
|
|
1406
|
+
if (weslRoot$1 === void 0) weslRoot$1 = "";
|
|
1407
|
+
else if (!weslRoot$1.endsWith("/")) weslRoot$1 += "/";
|
|
1408
|
+
const srcModules = Object.entries(srcFiles).map(([filePath, src]) => {
|
|
1409
|
+
const modulePath = fileToModulePath(filePath, packageName);
|
|
1410
|
+
return {
|
|
1411
|
+
modulePath,
|
|
1412
|
+
debugFilePath: weslRoot$1 + filePath,
|
|
1413
|
+
src
|
|
1414
|
+
};
|
|
1415
|
+
});
|
|
1416
|
+
srcModules.forEach((mod) => {
|
|
1417
|
+
const parsed = parseSrcModule(mod);
|
|
1418
|
+
if (registry.modules[mod.modulePath]) throw new Error(`duplicate module path: '${mod.modulePath}'`);
|
|
1419
|
+
registry.modules[mod.modulePath] = parsed;
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
const libRegex = /^lib\.w[eg]sl$/i;
|
|
1423
|
+
/** convert a file path (./foo/bar.wesl)
|
|
1424
|
+
* to a module path (package::foo::bar) */
|
|
1425
|
+
function fileToModulePath(filePath, packageName) {
|
|
1426
|
+
if (filePath.includes("::")) return filePath;
|
|
1427
|
+
if (packageName !== "package" && libRegex.test(filePath)) return packageName;
|
|
1428
|
+
const strippedPath = noSuffix(normalize(filePath));
|
|
1429
|
+
const moduleSuffix = strippedPath.replaceAll("/", "::");
|
|
1430
|
+
const modulePath = packageName + "::" + moduleSuffix;
|
|
1431
|
+
return modulePath;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
//#endregion
|
|
1435
|
+
//#region src/cli.ts
|
|
1436
|
+
async function cli(rawArgs$1) {
|
|
1437
|
+
enableTracing();
|
|
1438
|
+
const argv = await parseArgs(rawArgs$1);
|
|
1439
|
+
await linkNormally(argv);
|
|
1440
|
+
}
|
|
1441
|
+
async function parseArgs(args) {
|
|
1442
|
+
const toolDir = path.join(import.meta.url, "..");
|
|
1443
|
+
const appVersion = await versionFromPackageJson(toolDir);
|
|
1444
|
+
return yargs(args).version(appVersion).option("src", {
|
|
1445
|
+
type: "string",
|
|
1446
|
+
default: "./shaders/*.w[eg]sl",
|
|
1447
|
+
describe: "WGSL/WESL files to bundle in the package (glob syntax)"
|
|
1448
|
+
}).option("rootModule", {
|
|
1449
|
+
type: "string",
|
|
1450
|
+
default: "main",
|
|
1451
|
+
describe: "start linking from this module name"
|
|
1452
|
+
}).option("define", {
|
|
1453
|
+
type: "array",
|
|
1454
|
+
describe: "definitions for preprocessor and linking"
|
|
1455
|
+
}).option("baseDir", {
|
|
1456
|
+
requiresArg: true,
|
|
1457
|
+
type: "string",
|
|
1458
|
+
default: "./shaders",
|
|
1459
|
+
describe: "root directory for shaders"
|
|
1460
|
+
}).option("projectDir", {
|
|
1461
|
+
requiresArg: true,
|
|
1462
|
+
type: "string",
|
|
1463
|
+
default: ".",
|
|
1464
|
+
describe: "directory containing package.json"
|
|
1465
|
+
}).option("details", {
|
|
1466
|
+
type: "boolean",
|
|
1467
|
+
default: false,
|
|
1468
|
+
hidden: true,
|
|
1469
|
+
describe: "show details about parsed files"
|
|
1470
|
+
}).option("emit", {
|
|
1471
|
+
type: "boolean",
|
|
1472
|
+
default: true,
|
|
1473
|
+
hidden: true,
|
|
1474
|
+
describe: "emit linked result"
|
|
1475
|
+
}).help().parse();
|
|
1476
|
+
}
|
|
1477
|
+
async function linkNormally(argv) {
|
|
1478
|
+
const weslRoot$1 = argv.baseDir || process.cwd();
|
|
1479
|
+
const weslSrc = await loadModules(argv.projectDir, weslRoot$1, argv.src);
|
|
1480
|
+
if (argv.emit) {
|
|
1481
|
+
const linked = await link({
|
|
1482
|
+
weslSrc,
|
|
1483
|
+
rootModuleName: argv.rootModule
|
|
1484
|
+
});
|
|
1485
|
+
log(linked.dest);
|
|
1486
|
+
}
|
|
1487
|
+
if (argv.details) {
|
|
1488
|
+
const registry = parsedRegistry();
|
|
1489
|
+
try {
|
|
1490
|
+
parseIntoRegistry(weslSrc, registry, "package");
|
|
1491
|
+
} catch (e) {
|
|
1492
|
+
console.error(e);
|
|
1493
|
+
}
|
|
1494
|
+
Object.entries(registry.modules).forEach(([modulePath, ast]) => {
|
|
1495
|
+
log(`---\n${modulePath}`);
|
|
1496
|
+
log(`\n->ast`);
|
|
1497
|
+
log(astToString(ast.moduleElem));
|
|
1498
|
+
log(`\n->scope`);
|
|
1499
|
+
log(scopeToString(ast.rootScope));
|
|
1500
|
+
log();
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
//#endregion
|
|
1506
|
+
//#region src/main.ts
|
|
1507
|
+
const rawArgs = hideBin(process.argv);
|
|
1508
|
+
cli(rawArgs);
|
|
1509
|
+
|
|
1510
|
+
//#endregion
|