wesl 0.6.15 → 0.6.17

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.js CHANGED
@@ -976,6 +976,11 @@ function makeLiveDecls(parent = null) {
976
976
  parent
977
977
  };
978
978
  }
979
+ /** debug routine for logging LiveDecls */
980
+ function liveDeclsToString(liveDecls) {
981
+ const { decls, parent } = liveDecls;
982
+ return `decls: { ${Array.from(decls.entries()).map(([name$1, decl]) => `${name$1}:${identToString(decl)}`).join(", ")} }, parent: ${parent ? liveDeclsToString(parent) : "null"}`;
983
+ }
979
984
 
980
985
  //#endregion
981
986
  //#region src/Mangler.ts
@@ -2306,63 +2311,46 @@ function stdEnumerant(name$1) {
2306
2311
  * @return any new declaration elements found (they will need to be emitted)
2307
2312
  */
2308
2313
  function bindIdents(params) {
2309
- const { rootAst, registry, virtuals, accumulateUnbound } = params;
2314
+ const { rootAst, resolver, virtuals, accumulateUnbound } = params;
2310
2315
  const { conditions = {}, mangler = minimalMangle } = params;
2311
- const { rootScope } = rootAst;
2312
- const globalNames = /* @__PURE__ */ new Set();
2313
- const knownDecls = /* @__PURE__ */ new Set();
2314
- const validRootDecls = findValidRootDecls(rootScope, conditions);
2315
- validRootDecls.forEach((decl) => {
2316
- decl.mangledName = decl.originalName;
2317
- globalNames.add(decl.originalName);
2318
- knownDecls.add(decl);
2319
- });
2320
- const unbound = accumulateUnbound ? [] : void 0;
2321
- const globalStatements = /* @__PURE__ */ new Map();
2316
+ const validRootDecls = findValidRootDecls(rootAst.rootScope, conditions);
2317
+ const { globalNames, knownDecls } = initializeRootDecls(validRootDecls);
2322
2318
  const bindContext = {
2323
- registry,
2319
+ resolver,
2324
2320
  conditions,
2325
2321
  knownDecls,
2326
- foundScopes: /* @__PURE__ */ new Set(),
2327
- globalNames,
2328
- globalStatements,
2329
2322
  virtuals,
2330
2323
  mangler,
2331
- unbound
2324
+ foundScopes: /* @__PURE__ */ new Set(),
2325
+ globalNames,
2326
+ globalStatements: /* @__PURE__ */ new Map(),
2327
+ unbound: accumulateUnbound ? [] : void 0
2332
2328
  };
2333
2329
  const declEntries = validRootDecls.map((d) => [d.originalName, d]);
2330
+ const liveDecls = {
2331
+ decls: new Map(declEntries),
2332
+ parent: null
2333
+ };
2334
2334
  return {
2335
- decls: bindIdentsRecursive(rootScope, bindContext, {
2336
- decls: new Map(declEntries),
2337
- parent: null
2338
- }, true),
2335
+ decls: bindIdentsRecursive(rootAst.rootScope, bindContext, liveDecls, true),
2339
2336
  globalNames,
2340
- newStatements: [...globalStatements.values()],
2341
- unbound
2337
+ newStatements: [...bindContext.globalStatements.values()],
2338
+ unbound: bindContext.unbound
2342
2339
  };
2343
2340
  }
2344
- /** bind local references in library sources to reveal references to external packages */
2345
- function findUnboundIdents(registry) {
2346
- const bindContext = {
2347
- registry,
2348
- conditions: {},
2349
- knownDecls: /* @__PURE__ */ new Set(),
2350
- foundScopes: /* @__PURE__ */ new Set(),
2351
- globalNames: /* @__PURE__ */ new Set(),
2352
- globalStatements: /* @__PURE__ */ new Map(),
2353
- mangler: minimalMangle,
2354
- unbound: [],
2355
- dontFollowDecls: true
2356
- };
2357
- Object.entries(registry.modules).forEach(([_module, ast]) => {
2358
- const declEntries = findValidRootDecls(ast.rootScope, {}).map((d) => [d.originalName, d]);
2359
- const liveDecls = {
2360
- decls: new Map(declEntries),
2361
- parent: null
2362
- };
2363
- bindIdentsRecursive(ast.rootScope, bindContext, liveDecls, true);
2341
+ /** Initialize root declarations with mangled names and add to tracking sets */
2342
+ function initializeRootDecls(validRootDecls) {
2343
+ const globalNames = /* @__PURE__ */ new Set();
2344
+ const knownDecls = /* @__PURE__ */ new Set();
2345
+ validRootDecls.forEach((decl) => {
2346
+ decl.mangledName = decl.originalName;
2347
+ globalNames.add(decl.originalName);
2348
+ knownDecls.add(decl);
2364
2349
  });
2365
- return bindContext.unbound;
2350
+ return {
2351
+ globalNames,
2352
+ knownDecls
2353
+ };
2366
2354
  }
2367
2355
  /**
2368
2356
  * Find all conditionally valid declarations at the root level.
@@ -2384,13 +2372,13 @@ function findValidRootDecls(rootScope, conditions) {
2384
2372
  let elseValid = false;
2385
2373
  for (const item of rootScope.contents) if (item.kind === "decl") {
2386
2374
  assertThatDebug(item.declElem);
2387
- const { valid, nextElseState } = validateConditional(findConditional(item.declElem.attributes), elseValid, conditions);
2388
- elseValid = nextElseState;
2389
- if (valid) found.push(item);
2375
+ const validation = validateConditional(findConditional(item.declElem.attributes), elseValid, conditions);
2376
+ elseValid = validation.nextElseState;
2377
+ if (validation.valid) found.push(item);
2390
2378
  } else if (item.kind === "partial") {
2391
- const { valid, nextElseState } = validateConditional(item.condAttribute, elseValid, conditions);
2392
- elseValid = nextElseState;
2393
- if (valid) collectDecls(item, found);
2379
+ const validation = validateConditional(item.condAttribute, elseValid, conditions);
2380
+ elseValid = validation.nextElseState;
2381
+ if (validation.valid) collectDecls(item, found);
2394
2382
  }
2395
2383
  return found;
2396
2384
  }
@@ -2431,14 +2419,22 @@ function bindIdentsRecursive(scope, bindContext, liveDecls, isRoot = false) {
2431
2419
  const { dontFollowDecls, foundScopes } = bindContext;
2432
2420
  if (foundScopes.has(scope)) return [];
2433
2421
  foundScopes.add(scope);
2422
+ const { newGlobals, newFromChildren } = processScope(scope, bindContext, liveDecls, isRoot);
2423
+ return [
2424
+ newGlobals,
2425
+ newFromChildren,
2426
+ dontFollowDecls ? [] : handleDecls(newGlobals, bindContext)
2427
+ ].flat();
2428
+ }
2429
+ /** Process all identifiers and subscopes in this scope */
2430
+ function processScope(scope, bindContext, liveDecls, isRoot) {
2434
2431
  const newGlobals = [];
2435
2432
  const newFromChildren = [];
2436
2433
  let elseValid = false;
2437
2434
  scope.contents.forEach((child) => {
2438
- const { kind: kind$1 } = child;
2439
- if (kind$1 === "decl") {
2435
+ if (child.kind === "decl") {
2440
2436
  if (!isRoot) liveDecls.decls.set(child.originalName, child);
2441
- } else if (kind$1 === "ref") {
2437
+ } else if (child.kind === "ref") {
2442
2438
  const newDecl = handleRef(child, liveDecls, bindContext);
2443
2439
  if (newDecl) newGlobals.push(newDecl);
2444
2440
  } else {
@@ -2449,24 +2445,21 @@ function bindIdentsRecursive(scope, bindContext, liveDecls, isRoot = false) {
2449
2445
  }
2450
2446
  }
2451
2447
  });
2452
- return [
2448
+ return {
2453
2449
  newGlobals,
2454
- newFromChildren,
2455
- dontFollowDecls ? [] : handleDecls(newGlobals, bindContext)
2456
- ].flat();
2450
+ newFromChildren
2451
+ };
2457
2452
  }
2458
2453
  /**
2459
- * Trace references to their declarations
2460
- * mutates to:
2461
- * mangle declarations
2462
- * mark references as 'std' if they match a wgsl std function or type
2454
+ * Trace references to their declarations.
2455
+ * Mutates to mangle declarations and mark std references.
2463
2456
  *
2464
2457
  * @return the found declaration, or undefined if this ref has already been processed
2465
2458
  */
2466
2459
  function handleRef(ident$1, liveDecls, bindContext) {
2467
- const { registry, conditions, unbound, virtuals } = bindContext;
2460
+ const { resolver, conditions, unbound, virtuals } = bindContext;
2468
2461
  if (ident$1.refersTo || ident$1.std) return;
2469
- const foundDecl = findDeclInModule(ident$1, liveDecls) ?? findQualifiedImport(ident$1, registry, conditions, virtuals, unbound);
2462
+ const foundDecl = findDeclInModule(ident$1, liveDecls) ?? findQualifiedImport(ident$1, resolver, conditions, virtuals, unbound);
2470
2463
  if (foundDecl) {
2471
2464
  ident$1.refersTo = foundDecl.decl;
2472
2465
  return handleNewDecl(ident$1, foundDecl, bindContext);
@@ -2519,7 +2512,7 @@ function handleNewDecl(refIdent$1, foundDecl, bindContext) {
2519
2512
  return decl;
2520
2513
  }
2521
2514
  }
2522
- /** Using the LiveDecls, search earlier in the scope and in parent scopes to find a matching decl ident */
2515
+ /** Search earlier in the scope and parent scopes for a matching declaration */
2523
2516
  function findDeclInModule(ident$1, liveDecls) {
2524
2517
  const found = liveDecls.decls.get(ident$1.originalName);
2525
2518
  if (found) return {
@@ -2532,25 +2525,27 @@ function findDeclInModule(ident$1, liveDecls) {
2532
2525
  * Match a reference identifier to a declaration in another module via an import statement
2533
2526
  * or via an inline qualified ident e.g. foo::bar().
2534
2527
  */
2535
- function findQualifiedImport(refIdent$1, parsed, conditions, virtuals, unbound) {
2528
+ function findQualifiedImport(refIdent$1, resolver, conditions, virtuals, unbound) {
2536
2529
  const flatImps = flatImports(refIdent$1.ast, conditions);
2537
2530
  const identParts = refIdent$1.originalName.split("::");
2538
2531
  const modulePathParts = matchingImport(identParts, flatImps) ?? qualifiedIdent(identParts);
2539
- if (modulePathParts) {
2540
- const result = findExport(modulePathParts, refIdent$1.ast.srcModule, parsed, conditions, virtuals);
2541
- if (!result) if (unbound) unbound.push(modulePathParts);
2542
- else failIdent(refIdent$1, `module not found for '${modulePathParts.join("::")}'`);
2543
- return result;
2544
- } else if (unbound) unbound.push(identParts);
2545
- }
2546
- /** Combine an import using the flattened import array, find an import that matches a provided ident */
2532
+ if (!modulePathParts) {
2533
+ if (unbound) unbound.push(identParts);
2534
+ return;
2535
+ }
2536
+ const result = findExport(modulePathParts, refIdent$1.ast.srcModule, resolver, conditions, virtuals);
2537
+ if (!result) if (unbound) unbound.push(modulePathParts);
2538
+ else failIdent(refIdent$1, `module not found for '${modulePathParts.join("::")}'`);
2539
+ return result;
2540
+ }
2541
+ /** Find an import statement that matches a provided identifier */
2547
2542
  function matchingImport(identParts, flatImports$1) {
2548
2543
  for (const flat of flatImports$1) if (flat.importPath.at(-1) === identParts.at(0)) return [...flat.modulePath, ...identParts.slice(1)];
2549
2544
  }
2550
2545
  /** @return an exported root declIdent for the provided path */
2551
- function findExport(modulePathParts, srcModule, parsed, conditions = {}, virtuals) {
2546
+ function findExport(modulePathParts, srcModule, resolver, conditions = {}, virtuals) {
2552
2547
  const modulePath = absoluteModulePath(modulePathParts, srcModule).slice(0, -1).join("::");
2553
- const moduleAst = parsed.modules[modulePath] ?? virtualModule(modulePathParts[0], conditions, virtuals);
2548
+ const moduleAst = resolver.resolveModule(modulePath) ?? virtualModule(modulePathParts[0], conditions, virtuals);
2554
2549
  if (!moduleAst) return void 0;
2555
2550
  const name$1 = last(modulePathParts);
2556
2551
  const decl = publicDecl(moduleAst.rootScope, name$1, conditions);
@@ -2594,9 +2589,8 @@ function rootLiveDecls(decl, conditions) {
2594
2589
  return liveDecls;
2595
2590
  }
2596
2591
  /**
2597
- * Mutate a DeclIdent to set a unique name for global linking
2598
- * using a mangling function to choose a unique name.
2599
- * Also update the set of globally unique names.
2592
+ * Set a globally unique mangled name for this declaration.
2593
+ * Uses the mangler function to avoid name collisions.
2600
2594
  */
2601
2595
  function setMangledName(proposedName, decl, globalNames, srcModule, mangler) {
2602
2596
  if (decl.mangledName) return;
@@ -2758,68 +2752,136 @@ function noSuffix(path) {
2758
2752
  }
2759
2753
 
2760
2754
  //#endregion
2761
- //#region src/ParsedRegistry.ts
2762
- function parsedRegistry() {
2763
- resetScopeIds();
2764
- return { modules: {} };
2765
- }
2766
- /** for debug */
2767
- function registryToString(registry) {
2768
- return `modules: ${[...Object.keys(registry.modules)]}`;
2769
- }
2770
- /** Look up a module with a flexible selector.
2771
- * :: separated module path, package::util
2772
- * / separated file path ./util.wesl (or ./util)
2773
- * - note: a file path should not include a weslRoot prefix, e.g. not ./shaders/util.wesl
2774
- * simpleName util
2775
- */
2776
- function selectModule(parsed, selectPath, packageName = "package") {
2777
- let modulePath;
2778
- if (selectPath.includes("::")) modulePath = selectPath;
2779
- else if (selectPath.includes("/") || selectPath.endsWith(".wesl") || selectPath.endsWith(".wgsl")) modulePath = fileToModulePath(selectPath, packageName);
2780
- else modulePath = packageName + "::" + selectPath;
2781
- return parsed.modules[modulePath];
2782
- }
2783
- /**
2784
- * @param srcFiles map of source strings by file path
2785
- * key is '/' separated relative path (relative to srcRoot, not absolute file path )
2786
- * value is wesl source string
2787
- * @param registry add parsed modules to this registry
2788
- * @param packageName name of package
2789
- */
2790
- function parseIntoRegistry(srcFiles, registry, packageName = "package", debugWeslRoot) {
2791
- let weslRoot$1 = debugWeslRoot;
2792
- if (weslRoot$1 === void 0) weslRoot$1 = "";
2793
- else if (!weslRoot$1.endsWith("/")) weslRoot$1 += "/";
2794
- Object.entries(srcFiles).map(([filePath, src]) => {
2795
- return {
2796
- modulePath: fileToModulePath(filePath, packageName),
2797
- debugFilePath: weslRoot$1 + filePath,
2798
- src
2799
- };
2800
- }).forEach((mod) => {
2801
- const parsed = parseSrcModule(mod);
2802
- if (registry.modules[mod.modulePath]) return;
2803
- registry.modules[mod.modulePath] = parsed;
2804
- });
2805
- }
2806
- function parseLibsIntoRegistry(libs, registry) {
2807
- libs.forEach(({ modules, name: name$1 }) => {
2808
- parseIntoRegistry(modules, registry, name$1);
2809
- });
2810
- libs.forEach(({ dependencies }) => {
2811
- parseLibsIntoRegistry(dependencies || [], registry);
2812
- });
2813
- }
2755
+ //#region src/ModuleResolver.ts
2814
2756
  const libRegex = /^lib\.w[eg]sl$/i;
2815
- /** convert a file path (./foo/bar.wesl)
2816
- * to a module path (package::foo::bar) */
2817
- function fileToModulePath(filePath, packageName) {
2757
+ /** Module resolver for in-memory source records. Lazy by default. */
2758
+ var RecordResolver = class {
2759
+ astCache = /* @__PURE__ */ new Map();
2760
+ sources;
2761
+ packageName;
2762
+ debugWeslRoot;
2763
+ constructor(sources, options = {}) {
2764
+ const { packageName = "package", debugWeslRoot } = options;
2765
+ this.sources = sources;
2766
+ this.packageName = packageName;
2767
+ this.debugWeslRoot = normalizeDebugRoot(debugWeslRoot);
2768
+ }
2769
+ resolveModule(modulePath) {
2770
+ const cached = this.astCache.get(modulePath);
2771
+ if (cached) return cached;
2772
+ const source = this.findSource(modulePath);
2773
+ if (!source) return void 0;
2774
+ const ast = parseSrcModule({
2775
+ modulePath,
2776
+ debugFilePath: this.modulePathToDebugPath(modulePath),
2777
+ src: source
2778
+ });
2779
+ this.astCache.set(modulePath, ast);
2780
+ return ast;
2781
+ }
2782
+ findSource(modulePath) {
2783
+ if (this.sources[modulePath]) return this.sources[modulePath];
2784
+ const filePath = this.moduleToFilePath(modulePath);
2785
+ return findInVariants(this.sources, filePath);
2786
+ }
2787
+ moduleToFilePath(modulePath) {
2788
+ const parts = modulePath.split("::");
2789
+ if (parts[0] !== this.packageName && parts[0] !== "package") return modulePath;
2790
+ return parts.slice(1).join("/");
2791
+ }
2792
+ modulePathToDebugPath(modulePath) {
2793
+ const filePath = this.moduleToFilePath(modulePath);
2794
+ return this.debugWeslRoot + filePath + ".wesl";
2795
+ }
2796
+ /** Return all modules, parsing them on-demand if needed. */
2797
+ allModules() {
2798
+ for (const filePath of Object.keys(this.sources)) {
2799
+ const modulePath = fileToModulePath(filePath, this.packageName, this.packageName !== "package");
2800
+ this.resolveModule(modulePath);
2801
+ }
2802
+ return this.astCache.entries();
2803
+ }
2804
+ };
2805
+ /** Composite resolver that tries each resolver in order until one succeeds. */
2806
+ var CompositeResolver = class {
2807
+ resolvers;
2808
+ constructor(resolvers) {
2809
+ this.resolvers = resolvers;
2810
+ }
2811
+ resolveModule(modulePath) {
2812
+ for (const resolver of this.resolvers) {
2813
+ const ast = resolver.resolveModule(modulePath);
2814
+ if (ast) return ast;
2815
+ }
2816
+ }
2817
+ };
2818
+ /** Lazy resolver for WeslBundle library modules. */
2819
+ var BundleResolver = class {
2820
+ astCache = /* @__PURE__ */ new Map();
2821
+ sources;
2822
+ packageName;
2823
+ debugWeslRoot;
2824
+ constructor(bundle, debugWeslRoot) {
2825
+ this.sources = bundle.modules;
2826
+ this.packageName = bundle.name;
2827
+ this.debugWeslRoot = normalizeDebugRoot(debugWeslRoot);
2828
+ }
2829
+ resolveModule(modulePath) {
2830
+ if (modulePath !== this.packageName && !modulePath.startsWith(this.packageName + "::")) return;
2831
+ const cached = this.astCache.get(modulePath);
2832
+ if (cached) return cached;
2833
+ const source = this.findSource(modulePath);
2834
+ if (!source) return void 0;
2835
+ const ast = parseSrcModule({
2836
+ modulePath,
2837
+ debugFilePath: this.modulePathToDebugPath(modulePath),
2838
+ src: source
2839
+ });
2840
+ this.astCache.set(modulePath, ast);
2841
+ return ast;
2842
+ }
2843
+ findSource(modulePath) {
2844
+ const filePath = this.moduleToFilePath(modulePath);
2845
+ if (modulePath === this.packageName) {
2846
+ const libSrc = findInVariants(this.sources, "lib", ["wesl", "wgsl"]);
2847
+ if (libSrc) return libSrc;
2848
+ }
2849
+ return findInVariants(this.sources, filePath);
2850
+ }
2851
+ moduleToFilePath(modulePath) {
2852
+ const parts = modulePath.split("::");
2853
+ if (parts[0] !== this.packageName) return modulePath;
2854
+ return parts.slice(1).join("/");
2855
+ }
2856
+ modulePathToDebugPath(modulePath) {
2857
+ const filePath = this.moduleToFilePath(modulePath);
2858
+ return this.debugWeslRoot + this.packageName + "/" + filePath + ".wesl";
2859
+ }
2860
+ };
2861
+ /** Convert file path to module path (e.g., "foo/bar.wesl" -> "package::foo::bar"). */
2862
+ function fileToModulePath(filePath, packageName, treatLibAsRoot) {
2818
2863
  if (filePath.includes("::")) return filePath;
2819
- if (packageName !== "package" && libRegex.test(filePath)) return packageName;
2864
+ if (treatLibAsRoot && libRegex.test(filePath)) return packageName;
2820
2865
  const moduleSuffix = noSuffix(normalize(filePath)).replaceAll("/", "::");
2821
2866
  return packageName + "::" + moduleSuffix;
2822
2867
  }
2868
+ /** Normalize debug root to end with / or be empty. */
2869
+ function normalizeDebugRoot(debugWeslRoot) {
2870
+ if (debugWeslRoot === void 0) return "./";
2871
+ if (debugWeslRoot === "") return "";
2872
+ return debugWeslRoot.endsWith("/") ? debugWeslRoot : debugWeslRoot + "/";
2873
+ }
2874
+ /** Try path variants with and without ./ prefix and extension suffixes. */
2875
+ function findInVariants(sources, basePath, extensions = ["wesl", "wgsl"]) {
2876
+ for (const prefix of ["", "./"]) {
2877
+ const path = prefix + basePath;
2878
+ if (sources[path]) return sources[path];
2879
+ for (const ext of extensions) {
2880
+ const withExt = `${path}.${ext}`;
2881
+ if (sources[withExt]) return sources[withExt];
2882
+ }
2883
+ }
2884
+ }
2823
2885
 
2824
2886
  //#endregion
2825
2887
  //#region src/Linker.ts
@@ -2833,25 +2895,47 @@ function fileToModulePath(filePath, packageName) {
2833
2895
  * Only code that is valid with the current conditions is included in the output.
2834
2896
  */
2835
2897
  async function link(params) {
2836
- const { weslSrc, debugWeslRoot, libs = [] } = params;
2837
- const registry = parsedRegistry();
2838
- parseIntoRegistry(weslSrc, registry, "package", debugWeslRoot);
2839
- parseLibsIntoRegistry(libs, registry);
2840
- return new LinkedWesl(linkRegistry({
2841
- registry,
2842
- ...params
2843
- }));
2898
+ return new LinkedWesl(_linkSync(params));
2844
2899
  }
2845
2900
  /** linker api for benchmarking */
2846
2901
  function _linkSync(params) {
2847
- const { weslSrc, debugWeslRoot, libs = [] } = params;
2848
- const registry = parsedRegistry();
2849
- parseIntoRegistry(weslSrc, registry, "package", debugWeslRoot);
2850
- parseLibsIntoRegistry(libs, registry);
2851
- return new LinkedWesl(linkRegistry({
2852
- registry,
2853
- ...params
2902
+ const { weslSrc, libs = [], packageName, debugWeslRoot } = params;
2903
+ const { resolver } = params;
2904
+ const resolvers = [];
2905
+ if (resolver) resolvers.push(resolver);
2906
+ else if (weslSrc) resolvers.push(new RecordResolver(weslSrc, {
2907
+ packageName,
2908
+ debugWeslRoot
2854
2909
  }));
2910
+ else throw new Error("Either resolver or weslSrc must be provided");
2911
+ if (libs.length > 0) {
2912
+ const libResolvers = createLibraryResolvers(libs, debugWeslRoot);
2913
+ resolvers.push(...libResolvers);
2914
+ }
2915
+ return linkRegistry({
2916
+ resolver: resolvers.length === 1 ? resolvers[0] : new CompositeResolver(resolvers),
2917
+ ...params
2918
+ });
2919
+ }
2920
+ function createLibraryResolvers(libs, debugWeslRoot) {
2921
+ return flattenLibraryTree(libs).map((lib) => new BundleResolver(lib, debugWeslRoot));
2922
+ }
2923
+ /** Flatten library dependency tree, deduplicating by object identity rather than package name.
2924
+ *
2925
+ * Some packages (like Lygia) provide multiple bundles in the same npm package
2926
+ * to enable tree shaking. All bundles share the same package name, so we deduplicate
2927
+ * by object identity to keep them distinct. Also handles circular dependencies correctly. */
2928
+ function flattenLibraryTree(libs) {
2929
+ const result = [];
2930
+ const seen = /* @__PURE__ */ new Set();
2931
+ function visit(bundle) {
2932
+ if (seen.has(bundle)) return;
2933
+ seen.add(bundle);
2934
+ result.push(bundle);
2935
+ bundle.dependencies?.forEach(visit);
2936
+ }
2937
+ libs.forEach(visit);
2938
+ return result;
2855
2939
  }
2856
2940
  /** Link wesl from a registry of already parsed modules.
2857
2941
  *
@@ -2864,22 +2948,16 @@ function linkRegistry(params) {
2864
2948
  const { transformedAst, newDecls, newStatements } = bindAndTransform(params);
2865
2949
  return SrcMapBuilder.build(emitWgsl(transformedAst.moduleElem, transformedAst.srcModule, newDecls, newStatements, params.conditions));
2866
2950
  }
2867
- /** bind identifers and apply any transform plugins */
2951
+ /** Bind identifiers and apply transform plugins */
2868
2952
  function bindAndTransform(params) {
2869
- const { registry, mangler } = params;
2953
+ const { resolver, mangler, constants, config } = params;
2870
2954
  const { rootModuleName = "main", conditions = {} } = params;
2871
- const rootAst = getRootModule(registry, rootModuleName);
2872
- const { constants, config } = params;
2873
- let { virtualLibs } = params;
2874
- if (constants) virtualLibs = {
2875
- ...virtualLibs,
2876
- constants: constantsGenerator(constants)
2877
- };
2955
+ const rootAst = getRootModule(resolver, normalizeModuleName(rootModuleName), rootModuleName);
2878
2956
  const { globalNames, decls: newDecls, newStatements } = bindIdents({
2879
2957
  rootAst,
2880
- registry,
2958
+ resolver,
2881
2959
  conditions,
2882
- virtuals: virtualLibs && mapValues(virtualLibs, (fn$1) => ({ fn: fn$1 })),
2960
+ virtuals: setupVirtualLibs(params.virtualLibs, constants),
2883
2961
  mangler
2884
2962
  });
2885
2963
  return {
@@ -2888,22 +2966,34 @@ function bindAndTransform(params) {
2888
2966
  newStatements
2889
2967
  };
2890
2968
  }
2891
- function constantsGenerator(constants) {
2892
- return () => Object.entries(constants).map(([name$1, value]) => `const ${name$1} = ${value};`).join("\n");
2969
+ /** Convert root module name to module path format.
2970
+ * Accepts: module path (package::foo), file path (./foo.wesl), or name (foo) */
2971
+ function normalizeModuleName(name$1) {
2972
+ if (name$1.includes("::")) return name$1;
2973
+ if (name$1.includes("/") || name$1.endsWith(".wesl") || name$1.endsWith(".wgsl")) return "package::" + name$1.replace(/\.(wesl|wgsl)$/, "").replace(/^\.\//, "").replaceAll("/", "::");
2974
+ return "package::" + name$1;
2893
2975
  }
2894
- /** get a reference to the root module, selecting by module name */
2895
- function getRootModule(parsed, rootModuleName) {
2896
- const rootModule = selectModule(parsed, rootModuleName);
2897
- if (!rootModule) {
2898
- if (tracing) {
2899
- console.log(`parsed modules: ${Object.keys(parsed.modules)}`);
2900
- console.log(`root module not found: ${rootModuleName}`);
2901
- }
2976
+ /** Resolve root module AST or throw if not found. */
2977
+ function getRootModule(resolver, modulePath, rootModuleName) {
2978
+ const rootAst = resolver.resolveModule(modulePath);
2979
+ if (!rootAst) {
2980
+ if (tracing) console.log(`root module not found: ${modulePath} (from ${rootModuleName})`);
2902
2981
  throw new Error(`Root module not found: ${rootModuleName}`);
2903
2982
  }
2904
- return rootModule;
2983
+ return rootAst;
2984
+ }
2985
+ /** Create virtual library set from code generators and host constants. */
2986
+ function setupVirtualLibs(virtualLibs, constants) {
2987
+ let libs = virtualLibs;
2988
+ if (constants) {
2989
+ const constantsGen = () => Object.entries(constants).map(([name$1, value]) => `const ${name$1} = ${value};`).join("\n");
2990
+ libs = {
2991
+ ...libs,
2992
+ constants: constantsGen
2993
+ };
2994
+ }
2995
+ return libs && mapValues(libs, (fn$1) => ({ fn: fn$1 }));
2905
2996
  }
2906
- /** run any plugins that transform the AST */
2907
2997
  function applyTransformPlugins(rootModule, globalNames, config) {
2908
2998
  const { moduleElem, srcModule } = rootModule;
2909
2999
  const startAst = {
@@ -2914,23 +3004,8 @@ function applyTransformPlugins(rootModule, globalNames, config) {
2914
3004
  };
2915
3005
  return filterMap(config?.plugins ?? [], (plugin) => plugin.transform).reduce((ast, transform) => transform(ast), startAst);
2916
3006
  }
2917
- /** traverse the AST and emit WGSL */
2918
3007
  function emitWgsl(rootModuleElem, srcModule, newDecls, newStatements, conditions = {}) {
2919
- const prologueBuilders = newStatements.map((s) => {
2920
- const { elem, srcModule: srcModule$1 } = s;
2921
- const { src: text$1, debugFilePath: path } = srcModule$1;
2922
- const builder = new SrcMapBuilder({
2923
- text: text$1,
2924
- path
2925
- });
2926
- lowerAndEmit({
2927
- srcBuilder: builder,
2928
- rootElems: [elem],
2929
- conditions
2930
- });
2931
- builder.addNl();
2932
- return builder;
2933
- });
3008
+ const prologueBuilders = newStatements.map((s) => emitStatement(s, conditions));
2934
3009
  const rootBuilder = new SrcMapBuilder({
2935
3010
  text: srcModule.src,
2936
3011
  path: srcModule.debugFilePath
@@ -2941,25 +3016,43 @@ function emitWgsl(rootModuleElem, srcModule, newDecls, newStatements, conditions
2941
3016
  conditions,
2942
3017
  extracting: false
2943
3018
  });
2944
- const declBuilders = newDecls.map((decl) => {
2945
- const builder = new SrcMapBuilder({
2946
- text: decl.srcModule.src,
2947
- path: decl.srcModule.debugFilePath
2948
- });
2949
- lowerAndEmit({
2950
- srcBuilder: builder,
2951
- rootElems: [decl.declElem],
2952
- conditions,
2953
- skipConditionalFiltering: true
2954
- });
2955
- return builder;
2956
- });
3019
+ const declBuilders = newDecls.map((decl) => emitDecl(decl, conditions));
2957
3020
  return [
2958
3021
  ...prologueBuilders,
2959
3022
  rootBuilder,
2960
3023
  ...declBuilders
2961
3024
  ];
2962
3025
  }
3026
+ function emitStatement(s, conditions) {
3027
+ const { elem, srcModule } = s;
3028
+ const { src: text$1, debugFilePath: path } = srcModule;
3029
+ const builder = new SrcMapBuilder({
3030
+ text: text$1,
3031
+ path
3032
+ });
3033
+ lowerAndEmit({
3034
+ srcBuilder: builder,
3035
+ rootElems: [elem],
3036
+ conditions
3037
+ });
3038
+ builder.addNl();
3039
+ return builder;
3040
+ }
3041
+ /** Skip conditional filtering because findValidRootDecls already validated these declarations */
3042
+ function emitDecl(decl, conditions) {
3043
+ const { src: text$1, debugFilePath: path } = decl.srcModule;
3044
+ const builder = new SrcMapBuilder({
3045
+ text: text$1,
3046
+ path
3047
+ });
3048
+ lowerAndEmit({
3049
+ srcBuilder: builder,
3050
+ rootElems: [decl.declElem],
3051
+ conditions,
3052
+ skipConditionalFiltering: true
3053
+ });
3054
+ return builder;
3055
+ }
2963
3056
 
2964
3057
  //#endregion
2965
3058
  //#region src/LinkerUtil.ts
@@ -3294,4 +3387,4 @@ function makeWeslDevice(device) {
3294
3387
  }
3295
3388
 
3296
3389
  //#endregion
3297
- export { LinkedWesl, WeslParseError, WeslStream, _linkSync, astToString, attributeToString, bindAndTransform, bindIdents, bindingStructsPlugin, blankWeslParseState, childIdent, childScope, containsScope, debugContentsToString, emptyScope, errorHighlight, filterMap, findMap, findRefsToBindingStructs, findUnboundIdents, findValidRootDecls, flatImports, groupBy, grouped, identToString, isGlobal, last, lengthPrefixMangle, link, linkRegistry, lowerBindingStructs, makeWeslDevice, mapForward, mapValues, markBindingStructs, markEntryTypes, mergeScope, minimalMangle, minimallyMangledName, multiKeySet, nextIdentId, noSuffix, normalize, offsetToLineNumber, overlapTail, parseIntoRegistry, parseLibsIntoRegistry, parseSrcModule, parsedRegistry, partition, publicDecl, registryToString, replaceWords, requestWeslDevice, resetScopeIds, scan, scopeToString, scopeToStringLong, selectModule, syntheticWeslParseState, transformBindingReference, transformBindingStruct, underscoreMangle };
3390
+ export { BundleResolver, CompositeResolver, LinkedWesl, RecordResolver, WeslParseError, WeslStream, _linkSync, astToString, attributeToString, bindAndTransform, bindIdents, bindIdentsRecursive, bindingStructsPlugin, blankWeslParseState, childIdent, childScope, containsScope, debugContentsToString, emptyScope, errorHighlight, filterMap, findMap, findRefsToBindingStructs, findValidRootDecls, flatImports, groupBy, grouped, identToString, isGlobal, last, lengthPrefixMangle, link, linkRegistry, liveDeclsToString, lowerBindingStructs, makeLiveDecls, makeWeslDevice, mapForward, mapValues, markBindingStructs, markEntryTypes, mergeScope, minimalMangle, minimallyMangledName, multiKeySet, nextIdentId, noSuffix, normalize, normalizeModuleName, offsetToLineNumber, overlapTail, parseSrcModule, partition, publicDecl, replaceWords, requestWeslDevice, resetScopeIds, scan, scopeToString, scopeToStringLong, syntheticWeslParseState, transformBindingReference, transformBindingStruct, underscoreMangle };