wesl 0.6.0-pre2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist/index.cjs +2617 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.js +2617 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/linker/packages/linker/src/AbstractElems.d.ts +104 -0
  6. package/dist/linker/packages/linker/src/BindIdents.d.ts +16 -0
  7. package/dist/linker/packages/linker/src/CommentsGrammar.d.ts +6 -0
  8. package/dist/linker/packages/linker/src/FlattenTreeImport.d.ts +11 -0
  9. package/dist/linker/packages/linker/src/ImportGrammar.d.ts +13 -0
  10. package/dist/linker/packages/linker/src/ImportTree.d.ts +17 -0
  11. package/dist/linker/packages/linker/src/Linker.d.ts +26 -0
  12. package/dist/linker/packages/linker/src/LowerAndEmit.d.ts +25 -0
  13. package/dist/linker/packages/linker/src/ParseWESL.d.ts +36 -0
  14. package/dist/linker/packages/linker/src/ParsedRegistry.d.ts +26 -0
  15. package/dist/linker/packages/linker/src/PathUtil.d.ts +9 -0
  16. package/dist/linker/packages/linker/src/Scope.d.ts +55 -0
  17. package/dist/linker/packages/linker/src/Slicer.d.ts +26 -0
  18. package/dist/linker/packages/linker/src/StandardTypes.d.ts +6 -0
  19. package/dist/linker/packages/linker/src/Util.d.ts +26 -0
  20. package/dist/linker/packages/linker/src/WESLCollect.d.ts +29 -0
  21. package/dist/linker/packages/linker/src/WESLGrammar.d.ts +23 -0
  22. package/dist/linker/packages/linker/src/WESLTokens.d.ts +42 -0
  23. package/dist/linker/packages/linker/src/WgslBundle.d.ts +13 -0
  24. package/dist/linker/packages/linker/src/debug/ASTtoString.d.ts +3 -0
  25. package/dist/linker/packages/linker/src/debug/ImportToString.d.ts +2 -0
  26. package/dist/linker/packages/linker/src/debug/LineWrapper.d.ts +21 -0
  27. package/dist/linker/packages/linker/src/debug/ScopeToString.d.ts +4 -0
  28. package/dist/linker/packages/linker/src/index.d.ts +7 -0
  29. package/dist/linker/packages/linker/src/test/ErrorLogging.test.d.ts +1 -0
  30. package/dist/linker/packages/linker/src/test/Expression.test.d.ts +1 -0
  31. package/dist/linker/packages/linker/src/test/FlattenTreeImport.test.d.ts +1 -0
  32. package/dist/linker/packages/linker/src/test/ImportCases.test.d.ts +1 -0
  33. package/dist/linker/packages/linker/src/test/ImportSyntaxCases.test.d.ts +1 -0
  34. package/dist/linker/packages/linker/src/test/LinkGlob.test.d.ts +1 -0
  35. package/dist/linker/packages/linker/src/test/LinkPackage.test.d.ts +1 -0
  36. package/dist/linker/packages/linker/src/test/Linker.test.d.ts +1 -0
  37. package/dist/linker/packages/linker/src/test/MatchWgslD.test.d.ts +1 -0
  38. package/dist/linker/packages/linker/src/test/ParseComments.test.d.ts +1 -0
  39. package/dist/linker/packages/linker/src/test/ParseWESL.test.d.ts +1 -0
  40. package/dist/linker/packages/linker/src/test/PathUtil.test.d.ts +1 -0
  41. package/dist/linker/packages/linker/src/test/PrettyGrammar.test.d.ts +1 -0
  42. package/dist/linker/packages/linker/src/test/ScopeWESL.test.d.ts +1 -0
  43. package/dist/linker/packages/linker/src/test/Slicer.test.d.ts +1 -0
  44. package/dist/linker/packages/linker/src/test/TestSetup.d.ts +1 -0
  45. package/dist/linker/packages/linker/src/test/TestUtil.d.ts +15 -0
  46. package/dist/linker/packages/linker/src/test/Util.test.d.ts +1 -0
  47. package/dist/linker/packages/linker/src/test/WgslTests.d.ts +0 -0
  48. package/dist/linker/packages/linker/src/test/shared/StringUtil.d.ts +8 -0
  49. package/dist/linker/packages/linker/src/test/shared/test/StringUtil.test.d.ts +1 -0
  50. package/dist/minified.cjs +2 -0
  51. package/dist/minified.cjs.map +1 -0
  52. package/dist/minified.js +2617 -0
  53. package/dist/minified.js.map +1 -0
  54. package/dist/wesl-testsuite/src/test-cases/BulkTests.d.ts +4 -0
  55. package/dist/wesl-testsuite/src/test-cases/ImportCases.d.ts +3 -0
  56. package/dist/wesl-testsuite/src/test-cases/ImportSyntaxCases.d.ts +3 -0
  57. package/package.json +45 -0
  58. package/src/AbstractElems.ts +148 -0
  59. package/src/BindIdents.ts +277 -0
  60. package/src/CommentsGrammar.ts +44 -0
  61. package/src/FlattenTreeImport.ts +59 -0
  62. package/src/ImportGrammar.ts +142 -0
  63. package/src/ImportTree.ts +19 -0
  64. package/src/Linker.ts +151 -0
  65. package/src/LowerAndEmit.ts +143 -0
  66. package/src/ParseWESL.ts +106 -0
  67. package/src/ParsedRegistry.ts +97 -0
  68. package/src/PathUtil.ts +52 -0
  69. package/src/Scope.ts +100 -0
  70. package/src/Slicer.ts +127 -0
  71. package/src/StandardTypes.ts +66 -0
  72. package/src/Util.ts +112 -0
  73. package/src/WESLCollect.ts +336 -0
  74. package/src/WESLGrammar.ts +538 -0
  75. package/src/WESLTokens.ts +97 -0
  76. package/src/WgslBundle.ts +16 -0
  77. package/src/debug/ASTtoString.ts +149 -0
  78. package/src/debug/ImportToString.ts +21 -0
  79. package/src/debug/LineWrapper.ts +65 -0
  80. package/src/debug/ScopeToString.ts +51 -0
  81. package/src/index.ts +7 -0
  82. package/src/test/ErrorLogging.test.ts +14 -0
  83. package/src/test/Expression.test.ts +22 -0
  84. package/src/test/FlattenTreeImport.test.ts +56 -0
  85. package/src/test/ImportCases.test.ts +440 -0
  86. package/src/test/ImportSyntaxCases.test.ts +22 -0
  87. package/src/test/LinkGlob.test.ts +25 -0
  88. package/src/test/LinkPackage.test.ts +26 -0
  89. package/src/test/Linker.test.ts +120 -0
  90. package/src/test/MatchWgslD.test.ts +16 -0
  91. package/src/test/ParseComments.test.ts +74 -0
  92. package/src/test/ParseWESL.test.ts +902 -0
  93. package/src/test/PathUtil.test.ts +34 -0
  94. package/src/test/PrettyGrammar.test.ts +21 -0
  95. package/src/test/ScopeWESL.test.ts +272 -0
  96. package/src/test/Slicer.test.ts +103 -0
  97. package/src/test/TestSetup.ts +4 -0
  98. package/src/test/TestUtil.ts +52 -0
  99. package/src/test/Util.test.ts +22 -0
  100. package/src/test/WgslTests.ts +0 -0
  101. package/src/test/__snapshots__/ParseDirectives.test.ts.snap +25 -0
  102. package/src/test/__snapshots__/ParseWESL.test.ts.snap +119 -0
  103. package/src/test/__snapshots__/ParseWESL2.test.ts.snap +67 -0
  104. package/src/test/__snapshots__/RustDirective.test.ts.snap +359 -0
  105. package/src/test/shared/StringUtil.ts +59 -0
  106. package/src/test/shared/test/StringUtil.test.ts +32 -0
  107. package/src/test/wgsl_1/main.wgsl +3 -0
  108. package/src/test/wgsl_1/util.wgsl +1 -0
  109. package/src/test/wgsl_2/main2.wgsl +3 -0
  110. package/src/test/wgsl_2/util2.wgsl +1 -0
package/src/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
+ }
@@ -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
+ }
@@ -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
+ }