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/Linker.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { SrcMap, SrcMapBuilder, tracing } from "mini-parse";
|
|
2
|
+
import { bindIdents } from "./BindIdents.ts";
|
|
3
|
+
import { lowerAndEmit } from "./LowerAndEmit.ts";
|
|
4
|
+
import {
|
|
5
|
+
parsedRegistry,
|
|
6
|
+
ParsedRegistry,
|
|
7
|
+
parseIntoRegistry,
|
|
8
|
+
parseLibsIntoRegistry,
|
|
9
|
+
parseWeslSrc,
|
|
10
|
+
selectModule,
|
|
11
|
+
} from "./ParsedRegistry.ts";
|
|
12
|
+
import { Conditions } from "./Scope.ts";
|
|
13
|
+
import { WgslBundle } from "./WgslBundle.ts";
|
|
14
|
+
|
|
15
|
+
/* --- Overview: Plan for Linking WESL --- */
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
This is a bit of a rework/reshuffling from the 'legacy' version described in Internals.md.
|
|
19
|
+
|
|
20
|
+
It expects the parser to identify three types of idents:
|
|
21
|
+
global declarations, local declarations, references
|
|
22
|
+
(the legacy version distingished between type and variable idents, and more)
|
|
23
|
+
|
|
24
|
+
It tracks scopes, and keeps them independently from the AST. It uses the scopes trees for
|
|
25
|
+
binding references to declarations. (The legacy linker used combination
|
|
26
|
+
of naming tricks and AST traversal to bind references to declarations.)
|
|
27
|
+
|
|
28
|
+
Binding idents is more generic, which should simplify the code
|
|
29
|
+
and extend more easily to for importing elements beyond structs and functions.
|
|
30
|
+
|
|
31
|
+
It asks less of the grammar, a complete WGSL grammar is easier to maintain
|
|
32
|
+
if it can match the WGSL spec.
|
|
33
|
+
|
|
34
|
+
It replaces an AST pass with scope table pass, which should be a little faster.
|
|
35
|
+
|
|
36
|
+
It's much more friendly to future parallel execution and incremental rebuilding,
|
|
37
|
+
which should make things a lot faster when we go there.
|
|
38
|
+
|
|
39
|
+
The architecture allows conditional compilation from the AST rather than from the src text.
|
|
40
|
+
|
|
41
|
+
The AST is now immutable, mutation is confined to the Idents and Scopes.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Link a set of WESL source modules (typically the text from .wesl files) into a single WGSL string.
|
|
46
|
+
* Linking starts with a specified 'root' source module, and recursively incorporates code
|
|
47
|
+
* referenced from other modules (in local files or libraries).
|
|
48
|
+
*
|
|
49
|
+
* Unreferenced (dead) code outside the root module is not included in the output WGSL.
|
|
50
|
+
* Additionally the caller can specify conditions for to control conditional compilation.
|
|
51
|
+
* Only code that is valid with the current conditions is included in the output.
|
|
52
|
+
*
|
|
53
|
+
* @param weslSrc map of wesl source strings (aka modules) by scoped path
|
|
54
|
+
* key is module path or file path
|
|
55
|
+
* `package::foo::bar`, or './foo/bar.wesl', or './foo/bar'
|
|
56
|
+
* value is wesl src
|
|
57
|
+
* (inludes both library and local WESL modules)
|
|
58
|
+
* @param rootModuleName name or module path of the root module
|
|
59
|
+
* @param conditions runtime conditions for conditional compilation
|
|
60
|
+
*/
|
|
61
|
+
export function linkWesl(
|
|
62
|
+
weslSrc: Record<string, string>,
|
|
63
|
+
rootModuleName: string = "main",
|
|
64
|
+
conditions: Conditions = {},
|
|
65
|
+
): SrcMap {
|
|
66
|
+
/* --- Step #1 Parsing WESL --- */
|
|
67
|
+
// parse all source modules in both app and libraries,
|
|
68
|
+
// producing Scope tree and AST elements for each module
|
|
69
|
+
const parsed: ParsedRegistry = parseWeslSrc(weslSrc);
|
|
70
|
+
|
|
71
|
+
return linkRegistry(parsed, rootModuleName, conditions);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// TODO DRY entry points
|
|
75
|
+
export function linkWeslFiles(
|
|
76
|
+
weslSrc: Record<string, string>,
|
|
77
|
+
rootModuleName: string = "main",
|
|
78
|
+
conditions: Conditions = {},
|
|
79
|
+
/** record of file names and wgsl text for modules */
|
|
80
|
+
libs: WgslBundle[] = [],
|
|
81
|
+
maxParseCount?: number,
|
|
82
|
+
): SrcMap {
|
|
83
|
+
const registry = parsedRegistry();
|
|
84
|
+
parseIntoRegistry(weslSrc, registry, "package", maxParseCount);
|
|
85
|
+
parseLibsIntoRegistry(libs, registry);
|
|
86
|
+
return linkRegistry(registry, rootModuleName, conditions);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function linkRegistry(
|
|
90
|
+
parsed: ParsedRegistry,
|
|
91
|
+
rootModuleName: string = "main",
|
|
92
|
+
conditions: Conditions = {},
|
|
93
|
+
): SrcMap {
|
|
94
|
+
// get a reference to the root module
|
|
95
|
+
const found = selectModule(parsed, rootModuleName);
|
|
96
|
+
if (!found) {
|
|
97
|
+
if (tracing) {
|
|
98
|
+
console.log(`parsed modules: ${Object.keys(parsed.modules)}`);
|
|
99
|
+
console.log(`root module not found: ${rootModuleName}`);
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`Root module not found: ${rootModuleName}`);
|
|
102
|
+
}
|
|
103
|
+
const { moduleElem: rootModule } = found;
|
|
104
|
+
|
|
105
|
+
/* --- Step #2 Binding Idents --- */
|
|
106
|
+
// link active Ident references to declarations, and uniquify global declarations
|
|
107
|
+
const newDecls = bindIdents(found, parsed, conditions);
|
|
108
|
+
|
|
109
|
+
/* --- Step #3 Writing WGSL --- */
|
|
110
|
+
// traverse the AST and emit WGSL (doesn't need scopes)
|
|
111
|
+
const srcBuilder = new SrcMapBuilder();
|
|
112
|
+
lowerAndEmit(srcBuilder, [rootModule], conditions, false); // emit the entire root module
|
|
113
|
+
lowerAndEmit(srcBuilder, newDecls, conditions); // emit referenced declarations from other modules
|
|
114
|
+
return srcBuilder.build();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* ---- Commentary on present and future features ---- */
|
|
118
|
+
/*
|
|
119
|
+
|
|
120
|
+
TODO
|
|
121
|
+
- distinguish between global and local declaration idents (only global ones need be uniquified)
|
|
122
|
+
|
|
123
|
+
Conditions
|
|
124
|
+
- conditions are attached to the AST elements where they are defined
|
|
125
|
+
- only conditionally valid elements are emitted
|
|
126
|
+
- consolidated conditions are attached to Idents
|
|
127
|
+
- only conditionally valid ref Idents are bound, and only to conditionaly valid declarations
|
|
128
|
+
- a condition stack (akin to the scope stack) is maintained while parsing to attach consolidated conditions to Idents
|
|
129
|
+
- re-linking with new conditions, conservatively
|
|
130
|
+
- clear all mutated Ident fields (refersTo and mangled links)
|
|
131
|
+
- re-bind Idents, re-emit
|
|
132
|
+
|
|
133
|
+
Generics & specialization
|
|
134
|
+
- attach generic parameters to ref and decl Idents, effectively creating a new Ident for each specialization
|
|
135
|
+
- generate specialized elements at emit time, by checking the generic parameters of the decl ident
|
|
136
|
+
|
|
137
|
+
Incrementally rebuilding
|
|
138
|
+
- unchanged files don't need to be reparsed, only reparse dirty files.
|
|
139
|
+
- support reflection only mode? no need to bind idents or emit for e.g. vite/IDE plugin generating reflection types
|
|
140
|
+
|
|
141
|
+
Parallel Processing (coarse grained via webworkers)
|
|
142
|
+
- Parsing each module can be done in parallel
|
|
143
|
+
- binding could be done partially in parallel? (esbuild doesn't parallelize here though)
|
|
144
|
+
- finding the declaration for each local ident could be done in parallel by module
|
|
145
|
+
- matching
|
|
146
|
+
- Emitting could be easily modified to be done in partially in parallel
|
|
147
|
+
- traversing the AST to list the top level elements to emit could be done serially
|
|
148
|
+
- the text for each top level element could be emitted in parallel (presumably the bulk of the work)
|
|
149
|
+
- the merged text can be assembled serially
|
|
150
|
+
|
|
151
|
+
*/
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { SrcMapBuilder, tracing } from "mini-parse";
|
|
2
|
+
import {
|
|
3
|
+
AbstractElem,
|
|
4
|
+
DeclIdentElem,
|
|
5
|
+
RefIdentElem,
|
|
6
|
+
NameElem,
|
|
7
|
+
TextElem,
|
|
8
|
+
} from "./AbstractElems.ts";
|
|
9
|
+
import { isGlobal } from "./BindIdents.ts";
|
|
10
|
+
import { Conditions, DeclIdent, Ident, RefIdent } from "./Scope.ts";
|
|
11
|
+
import { identToString } from "./debug/ScopeToString.ts";
|
|
12
|
+
|
|
13
|
+
/** passed to the emitters */
|
|
14
|
+
interface EmitContext {
|
|
15
|
+
srcBuilder: SrcMapBuilder; // constructing the linked output
|
|
16
|
+
conditions: Conditions; // settings for conditional compilation
|
|
17
|
+
extracting: boolean; // are we extracting or copying the root module
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** traverse the AST, starting from root elements, emitting wgsl for each */
|
|
21
|
+
export function lowerAndEmit(
|
|
22
|
+
srcBuilder: SrcMapBuilder,
|
|
23
|
+
rootElems: AbstractElem[],
|
|
24
|
+
conditions: Conditions,
|
|
25
|
+
extracting = true,
|
|
26
|
+
): void {
|
|
27
|
+
const emitContext: EmitContext = { conditions, srcBuilder, extracting };
|
|
28
|
+
lowerAndEmitRecursive(rootElems, emitContext);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function lowerAndEmitRecursive(
|
|
32
|
+
elems: AbstractElem[],
|
|
33
|
+
emitContext: EmitContext,
|
|
34
|
+
): void {
|
|
35
|
+
const validElems = elems.filter(e =>
|
|
36
|
+
conditionsValid(e, emitContext.conditions),
|
|
37
|
+
);
|
|
38
|
+
validElems.forEach(e => lowerAndEmitElem(e, emitContext));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function lowerAndEmitElem(e: AbstractElem, ctx: EmitContext): void {
|
|
42
|
+
switch (e.kind) {
|
|
43
|
+
case "import":
|
|
44
|
+
return; // drop imports statements from emitted text
|
|
45
|
+
case "fn":
|
|
46
|
+
case "struct":
|
|
47
|
+
case "override":
|
|
48
|
+
case "const":
|
|
49
|
+
case "assert":
|
|
50
|
+
case "alias":
|
|
51
|
+
case "gvar":
|
|
52
|
+
if (ctx.extracting) {
|
|
53
|
+
ctx.srcBuilder.addNl();
|
|
54
|
+
ctx.srcBuilder.addNl();
|
|
55
|
+
}
|
|
56
|
+
return emitContents(e, ctx);
|
|
57
|
+
case "text":
|
|
58
|
+
return emitText(e, ctx);
|
|
59
|
+
case "ref":
|
|
60
|
+
case "decl":
|
|
61
|
+
return emitIdent(e, ctx);
|
|
62
|
+
case "param":
|
|
63
|
+
case "var":
|
|
64
|
+
case "module":
|
|
65
|
+
case "member":
|
|
66
|
+
return emitContents(e, ctx);
|
|
67
|
+
case "name":
|
|
68
|
+
return emitName(e, ctx);
|
|
69
|
+
default:
|
|
70
|
+
const kind = (e as any).kind;
|
|
71
|
+
console.log("NYI for emit, elem kind:", kind);
|
|
72
|
+
throw new Error(`NYI emit elem kind: ${kind}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function emitText(e: TextElem, ctx: EmitContext): void {
|
|
77
|
+
ctx.srcBuilder.addCopy(e.srcModule.src, e.start, e.end);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function emitName(e: NameElem, ctx: EmitContext): void {
|
|
81
|
+
ctx.srcBuilder.add(e.name, e.srcModule.src, e.start, e.end);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function emitContents(
|
|
85
|
+
elem: AbstractElem & { contents: AbstractElem[] },
|
|
86
|
+
ctx: EmitContext,
|
|
87
|
+
): void {
|
|
88
|
+
elem.contents.forEach(e => lowerAndEmitElem(e, ctx));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function emitIdent(
|
|
92
|
+
e: RefIdentElem | DeclIdentElem,
|
|
93
|
+
ctx: EmitContext,
|
|
94
|
+
): void {
|
|
95
|
+
if ((e.ident as RefIdent).std) {
|
|
96
|
+
ctx.srcBuilder.add(e.ident.originalName, e.srcModule.src, e.start, e.end);
|
|
97
|
+
} else {
|
|
98
|
+
const declIdent = findDecl(e.ident);
|
|
99
|
+
const mangledName = displayName(declIdent);
|
|
100
|
+
ctx.srcBuilder.add(mangledName!, e.srcModule.src, e.start, e.end);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function displayName(declIdent: DeclIdent): string {
|
|
105
|
+
if (declIdent.declElem && isGlobal(declIdent.declElem)) {
|
|
106
|
+
// mangled name was set in binding step
|
|
107
|
+
const mangledName = declIdent.mangledName;
|
|
108
|
+
if (tracing && !mangledName) {
|
|
109
|
+
console.log(
|
|
110
|
+
"ERR: mangled name not found for decl ident",
|
|
111
|
+
identToString(declIdent),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return mangledName!;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return declIdent.mangledName || declIdent.originalName;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** trace through refersTo links in reference Idents until we find the declaration
|
|
121
|
+
* expects that bindIdents has filled in all refersTo: links
|
|
122
|
+
*/
|
|
123
|
+
export function findDecl(ident: Ident): DeclIdent {
|
|
124
|
+
let i: Ident | undefined = ident;
|
|
125
|
+
do {
|
|
126
|
+
if (i.kind === "decl") {
|
|
127
|
+
return i;
|
|
128
|
+
}
|
|
129
|
+
i = i.refersTo;
|
|
130
|
+
} while (i);
|
|
131
|
+
|
|
132
|
+
throw new Error(
|
|
133
|
+
`unresolved ident: ${ident.originalName} (bug in bindIdents?)`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** check if the element is visible with the current current conditional compilation settings */
|
|
138
|
+
export function conditionsValid(
|
|
139
|
+
elem: AbstractElem,
|
|
140
|
+
conditions: Conditions,
|
|
141
|
+
): boolean {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
package/src/ParseWESL.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { AppState, matchingLexer, ParserInit, SrcMap } from "mini-parse";
|
|
2
|
+
import { ModuleElem } from "./AbstractElems.ts";
|
|
3
|
+
import { ImportTree } from "./ImportTree.ts";
|
|
4
|
+
import { mainTokens } from "./WESLTokens.ts";
|
|
5
|
+
import { emptyScope, resetScopeIds, Scope, SrcModule } from "./Scope.ts";
|
|
6
|
+
import { OpenElem } from "./WESLCollect.ts";
|
|
7
|
+
import { weslRoot } from "./WESLGrammar.ts";
|
|
8
|
+
import { FlatImport, flattenTreeImport } from "./FlattenTreeImport.ts";
|
|
9
|
+
|
|
10
|
+
/** result of a parse */
|
|
11
|
+
export interface WeslAST {
|
|
12
|
+
/** source text for this module */
|
|
13
|
+
srcModule: SrcModule;
|
|
14
|
+
|
|
15
|
+
/** root module element */
|
|
16
|
+
moduleElem: ModuleElem;
|
|
17
|
+
|
|
18
|
+
/** root scope for this module */
|
|
19
|
+
rootScope: Scope;
|
|
20
|
+
|
|
21
|
+
/** imports found in this module */
|
|
22
|
+
imports: ImportTree[];
|
|
23
|
+
|
|
24
|
+
/* constructed on demand from import trees, and cached */
|
|
25
|
+
_flatImports?: FlatImport[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** stable and unstable state used during parsing */
|
|
29
|
+
export interface WeslParseState
|
|
30
|
+
extends AppState<WeslParseContext, StableState> {
|
|
31
|
+
context: WeslParseContext;
|
|
32
|
+
stable: StableState;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** stable values used or accumulated during parsing */
|
|
36
|
+
export type StableState = WeslAST;
|
|
37
|
+
|
|
38
|
+
/** unstable values used during parse collection */
|
|
39
|
+
export interface WeslParseContext {
|
|
40
|
+
scope: Scope; // current scope (points somewhere in rootScope)
|
|
41
|
+
openElems: OpenElem[]; // elems that are collecting their contents
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function parseSrcModule(
|
|
45
|
+
srcModule: SrcModule,
|
|
46
|
+
srcMap?: SrcMap,
|
|
47
|
+
maxParseCount: number | undefined = undefined,
|
|
48
|
+
): WeslAST {
|
|
49
|
+
// TODO allow returning undefined for failure, or throw?
|
|
50
|
+
|
|
51
|
+
resetScopeIds();
|
|
52
|
+
const lexer = matchingLexer(srcModule.src, mainTokens);
|
|
53
|
+
|
|
54
|
+
const appState = blankWeslParseState(srcModule);
|
|
55
|
+
|
|
56
|
+
const init: ParserInit = { lexer, appState, srcMap, maxParseCount };
|
|
57
|
+
const parseResult = weslRoot.parse(init);
|
|
58
|
+
if (parseResult === null) {
|
|
59
|
+
throw new Error("parseWESL failed");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return appState.stable as WeslAST;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// for tests. TODO rename
|
|
66
|
+
export function parseWESL(
|
|
67
|
+
src: string,
|
|
68
|
+
srcMap?: SrcMap,
|
|
69
|
+
maxParseCount: number | undefined = undefined,
|
|
70
|
+
): WeslAST {
|
|
71
|
+
const srcModule: SrcModule = {
|
|
72
|
+
modulePath: "package::test",
|
|
73
|
+
filePath: "./test.wesl",
|
|
74
|
+
src,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return parseSrcModule(srcModule, srcMap, maxParseCount);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function blankWeslParseState(srcModule: SrcModule): WeslParseState {
|
|
81
|
+
const rootScope = emptyScope("module-scope");
|
|
82
|
+
const moduleElem = null as any; // we'll fill this in later
|
|
83
|
+
return {
|
|
84
|
+
context: { scope: rootScope, openElems: [] },
|
|
85
|
+
stable: { srcModule, imports: [], rootScope, moduleElem },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function syntheticWeslParseState(): WeslParseState {
|
|
90
|
+
const srcModule: SrcModule = {
|
|
91
|
+
modulePath: "package::test",
|
|
92
|
+
filePath: "./test.wesl",
|
|
93
|
+
src: "",
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return blankWeslParseState(srcModule);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** @return a flattened form of the import tree for convenience in binding idents. */
|
|
100
|
+
export function flatImports(ast: WeslAST): FlatImport[] {
|
|
101
|
+
if (ast._flatImports) return ast._flatImports;
|
|
102
|
+
|
|
103
|
+
const flat = ast.imports.flatMap(flattenTreeImport);
|
|
104
|
+
ast._flatImports = flat;
|
|
105
|
+
return flat;
|
|
106
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { WgslBundle } from "random_wgsl";
|
|
2
|
+
import { parseSrcModule, parseWESL, WeslAST } from "./ParseWESL.ts";
|
|
3
|
+
import { normalize, noSuffix } from "./PathUtil.ts";
|
|
4
|
+
import { SrcModule } from "./Scope.ts";
|
|
5
|
+
|
|
6
|
+
export interface ParsedRegistry {
|
|
7
|
+
modules: Record<string, WeslAST>; // key is module path, e.g. "rand_pkg::foo::bar"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function parsedRegistry(): ParsedRegistry {
|
|
11
|
+
return { modules: {} };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse WESL each src module (file) into AST elements and a Scope tree.
|
|
16
|
+
* @param src keys are module paths, values are wesl src strings
|
|
17
|
+
*/
|
|
18
|
+
export function parseWeslSrc(src: Record<string, string>): ParsedRegistry {
|
|
19
|
+
const parsedEntries = Object.entries(src).map(([path, src]) => {
|
|
20
|
+
const weslAST = parseWESL(src);
|
|
21
|
+
return [path, weslAST];
|
|
22
|
+
});
|
|
23
|
+
return { modules: Object.fromEntries(parsedEntries) };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Look up a module with a flexible selector.
|
|
27
|
+
* :: separated module path, package::util
|
|
28
|
+
* / separated file path ./util.wesl (or ./util)
|
|
29
|
+
* simpleName util
|
|
30
|
+
*/
|
|
31
|
+
export function selectModule(
|
|
32
|
+
parsed: ParsedRegistry,
|
|
33
|
+
selectPath: string,
|
|
34
|
+
packageName = "package",
|
|
35
|
+
): WeslAST | undefined {
|
|
36
|
+
let modulePath: string;
|
|
37
|
+
if (selectPath.includes("::")) {
|
|
38
|
+
modulePath = selectPath;
|
|
39
|
+
} else if (selectPath.includes("/")) {
|
|
40
|
+
modulePath = fileToModulePath(selectPath, packageName);
|
|
41
|
+
} else {
|
|
42
|
+
modulePath = packageName + "::" + selectPath;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return parsed.modules[modulePath];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param srcFiles map of source strings by file path
|
|
50
|
+
* key is '/' separated relative path (relative to srcRoot, not absolute file path )
|
|
51
|
+
* value is wesl source string
|
|
52
|
+
* @param registry add parsed modules to this registry
|
|
53
|
+
* @param packageName name of package
|
|
54
|
+
*/
|
|
55
|
+
export function parseIntoRegistry(
|
|
56
|
+
srcFiles: Record<string, string>,
|
|
57
|
+
registry: ParsedRegistry,
|
|
58
|
+
packageName: string = "package",
|
|
59
|
+
maxParseCount?: number,
|
|
60
|
+
): void {
|
|
61
|
+
const srcModules: SrcModule[] = Object.entries(srcFiles).map(
|
|
62
|
+
([filePath, src]) => {
|
|
63
|
+
const modulePath = fileToModulePath(filePath, packageName);
|
|
64
|
+
return { modulePath, filePath, src };
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
srcModules.forEach(mod => {
|
|
68
|
+
const parsed = parseSrcModule(mod, undefined, maxParseCount);
|
|
69
|
+
if (registry.modules[mod.modulePath]) {
|
|
70
|
+
throw new Error(`duplicate module path: '${mod.modulePath}'`);
|
|
71
|
+
}
|
|
72
|
+
registry.modules[mod.modulePath] = parsed;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function parseLibsIntoRegistry(
|
|
77
|
+
libs: WgslBundle[],
|
|
78
|
+
registry: ParsedRegistry,
|
|
79
|
+
): void {
|
|
80
|
+
libs.forEach(({ modules, name }) =>
|
|
81
|
+
parseIntoRegistry(modules, registry, name),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const libExp = /^lib\.w[eg]sl$/i;
|
|
86
|
+
|
|
87
|
+
/** convert a relative file path (./foo/bar.wesl) to a module path (package::foo::bar) */
|
|
88
|
+
function fileToModulePath(filePath: string, packageName: string): string {
|
|
89
|
+
if (packageName !== "package" && libExp.test(filePath)) {
|
|
90
|
+
// special case for lib.wesl files in external packages
|
|
91
|
+
return packageName;
|
|
92
|
+
}
|
|
93
|
+
const strippedPath = noSuffix(normalize(filePath));
|
|
94
|
+
const moduleSuffix = strippedPath.replaceAll("/", "::");
|
|
95
|
+
const modulePath = packageName + "::" + moduleSuffix;
|
|
96
|
+
return modulePath;
|
|
97
|
+
}
|
package/src/PathUtil.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** simplistic path manipulation utilities */
|
|
2
|
+
|
|
3
|
+
export function relativePath(
|
|
4
|
+
srcPath: string | undefined,
|
|
5
|
+
reqPath: string,
|
|
6
|
+
): string {
|
|
7
|
+
if (!srcPath) return reqPath;
|
|
8
|
+
const srcDir = dirname(srcPath);
|
|
9
|
+
const relative = join(srcDir, reqPath);
|
|
10
|
+
return relative;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function dirname(path: string): string {
|
|
14
|
+
const lastSlash = path.lastIndexOf("/");
|
|
15
|
+
if (lastSlash === -1) return ".";
|
|
16
|
+
return path.slice(0, lastSlash);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function join(a: string, b: string): string {
|
|
20
|
+
const joined = b.startsWith("/") ? a + b : a + "/" + b;
|
|
21
|
+
return normalize(joined);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** return path with ./ and foo/.. elements removed */
|
|
25
|
+
export function normalize(path: string): string {
|
|
26
|
+
const segments = path.split("/");
|
|
27
|
+
const noDots = segments.filter(s => s !== ".");
|
|
28
|
+
const noDbl: string[] = [];
|
|
29
|
+
|
|
30
|
+
noDots.forEach(s => {
|
|
31
|
+
if (s !== "") {
|
|
32
|
+
if (s === ".." && noDbl.length && noDbl[noDbl.length - 1] !== "..") {
|
|
33
|
+
noDbl.pop();
|
|
34
|
+
} else {
|
|
35
|
+
noDbl.push(s);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return noDbl.join("/");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** return path w/o a suffix.
|
|
44
|
+
* e.g. /foo/bar.wgsl => /foo/bar */
|
|
45
|
+
export function noSuffix(path: string): string {
|
|
46
|
+
const lastSlash = path.lastIndexOf("/");
|
|
47
|
+
const lastStart = lastSlash === -1 ? 0 : lastSlash + 1;
|
|
48
|
+
|
|
49
|
+
const suffix = path.indexOf(".", lastStart);
|
|
50
|
+
const suffixStart = suffix === -1 ? path.length : suffix;
|
|
51
|
+
return path.slice(0, suffixStart);
|
|
52
|
+
}
|
package/src/Scope.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { DeclarationElem, RefIdentElem } from "./AbstractElems.ts";
|
|
2
|
+
import { WeslAST } from "./ParseWESL.ts";
|
|
3
|
+
|
|
4
|
+
export interface SrcModule {
|
|
5
|
+
/** module path "rand_pkg::sub::foo", or "package::main" */
|
|
6
|
+
modulePath: string; // TODO drop this?
|
|
7
|
+
|
|
8
|
+
/** file path to the module for user error reporting e.g "rand_pkg:sub/foo.wesl", or "./sub/foo.wesl" */
|
|
9
|
+
filePath: string;
|
|
10
|
+
|
|
11
|
+
/** original src for module */
|
|
12
|
+
src: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** a src declaration or reference to an ident */
|
|
16
|
+
export type Ident = DeclIdent | RefIdent;
|
|
17
|
+
|
|
18
|
+
export type Conditions = Record<string, boolean>;
|
|
19
|
+
|
|
20
|
+
interface IdentBase {
|
|
21
|
+
originalName: string; // name in the source code for ident matching (may be mangled in the output)
|
|
22
|
+
conditions?: Conditions; // conditions under which this ident is valid (combined from all containing elems)
|
|
23
|
+
id?: number; // for debugging
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface RefIdent extends IdentBase {
|
|
27
|
+
kind: "ref";
|
|
28
|
+
refersTo?: Ident; // import or decl ident in scope to which this ident refers. undefined before binding
|
|
29
|
+
std?: true; // true if this is a standard wgsl identifier (like sin, or u32)
|
|
30
|
+
ast: WeslAST; // AST from module that contains this ident (to find imports during decl binding)
|
|
31
|
+
scope: Scope; // scope containing this reference (bind to decls starting from this scope)
|
|
32
|
+
refIdentElem?: RefIdentElem; // for error reporting
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface DeclIdent extends IdentBase {
|
|
36
|
+
kind: "decl";
|
|
37
|
+
mangledName?: string; // name in the output code
|
|
38
|
+
declElem?: DeclarationElem; // link to AST so that we can traverse scopes and know what elems to emit // TODO make separate GlobalDecl kind with this required
|
|
39
|
+
scope: Scope; // scope for the references within this declaration
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type ScopeKind =
|
|
43
|
+
| "module-scope" // root scope for a module (file)
|
|
44
|
+
| "body-scope"; // a scope inside the module (fn body, nested block, etc.)
|
|
45
|
+
|
|
46
|
+
/** tree of ident references, organized by lexical scope */
|
|
47
|
+
export interface Scope {
|
|
48
|
+
id?: number; // for debugging
|
|
49
|
+
idents: Ident[]; // idents found in lexical order in this scope
|
|
50
|
+
parent: Scope | null; // null for root scope in a module
|
|
51
|
+
children: Scope[];
|
|
52
|
+
kind: ScopeKind;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function resetScopeIds() {
|
|
56
|
+
// for debugging
|
|
57
|
+
scopeId = 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let scopeId = 0; // for debugging
|
|
61
|
+
|
|
62
|
+
/** make a new Scope object */
|
|
63
|
+
export function makeScope(s: Omit<Scope, "id">): Scope {
|
|
64
|
+
return { ...s, id: scopeId++ };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface RootAndScope {
|
|
68
|
+
rootScope: Scope;
|
|
69
|
+
scope: Scope;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function emptyScope(kind: ScopeKind): Scope {
|
|
73
|
+
return makeScope({ idents: [], parent: null, children: [], kind });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function emptyBodyScope(parent: Scope): Scope {
|
|
77
|
+
return makeScope({ kind: "body-scope", idents: [], parent, children: [] });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** For debugging,
|
|
81
|
+
* @return true if a scope is in the rootScope tree somewhere */
|
|
82
|
+
export function containsScope(rootScope: Scope, scope: Scope): boolean {
|
|
83
|
+
if (scope === rootScope) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
for (const child of rootScope.children) {
|
|
87
|
+
if (containsScope(child, scope)) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function exportDecl(scope: Scope, name: string): DeclIdent | undefined {
|
|
95
|
+
for (const ident of scope.idents) {
|
|
96
|
+
if (ident.originalName === name && ident.kind === "decl") {
|
|
97
|
+
return ident;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|