diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..d3c7b99f0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Use LF everywhere +* text=auto eol=lf diff --git a/.gitignore b/.gitignore index da183bd09..763301fc0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,2 @@ -# dependency packages -node_modules - -# bundled release -dist - -# macOS -.DS_Store - -# vi temp files -*.swp - -# typescript build info cache files -*.tsbuildinfo - -# output file for esbuild meta.data for bundle size analysis -meta.json - -# output file for rollup-plugin-visualizer -stats.html \ No newline at end of file +dist/ +node_modules/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..f0820f072 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "deno.enable": true, + "deno.lint": true, + "[typescript]": { "editor.defaultFormatter": "denoland.vscode-deno" } +} diff --git a/README.md b/README.md index 684ca356e..97111e833 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,12 @@ The linker will rename one of them, and all the calls to the renamed function. You get only that function and its references, not the whole file. **wgsl-linker** is currently being revised to follow the upcoming community [WESL standard](https://github.com/wgsl-tooling-wg/wesl-spec). + +## Development + +Deno is used for almost everything. +- `deno install` for setup +- `deno lint` for linting +- `deno fmt` for formatting +- `deno test --allow-read` to run the unit tests + - To update the snapshots, run `deno test --allow-all -- --update` \ No newline at end of file diff --git a/packages/bench/README.md b/bench/README.md similarity index 72% rename from packages/bench/README.md rename to bench/README.md index 21756a85d..488d6732d 100644 --- a/packages/bench/README.md +++ b/bench/README.md @@ -5,7 +5,7 @@ Launch with: ```sh -pnpm tsx --inspect-brk bin/bench.ts --profile +deno --allow-read --allow-env --inspect-brk bench.ts --profile ``` And then launch the chrome debugger and press the green node button, and press play @@ -15,5 +15,5 @@ See instructions [here](https://developer.chrome.com/docs/devtools/performance/n ## Benchmark ```sh -pnpm tsx bin/bench.ts --bench +deno --allow-read --allow-env bench.ts --bench ``` diff --git a/packages/bench/bin/bench.ts b/bench/bench.ts similarity index 87% rename from packages/bench/bin/bench.ts rename to bench/bench.ts index 0bfa4b102..7563baf80 100644 --- a/packages/bench/bin/bench.ts +++ b/bench/bench.ts @@ -1,11 +1,11 @@ +#!/usr/bin/env -S deno run --allow-read --allow-env + import { WGSLLinker } from "@use-gpu/shader"; -import fs from "fs/promises"; -import path from "path"; -import { ModuleRegistry } from "wgsl-linker"; +import * as path from "@std/path"; +import { ModuleRegistry } from "@wesl/linker"; import { WgslReflect } from "wgsl_reflect"; import yargs from "yargs"; - -import { hideBin } from "yargs/helpers"; +import { profile, profileEnd } from "node:console"; type ParserVariant = | "wgsl-linker" @@ -16,8 +16,9 @@ type ParserVariant = type CliArgs = ReturnType; -const rawArgs = hideBin(process.argv); -main(rawArgs); +if (import.meta.main) { + await main(Deno.args); +} async function main(args: string[]): Promise { const argv = parseArgs(args); @@ -52,7 +53,7 @@ async function bench(argv: CliArgs): Promise { if (argv.bench) { const ms = runBench(variant, texts); const codeLines = texts - .map(t => t.text.split("\n").length) + .map((t) => t.text.split("\n").length) .reduce((a, b) => a + b, 0); const locSec = codeLines / ms; const locSecStr = new Intl.NumberFormat().format(Math.round(locSec)); @@ -60,9 +61,9 @@ async function bench(argv: CliArgs): Promise { } if (argv.profile) { - console.profile(); + profile(); runOnAllFiles(variant, texts); - console.profileEnd(); + profileEnd(); } } @@ -121,24 +122,24 @@ async function loadAllFiles(): Promise { // return [...boat]; const reduceBuffer = await loadFile( "reduceBuffer", - "./src/examples/reduceBuffer.wgsl", + "./examples/reduceBuffer.wgsl", ); const particle = await loadFile( "reduceBuffer", - "../../../community-wgsl/webgpu-samples/sample/particles/particle.wgsl", + "../../community-wgsl/webgpu-samples/sample/particles/particle.wgsl", ); return [reduceBuffer, particle]; } async function loadFile(name: string, path: string): Promise { - const text = await fs.readFile(path, { encoding: "utf8" }); + const text = await Deno.readTextFile(path); return { name, text }; } async function loadBoatFiles(): Promise { const boatAttackDir = - "../../../community-wgsl/unity_web_research/webgpu/wgsl/boat_attack"; + "../../community-wgsl/unity_web_research/webgpu/wgsl/boat_attack"; const boatName = "unity_webgpu_0000026E5689B260.fs.wgsl"; const boatPath = path.join(boatAttackDir, boatName); diff --git a/bench/deno.json b/bench/deno.json new file mode 100644 index 000000000..5fbad8c37 --- /dev/null +++ b/bench/deno.json @@ -0,0 +1,14 @@ +{ + "version": "0.1.0", + "tasks": { + "dev": "deno run --watch main.ts" + }, + "imports": { + "@std/fs": "jsr:@std/fs", + "@std/path": "jsr:@std/path", + "yargs": "https://deno.land/x/yargs@v17.7.2-deno/deno.ts", + "diff": "npm:diff", + "wgsl_reflect": "npm:wgsl_reflect@1.0.16", + "@use-gpu/shader": "npm:@use-gpu/shader@0.12.0" + } +} \ No newline at end of file diff --git a/packages/bench/src/examples/reduceBuffer.wgsl b/bench/examples/reduceBuffer.wgsl similarity index 100% rename from packages/bench/src/examples/reduceBuffer.wgsl rename to bench/examples/reduceBuffer.wgsl diff --git a/packages/bulk-test/README.md b/bulk-test/README.md similarity index 75% rename from packages/bulk-test/README.md rename to bulk-test/README.md index 4803972f3..85b88295b 100644 --- a/packages/bulk-test/README.md +++ b/bulk-test/README.md @@ -3,5 +3,5 @@ Test the parser/linker on community projects. ```sh -pnpm bulk-test +deno test --allow-read ``` diff --git a/bulk-test/deno.json b/bulk-test/deno.json new file mode 100644 index 000000000..79ce93597 --- /dev/null +++ b/bulk-test/deno.json @@ -0,0 +1,11 @@ +{ + "version": "0.1.0", + "tasks": { + "dev": "deno run --watch main.ts" + }, + "imports": { + "@std/fs": "jsr:@std/fs", + "@std/path": "jsr:@std/path", + "diff": "npm:diff" + } +} \ No newline at end of file diff --git a/packages/bulk-test/src/parallelDriver.ts b/bulk-test/parallelDriver.ts similarity index 72% rename from packages/bulk-test/src/parallelDriver.ts rename to bulk-test/parallelDriver.ts index 8a1935360..ce54eee37 100644 --- a/packages/bulk-test/src/parallelDriver.ts +++ b/bulk-test/parallelDriver.ts @@ -1,7 +1,7 @@ -import { glob } from "glob"; -import path from "node:path"; import { BulkTest, bulkTests } from "wesl-testsuite"; import { NamedPath } from "./parallelTest.ts"; +import * as path from "@std/path"; +import { expandGlob } from "@std/fs"; /* Vitest parallelizes per .test.ts file. * @@ -12,7 +12,7 @@ import { NamedPath } from "./parallelTest.ts"; * 16 parts, which we'll later use in 16 .test.ts runners. */ -const communityRoot = path.join("..", "..", "..", "community-wgsl"); +const communityRoot = path.join("..", "..", "community-wgsl"); const numParts = 16; const allPaths = await loadTests(); @@ -37,7 +37,7 @@ async function loadBulkSet(bulk: BulkTest): Promise { bulk.exclude, ); const relativePaths: string[] = [...includeFiles, ...globFiles]; - const namePaths: NamedPath[] = relativePaths.map(f => ({ + const namePaths: NamedPath[] = relativePaths.map((f) => ({ name: f, filePath: path.join(baseDir, f), })); @@ -49,19 +49,19 @@ async function findGlobFiles( globs: string[] | undefined, exclude: string[] | undefined, ): Promise { - const fullBaseDir = path.resolve(baseDir); - const cwd = process.cwd(); const skip = exclude ?? []; - try { - process.chdir(fullBaseDir); - const futurePaths = - globs?.map(g => glob(g, { ignore: ["node_modules/**"] })) ?? []; - const pathSets = await Promise.all(futurePaths); - const filePaths = pathSets.flat(); - return filePaths.filter(p => !skip.some(s => p.includes(s))); - } finally { - process.chdir(cwd); - } + const futurePaths = (globs ?? []).map((g) => + Array.fromAsync(expandGlob(g, { + root: baseDir, + exclude: ["node_modules/**"], + })) + ); + + const pathSets = await Promise.all(futurePaths); + const filePaths = pathSets.flat().filter((f) => f.isFile).map((f) => + path.relative(baseDir, f.path) + ); + return filePaths.filter((p) => !skip.some((s) => p.includes(s))); } /** split an array into n partitions */ diff --git a/packages/bulk-test/src/parallelTest.ts b/bulk-test/parallelTest.ts similarity index 83% rename from packages/bulk-test/src/parallelTest.ts rename to bulk-test/parallelTest.ts index ba948ef5d..c7878793c 100644 --- a/packages/bulk-test/src/parallelTest.ts +++ b/bulk-test/parallelTest.ts @@ -1,6 +1,5 @@ -import fs from "node:fs/promises"; import { test } from "vitest"; -import { ModuleRegistry } from "wgsl-linker"; +import { ModuleRegistry } from "@wesl/linker"; export interface NamedPath { name: string; // test name @@ -16,7 +15,7 @@ export function testWgslFiles(namedPaths: NamedPath[]) { namedPaths.forEach(({ name, filePath }) => { const shortPath = "./" + name; test(name, async () => { - const text = await fs.readFile(filePath, { encoding: "utf8" }); + const text = await Deno.readTextFile(filePath); const registry = new ModuleRegistry({ wgsl: { [shortPath]: text } }); registry.parsed(); // registry.link(shortPath); diff --git a/packages/bulk-test/bin/pickBoatShaders.ts b/bulk-test/pickBoatShaders.ts similarity index 78% rename from packages/bulk-test/bin/pickBoatShaders.ts rename to bulk-test/pickBoatShaders.ts index dedffe20d..b7ac00ccb 100644 --- a/packages/bulk-test/bin/pickBoatShaders.ts +++ b/bulk-test/pickBoatShaders.ts @@ -1,5 +1,4 @@ -import { dlogOpt } from "berry-pretty"; -import { uniquishFiles } from "../src/util/uniqueDocs.js"; +import { uniquishFiles } from "./util/uniqueDocs.ts"; /* The boat_attack sample shaders have significantly internal duplication * This script tries to find the relatively unique shaders for testing. @@ -21,8 +20,8 @@ const uniqueHlsl = await uniquishFiles({ // limitSearch: 100, // limitCheck: 5 }); -const wgsl = uniqueHlsl.map(p => p.replace(/hlsl$/, "wgsl")); -dlogOpt({ maxArray: 500 }, { wgsl }); +const wgsl = uniqueHlsl.map((p) => p.replace(/hlsl$/, "wgsl")); +console.log({ maxArray: 500 }, { wgsl }); function removeDirectiveLines(text: string): string { return text.replace(/^#.*$/gm, ""); diff --git a/bulk-test/test/parallel-0.test.ts b/bulk-test/test/parallel-0.test.ts new file mode 100644 index 000000000..9ae7646bd --- /dev/null +++ b/bulk-test/test/parallel-0.test.ts @@ -0,0 +1,4 @@ +import { pathSets } from "../parallelDriver.ts"; +import { testWgslFiles } from "../parallelTest.ts"; + +testWgslFiles(pathSets[0]); diff --git a/bulk-test/test/parallel-1.test.ts b/bulk-test/test/parallel-1.test.ts new file mode 100644 index 000000000..e4480fc94 --- /dev/null +++ b/bulk-test/test/parallel-1.test.ts @@ -0,0 +1,4 @@ +import { pathSets } from "../parallelDriver.ts"; +import { testWgslFiles } from "../parallelTest.ts"; + +testWgslFiles(pathSets[1]); diff --git a/bulk-test/test/parallel-10.test.ts b/bulk-test/test/parallel-10.test.ts new file mode 100644 index 000000000..8bdb1f5ce --- /dev/null +++ b/bulk-test/test/parallel-10.test.ts @@ -0,0 +1,4 @@ +import { pathSets } from "../parallelDriver.ts"; +import { testWgslFiles } from "../parallelTest.ts"; + +testWgslFiles(pathSets[10]); diff --git a/bulk-test/test/parallel-11.test.ts b/bulk-test/test/parallel-11.test.ts new file mode 100644 index 000000000..36819fd94 --- /dev/null +++ b/bulk-test/test/parallel-11.test.ts @@ -0,0 +1,4 @@ +import { pathSets } from "../parallelDriver.ts"; +import { testWgslFiles } from "../parallelTest.ts"; + +testWgslFiles(pathSets[11]); diff --git a/bulk-test/test/parallel-12.test.ts b/bulk-test/test/parallel-12.test.ts new file mode 100644 index 000000000..27b35160e --- /dev/null +++ b/bulk-test/test/parallel-12.test.ts @@ -0,0 +1,4 @@ +import { pathSets } from "../parallelDriver.ts"; +import { testWgslFiles } from "../parallelTest.ts"; + +testWgslFiles(pathSets[12]); diff --git a/bulk-test/test/parallel-13.test.ts b/bulk-test/test/parallel-13.test.ts new file mode 100644 index 000000000..75e53d0e3 --- /dev/null +++ b/bulk-test/test/parallel-13.test.ts @@ -0,0 +1,4 @@ +import { pathSets } from "../parallelDriver.ts"; +import { testWgslFiles } from "../parallelTest.ts"; + +testWgslFiles(pathSets[13]); diff --git a/bulk-test/test/parallel-14.test.ts b/bulk-test/test/parallel-14.test.ts new file mode 100644 index 000000000..5f09f0f76 --- /dev/null +++ b/bulk-test/test/parallel-14.test.ts @@ -0,0 +1,4 @@ +import { pathSets } from "../parallelDriver.ts"; +import { testWgslFiles } from "../parallelTest.ts"; + +testWgslFiles(pathSets[14]); diff --git a/bulk-test/test/parallel-15.test.ts b/bulk-test/test/parallel-15.test.ts new file mode 100644 index 000000000..9d89c1d91 --- /dev/null +++ b/bulk-test/test/parallel-15.test.ts @@ -0,0 +1,4 @@ +import { pathSets } from "../parallelDriver.ts"; +import { testWgslFiles } from "../parallelTest.ts"; + +testWgslFiles(pathSets[15]); diff --git a/bulk-test/test/parallel-2.test.ts b/bulk-test/test/parallel-2.test.ts new file mode 100644 index 000000000..35c28f2f6 --- /dev/null +++ b/bulk-test/test/parallel-2.test.ts @@ -0,0 +1,4 @@ +import { pathSets } from "../parallelDriver.ts"; +import { testWgslFiles } from "../parallelTest.ts"; + +testWgslFiles(pathSets[2]); diff --git a/bulk-test/test/parallel-3.test.ts b/bulk-test/test/parallel-3.test.ts new file mode 100644 index 000000000..2db2b4d06 --- /dev/null +++ b/bulk-test/test/parallel-3.test.ts @@ -0,0 +1,4 @@ +import { pathSets } from "../parallelDriver.ts"; +import { testWgslFiles } from "../parallelTest.ts"; + +testWgslFiles(pathSets[3]); diff --git a/bulk-test/test/parallel-4.test.ts b/bulk-test/test/parallel-4.test.ts new file mode 100644 index 000000000..476ea840a --- /dev/null +++ b/bulk-test/test/parallel-4.test.ts @@ -0,0 +1,4 @@ +import { pathSets } from "../parallelDriver.ts"; +import { testWgslFiles } from "../parallelTest.ts"; + +testWgslFiles(pathSets[4]); diff --git a/bulk-test/test/parallel-5.test.ts b/bulk-test/test/parallel-5.test.ts new file mode 100644 index 000000000..cb7d1624d --- /dev/null +++ b/bulk-test/test/parallel-5.test.ts @@ -0,0 +1,4 @@ +import { pathSets } from "../parallelDriver.ts"; +import { testWgslFiles } from "../parallelTest.ts"; + +testWgslFiles(pathSets[5]); diff --git a/bulk-test/test/parallel-6.test.ts b/bulk-test/test/parallel-6.test.ts new file mode 100644 index 000000000..1e8c291d6 --- /dev/null +++ b/bulk-test/test/parallel-6.test.ts @@ -0,0 +1,4 @@ +import { pathSets } from "../parallelDriver.ts"; +import { testWgslFiles } from "../parallelTest.ts"; + +testWgslFiles(pathSets[6]); diff --git a/bulk-test/test/parallel-7.test.ts b/bulk-test/test/parallel-7.test.ts new file mode 100644 index 000000000..e2c052710 --- /dev/null +++ b/bulk-test/test/parallel-7.test.ts @@ -0,0 +1,4 @@ +import { pathSets } from "../parallelDriver.ts"; +import { testWgslFiles } from "../parallelTest.ts"; + +testWgslFiles(pathSets[7]); diff --git a/bulk-test/test/parallel-8.test.ts b/bulk-test/test/parallel-8.test.ts new file mode 100644 index 000000000..5ab3c23d5 --- /dev/null +++ b/bulk-test/test/parallel-8.test.ts @@ -0,0 +1,4 @@ +import { pathSets } from "../parallelDriver.ts"; +import { testWgslFiles } from "../parallelTest.ts"; + +testWgslFiles(pathSets[8]); diff --git a/bulk-test/test/parallel-9.test.ts b/bulk-test/test/parallel-9.test.ts new file mode 100644 index 000000000..5b840c665 --- /dev/null +++ b/bulk-test/test/parallel-9.test.ts @@ -0,0 +1,4 @@ +import { pathSets } from "../parallelDriver.ts"; +import { testWgslFiles } from "../parallelTest.ts"; + +testWgslFiles(pathSets[9]); diff --git a/packages/bulk-test/src/util/uniqueDocs.ts b/bulk-test/util/uniqueDocs.ts similarity index 80% rename from packages/bulk-test/src/util/uniqueDocs.ts rename to bulk-test/util/uniqueDocs.ts index 3bec11087..3ab6ddb88 100644 --- a/packages/bulk-test/src/util/uniqueDocs.ts +++ b/bulk-test/util/uniqueDocs.ts @@ -1,7 +1,5 @@ -import { dlog } from "berry-pretty"; import { Change, diffLines } from "diff"; -import { glob } from "glob"; -import fs from "node:fs/promises"; +import { expandGlob } from "@std/fs"; /* Search through a set of files to find the uniquest ones. * Uses a diff library to compare files with each other @@ -52,8 +50,11 @@ export async function uniquishFiles( const { preprocessFn = (s: string) => s } = options; const { minAddLines, minAddPercent, limitCheck } = options; - process.chdir(rootDir); - const files = await glob(`./**/*.${suffix}`, { ignore: ["node_modules/**"] }); + // TODO: Don't use chdir ( https://github.com/denoland/deno/issues/25559 ) + Deno.chdir(rootDir); + const files = (await Array.fromAsync( + expandGlob(`./**/*.${suffix}`, { exclude: ["node_modules/**"] }), + )).filter((f) => f.isFile).map((f) => f.path); const sorted = await sortBySize(files); // consider shaders in largest first order const saved: SavedText[] = []; @@ -62,20 +63,20 @@ export async function uniquishFiles( let totalLines = 0; let nth = 0; for (const path of paths) { - const orig = await fs.readFile(path, { encoding: "utf8" }); + const orig = await Deno.readTextFile(path); const text = preprocessFn(orig); - dlog("checking", { nth: ++nth, path }); + console.log("checking", { nth: ++nth, path }); const diffOpts = { minAddLines, minAddPercent, limitCheck }; const percentDiff = differentText(saved, text, diffOpts); if (percentDiff !== undefined) { totalLines += text.split("\n").length; - dlog("saving", { path, percentDiff, totalLines }); + console.log("saving", { path, percentDiff, totalLines }); saved.push({ path, text }); } } - return saved.map(s => s.path); + return saved.map((s) => s.path); } /** return % difference if the the text differs significantly from saved texts, @@ -98,7 +99,7 @@ export function differentText( const addCount = addLines(changes); if (addCount < minAddLines) { const failedNth = saved.length - i; - if (limitCheck > 0 && failedNth > limitCheck) dlog({ failedNth }); + if (limitCheck > 0 && failedNth > limitCheck) console.log({ failedNth }); return undefined; } @@ -112,25 +113,25 @@ export function differentText( /** count the number of lines added in these changes */ function addLines(changes: Change[]): number { - const addChanges = changes.filter(c => c.added); - // dlog({addChanges}) + const addChanges = changes.filter((c) => c.added); + // console.log({addChanges}) // addChanges.forEach(c => { - // dlog({c}); + // console.log({c}); // }); - const addLineCounts = addChanges.map(c => c.value.split("\n").length); + const addLineCounts = addChanges.map((c) => c.value.split("\n").length); const totalLines = addLineCounts.reduce((a, b) => a + b, 0); - // dlog({ totalLines }); + // console.log({ totalLines }); return totalLines; } /** sort a set of files by size, largest first */ export async function sortBySize(paths: string[]): Promise { const sizes = await Promise.all( - paths.map(async path => { - const stats = await fs.stat(path); + paths.map(async (path) => { + const stats = await Deno.stat(path); return { path, size: stats.size }; }), ); sizes.sort((a, b) => b.size - a.size); - return sizes.map(s => s.path); + return sizes.map((s) => s.path); } diff --git a/packages/bulk-test/bin/verifyBoatShaders.ts b/bulk-test/verifyBoatShaders.ts similarity index 89% rename from packages/bulk-test/bin/verifyBoatShaders.ts rename to bulk-test/verifyBoatShaders.ts index 2c02f63dd..e8d138418 100644 --- a/packages/bulk-test/bin/verifyBoatShaders.ts +++ b/bulk-test/verifyBoatShaders.ts @@ -1,11 +1,9 @@ -import fs from "fs/promises"; import { DifferenceOptions, differentText, SavedText, sortBySize, -} from "../src/util/uniqueDocs.ts"; -import { dlog } from "berry-pretty"; +} from "./util/uniqueDocs.ts"; /* * The pickBoatShaders script diff compares hlsl files, incrementally and @@ -47,10 +45,11 @@ const texts = await loadTexts(selected); checkDifferences(texts); async function loadTexts(paths: string[]): Promise { - process.chdir(boatAttackDir); + // TODO: Don't use chdir + Deno.chdir(boatAttackDir); const sorted = await sortBySize(paths); - const futureTexts = sorted.map(async path => { - const text = await fs.readFile(path, { encoding: "utf8" }); + const futureTexts = sorted.map(async (path) => { + const text = await Deno.readTextFile(path); return { path, text }; }); @@ -68,6 +67,6 @@ function checkDifferences(texts: SavedText[]): void { minAddPercent: 0, }; const percent = differentText(otherTexts, t.text, opts); - dlog({ path: t.path, percent }); + console.log({ path: t.path, percent }); }); } diff --git a/packages/bulk-test/bin/writeParallelTests.ts b/bulk-test/writeParallelTests.ts similarity index 63% rename from packages/bulk-test/bin/writeParallelTests.ts rename to bulk-test/writeParallelTests.ts index a7846c37e..7e793e54c 100644 --- a/packages/bulk-test/bin/writeParallelTests.ts +++ b/bulk-test/writeParallelTests.ts @@ -1,5 +1,4 @@ -import fs from "node:fs/promises"; -import path from "node:path"; +import * as path from "@std/path"; const numParts = 16; @@ -10,7 +9,7 @@ async function writeFiles(): Promise { const fileName = `parallel-${i}.test.ts`; const filePath = path.join("src", "test", fileName); console.log(`Writing ${filePath}`); - return fs.writeFile(filePath, testText(i), { encoding: "utf8" }); + return Deno.writeTextFile(filePath, testText(i)); }); await Promise.all(writes); @@ -18,8 +17,8 @@ async function writeFiles(): Promise { function testText(i: number) { return ` -import { pathSets } from "../parallelDriver.js"; -import { testWgslFiles } from "../parallelTest.js"; +import { pathSets } from "../parallelDriver.ts"; +import { testWgslFiles } from "../parallelTest.ts"; testWgslFiles(pathSets[${i}]); `; diff --git a/packages/cli/README.md b/cli/README.md similarity index 100% rename from packages/cli/README.md rename to cli/README.md diff --git a/packages/cli/src/cli.ts b/cli/cli.ts similarity index 57% rename from packages/cli/src/cli.ts rename to cli/cli.ts index 8903b1d21..65ca5b035 100644 --- a/packages/cli/src/cli.ts +++ b/cli/cli.ts @@ -1,20 +1,27 @@ +import { ModuleRegistry, normalize } from "@wesl/linker"; import { createTwoFilesPatch } from "diff"; -import fs from "fs"; -import { ModuleRegistry, normalize } from "wgsl-linker"; +import { TypeRefElem } from "../linker/AbstractElems.ts"; import yargs from "yargs"; -import { TypeRefElem } from "../../linker/src/AbstractElems.js"; -type CliArgs = ReturnType; -let argv: CliArgs; +// TODO: Check if these are correct, and figure out why the types are broken +type CliArgs = { + define?: (string | number)[]; + baseDir?: string; + separately: boolean; + details: boolean; + diff: boolean; + emit: boolean; + files: string[]; +}; export async function cli(rawArgs: string[]): Promise { - argv = parseArgs(rawArgs); + const argv = parseArgs(rawArgs); const files = argv.files as string[]; - if (argv.separately) linkSeparately(files); - else linkNormally(files); + if (argv.separately) await linkSeparately(argv, files); + else await linkNormally(argv, files); } -function parseArgs(args: string[]) { +function parseArgs(args: string[]): CliArgs { return yargs(args) .command( "$0 ", @@ -57,44 +64,48 @@ function parseArgs(args: string[]) { .parseSync(); } -function linkNormally(paths: string[]): void { - const pathAndTexts = paths.map(f => { - const text = fs.readFileSync(f, { encoding: "utf8" }); - const basedPath = normalize(rmBaseDirPrefix(f)); - return [basedPath, text]; - }); - const wgsl = Object.fromEntries(pathAndTexts); +async function linkNormally(argv: CliArgs, paths: string[]): Promise { + const basedPaths = paths.map((path) => ({ + path: path, + basedPath: normalize(rmBaseDirPrefix(argv.baseDir, path)), + })); + const wgsl: Record = {}; + for (const { path, basedPath } of basedPaths) { + wgsl[basedPath] = await Deno.readTextFile(path); + } const registry = new ModuleRegistry({ wgsl }); - const [srcPath, srcText] = pathAndTexts[0]; - doLink(srcPath, registry, srcText); + const srcPath = basedPaths[0].basedPath; + const srcText = wgsl[srcPath]; + doLink(argv, srcPath, registry, srcText); } -function linkSeparately(paths: string[]): void { - paths.forEach(f => { - const srcText = fs.readFileSync(f, { encoding: "utf8" }); - const basedPath = normalize(rmBaseDirPrefix(f)); +async function linkSeparately(argv: CliArgs, paths: string[]): Promise { + for (const path of paths) { + const srcText = await Deno.readTextFile(path); + const basedPath = normalize(rmBaseDirPrefix(argv.baseDir, path)); const registry = new ModuleRegistry({ wgsl: { [basedPath]: srcText } }); - doLink(basedPath, registry, srcText); - }); + doLink(argv, basedPath, registry, srcText); + } } function doLink( + argv: CliArgs, srcPath: string, registry: ModuleRegistry, origWgsl: string, ): void { const asRelative = "./" + srcPath; - const linked = registry.link(asRelative, externalDefines()); + const linked = registry.link(asRelative, externalDefines(argv)); if (argv.emit) console.log(linked); if (argv.diff) printDiff(srcPath, origWgsl, linked); - if (argv.details) printDetails(srcPath, registry); + if (argv.details) printDetails(argv, srcPath, registry); } -function externalDefines(): Record { +function externalDefines(argv: CliArgs): Record { if (!argv.define) return {}; - const pairs = argv.define.map(d => d.toString().split("=")); + const pairs = argv.define.map((d) => d.toString().split("=")); - const badPair = pairs.find(p => p.length !== 2); + const badPair = pairs.find((p) => p.length !== 2); if (badPair) { console.error("invalid define", badPair); return {}; @@ -123,36 +134,39 @@ function printDiff(modulePath: string, src: string, linked: string): void { } } -function printDetails(modulePath: string, registry: ModuleRegistry): void { +function printDetails( + argv: CliArgs, + modulePath: string, + registry: ModuleRegistry, +): void { console.log(modulePath, ":"); - const parsed = registry.parsed(externalDefines()); + const parsed = registry.parsed(externalDefines(argv)); const m = parsed.findTextModule(modulePath)!; - m.fns.forEach(f => { + m.fns.forEach((f) => { console.log(` fn ${f.name}`); - const calls = f.calls.map(c => c.name).join(" "); + const calls = f.calls.map((c) => c.name).join(" "); console.log(` calls: ${calls}`); printTypeRefs(f); }); - m.vars.forEach(v => { + m.vars.forEach((v) => { console.log(` var ${v.name}`); printTypeRefs(v); }); - m.structs.forEach(s => { + m.structs.forEach((s) => { console.log(` struct ${s.name}`); - const members = (s.members ?? []).map(m => m.name).join(" "); + const members = (s.members ?? []).map((m) => m.name).join(" "); console.log(` members: ${members}`); - s.members.map(m => printTypeRefs(m)); + s.members.map((m) => printTypeRefs(m)); }); console.log(); } function printTypeRefs(hasTypeRefs: { typeRefs: TypeRefElem[] }): void { - const typeRefs = hasTypeRefs.typeRefs.map(t => t.name).join(" "); + const typeRefs = hasTypeRefs.typeRefs.map((t) => t.name).join(" "); console.log(` typeRefs: ${typeRefs}`); } -function rmBaseDirPrefix(path: string): string { - const baseDir = argv.baseDir; +function rmBaseDirPrefix(baseDir: string | undefined, path: string): string { if (baseDir) { const found = path.indexOf(baseDir); if (found !== -1) { diff --git a/cli/deno.json b/cli/deno.json new file mode 100644 index 000000000..d0687630f --- /dev/null +++ b/cli/deno.json @@ -0,0 +1,10 @@ +{ + "version": "0.1.0", + "tasks": { + "dev": "deno run --watch main.ts" + }, + "imports": { + "yargs": "https://deno.land/x/yargs@v17.7.2-deno/deno.ts", + "diff": "npm:diff" + } +} \ No newline at end of file diff --git a/cli/main.ts b/cli/main.ts new file mode 100644 index 000000000..3e1982227 --- /dev/null +++ b/cli/main.ts @@ -0,0 +1,6 @@ +#!/usr/bin/env -S deno run --allow-read --allow-write +import { cli } from "./cli.ts"; + +if (import.meta.main) { + await cli(Deno.args); +} diff --git a/cli/test/__snapshots__/wgsl-link.test.ts.snap b/cli/test/__snapshots__/wgsl-link.test.ts.snap new file mode 100644 index 000000000..f4f6b5770 --- /dev/null +++ b/cli/test/__snapshots__/wgsl-link.test.ts.snap @@ -0,0 +1,11 @@ +export const snapshot = {}; + +snapshot[`simple link 1`] = ` +"fn main() { + foo(); +} + +fn foo() { + // fooImpl +}" +`; diff --git a/cli/test/wgsl-link.test.ts b/cli/test/wgsl-link.test.ts new file mode 100644 index 000000000..095987819 --- /dev/null +++ b/cli/test/wgsl-link.test.ts @@ -0,0 +1,31 @@ +import { expect, test } from "vitest"; +import { stub } from "@std/testing/mock"; +import { cli } from "../cli.ts"; +import { assertSnapshot } from "@std/testing/snapshot"; + +/** so vitest triggers when these files change */ +// Deno doesn't support this at the moment :( +// https://github.com/denoland/deno/issues/17994#issuecomment-1879636936 hasn't materialized yet +// import("./src/test/wgsl/main.wgsl?raw"); +// import("./src/test/wgsl/util.wgsl?raw"); + +test("simple link", async (ctx) => { + using consoleStub = stub(console, "log", () => {}); + await cli( + `./test/wgsl/main.wgsl + ./test/wgsl/util.wgsl`.split(/\s+/), + ); + const logged = consoleStub.calls[0].args.join(""); + await assertSnapshot(ctx, logged); +}); + +test.ignore("link with definition", async () => { + using consoleStub = stub(console, "log", () => {}); + await cli( + `./test/wgsl/main.wgsl + ./test/wgsl/util.wgsl + --define EXTRA=true`.split(/\s+/), + ); + const logged = consoleStub.calls[0].args.join(""); + expect(logged).toContain("fn extra()"); +}); diff --git a/packages/cli/src/test/wgsl/main.wgsl b/cli/test/wgsl/main.wgsl similarity index 100% rename from packages/cli/src/test/wgsl/main.wgsl rename to cli/test/wgsl/main.wgsl diff --git a/packages/cli/src/test/wgsl/util.wgsl b/cli/test/wgsl/util.wgsl similarity index 100% rename from packages/cli/src/test/wgsl/util.wgsl rename to cli/test/wgsl/util.wgsl diff --git a/deno.json b/deno.json new file mode 100644 index 000000000..c834891ee --- /dev/null +++ b/deno.json @@ -0,0 +1,30 @@ +{ + "tasks": { + "dev": "deno test --watch --allow-read" + }, + "license": "MIT", + "imports": { + "@std/assert": "jsr:@std/assert@1", + "@std/expect": "jsr:@std/expect@1", + "@std/testing": "jsr:@std/testing@1", + "vitest": "./testlib/mod.ts", + "wesl-testsuite": "../wesl-testsuite/src/index.ts" + }, + "workspace": [ + "./bench", + "./bulk-test", + "./cli", + "./linker", + "./mini-parse", + "./packager", + "./testlib" + ], + "compilerOptions": { + "lib": [ + "dom", + "dom.iterable", + "dom.asynciterable", + "deno.ns" + ] + } +} \ No newline at end of file diff --git a/deno.lock b/deno.lock new file mode 100644 index 000000000..a38ce1fb0 --- /dev/null +++ b/deno.lock @@ -0,0 +1,341 @@ +{ + "version": "4", + "specifiers": { + "jsr:@std/assert@1": "1.0.7", + "jsr:@std/assert@^1.0.7": "1.0.7", + "jsr:@std/async@^1.0.8": "1.0.8", + "jsr:@std/data-structures@^1.0.4": "1.0.4", + "jsr:@std/expect@1": "1.0.7", + "jsr:@std/fs@*": "1.0.5", + "jsr:@std/fs@^1.0.5": "1.0.5", + "jsr:@std/internal@^1.0.5": "1.0.5", + "jsr:@std/path@*": "1.0.8", + "jsr:@std/path@^1.0.7": "1.0.8", + "jsr:@std/path@^1.0.8": "1.0.8", + "jsr:@std/testing@1": "1.0.4", + "npm:@types/node@*": "22.5.4", + "npm:@use-gpu/shader@0.12.0": "0.12.0", + "npm:diff@*": "7.0.0", + "npm:wgsl_reflect@1.0.16": "1.0.16" + }, + "jsr": { + "@std/assert@1.0.7": { + "integrity": "64ce9fac879e0b9f3042a89b3c3f8ccfc9c984391af19e2087513a79d73e28c3", + "dependencies": [ + "jsr:@std/internal" + ] + }, + "@std/async@1.0.8": { + "integrity": "c057c5211a0f1d12e7dcd111ab430091301b8d64b4250052a79d277383bc3ba7" + }, + "@std/data-structures@1.0.4": { + "integrity": "fa0e20c11eb9ba673417450915c750a0001405a784e2a4e0c3725031681684a0" + }, + "@std/expect@1.0.7": { + "integrity": "3904afa77cfe5c45dd2cd2c476e6fc5b6f57103d712890f0783d74c19d4b05a9", + "dependencies": [ + "jsr:@std/assert@^1.0.7", + "jsr:@std/internal" + ] + }, + "@std/fs@1.0.5": { + "integrity": "41806ad6823d0b5f275f9849a2640d87e4ef67c51ee1b8fb02426f55e02fd44e", + "dependencies": [ + "jsr:@std/path@^1.0.7" + ] + }, + "@std/internal@1.0.5": { + "integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba" + }, + "@std/path@1.0.8": { + "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" + }, + "@std/testing@1.0.4": { + "integrity": "ca1368d720b183f572d40c469bb9faf09643ddd77b54f8b44d36ae6b94940576", + "dependencies": [ + "jsr:@std/assert@^1.0.7", + "jsr:@std/async", + "jsr:@std/data-structures", + "jsr:@std/fs@^1.0.5", + "jsr:@std/internal", + "jsr:@std/path@^1.0.8" + ] + } + }, + "npm": { + "@jridgewell/sourcemap-codec@1.5.0": { + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "@lezer/common@1.2.3": { + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" + }, + "@lezer/generator@1.7.1": { + "integrity": "sha512-MgPJN9Si+ccxzXl3OAmCeZuUKw4XiPl4y664FX/hnnyG9CTqUPq65N3/VGPA2jD23D7QgMTtNqflta+cPN+5mQ==", + "dependencies": [ + "@lezer/common", + "@lezer/lr" + ] + }, + "@lezer/lr@1.4.2": { + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "dependencies": [ + "@lezer/common" + ] + }, + "@types/command-line-args@5.2.3": { + "integrity": "sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==" + }, + "@types/node@22.5.4": { + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "dependencies": [ + "undici-types" + ] + }, + "@use-gpu/shader@0.12.0": { + "integrity": "sha512-fVMXsKSuhvknOJecLX8j80nu8Jg5C/ALAWkDEjodooGzIFQeUXI0QfGw03GK7Adqsd8GfjbDUqRERE6vtvE5JQ==", + "dependencies": [ + "@lezer/generator", + "@lezer/lr", + "@types/command-line-args", + "command-line-args", + "lodash", + "lru-cache", + "magic-string" + ] + }, + "array-back@6.2.2": { + "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==" + }, + "command-line-args@6.0.1": { + "integrity": "sha512-Jr3eByUjqyK0qd8W0SGFW1nZwqCaNCtbXjRo2cRJC1OYxWl3MZ5t1US3jq+cO4sPavqgw4l9BMGX0CBe+trepg==", + "dependencies": [ + "array-back", + "find-replace", + "lodash.camelcase", + "typical" + ] + }, + "diff@7.0.0": { + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==" + }, + "find-replace@5.0.2": { + "integrity": "sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==" + }, + "lodash.camelcase@4.3.0": { + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "lodash@4.17.21": { + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lru-cache@6.0.0": { + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": [ + "yallist" + ] + }, + "magic-string@0.30.12": { + "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", + "dependencies": [ + "@jridgewell/sourcemap-codec" + ] + }, + "typical@7.3.0": { + "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==" + }, + "undici-types@6.19.8": { + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "wgsl_reflect@1.0.16": { + "integrity": "sha512-OE3urfXXbHMD5lhKZwxOxC9SFYynEGEkWXQmvi7B1gzzr5jb9+drh9A8MeBvVqKqznCoBuh8WOzVuSGSZs4CkQ==" + }, + "yallist@4.0.0": { + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + }, + "redirects": { + "https://deno.land/std/fmt/printf.ts": "https://deno.land/std@0.224.0/fmt/printf.ts", + "https://deno.land/std/path/mod.ts": "https://deno.land/std@0.224.0/path/mod.ts", + "https://deno.land/std/testing/asserts.ts": "https://deno.land/std@0.224.0/testing/asserts.ts" + }, + "remote": { + "https://deno.land/std@0.224.0/assert/_constants.ts": "a271e8ef5a573f1df8e822a6eb9d09df064ad66a4390f21b3e31f820a38e0975", + "https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834", + "https://deno.land/std@0.224.0/assert/assert_almost_equals.ts": "9e416114322012c9a21fa68e187637ce2d7df25bcbdbfd957cd639e65d3cf293", + "https://deno.land/std@0.224.0/assert/assert_array_includes.ts": "14c5094471bc8e4a7895fc6aa5a184300d8a1879606574cb1cd715ef36a4a3c7", + "https://deno.land/std@0.224.0/assert/assert_equals.ts": "3bbca947d85b9d374a108687b1a8ba3785a7850436b5a8930d81f34a32cb8c74", + "https://deno.land/std@0.224.0/assert/assert_exists.ts": "43420cf7f956748ae6ed1230646567b3593cb7a36c5a5327269279c870c5ddfd", + "https://deno.land/std@0.224.0/assert/assert_false.ts": "3e9be8e33275db00d952e9acb0cd29481a44fa0a4af6d37239ff58d79e8edeff", + "https://deno.land/std@0.224.0/assert/assert_greater.ts": "5e57b201fd51b64ced36c828e3dfd773412c1a6120c1a5a99066c9b261974e46", + "https://deno.land/std@0.224.0/assert/assert_greater_or_equal.ts": "9870030f997a08361b6f63400273c2fb1856f5db86c0c3852aab2a002e425c5b", + "https://deno.land/std@0.224.0/assert/assert_instance_of.ts": "e22343c1fdcacfaea8f37784ad782683ec1cf599ae9b1b618954e9c22f376f2c", + "https://deno.land/std@0.224.0/assert/assert_is_error.ts": "f856b3bc978a7aa6a601f3fec6603491ab6255118afa6baa84b04426dd3cc491", + "https://deno.land/std@0.224.0/assert/assert_less.ts": "60b61e13a1982865a72726a5fa86c24fad7eb27c3c08b13883fb68882b307f68", + "https://deno.land/std@0.224.0/assert/assert_less_or_equal.ts": "d2c84e17faba4afe085e6c9123a63395accf4f9e00150db899c46e67420e0ec3", + "https://deno.land/std@0.224.0/assert/assert_match.ts": "ace1710dd3b2811c391946954234b5da910c5665aed817943d086d4d4871a8b7", + "https://deno.land/std@0.224.0/assert/assert_not_equals.ts": "78d45dd46133d76ce624b2c6c09392f6110f0df9b73f911d20208a68dee2ef29", + "https://deno.land/std@0.224.0/assert/assert_not_instance_of.ts": "3434a669b4d20cdcc5359779301a0588f941ffdc2ad68803c31eabdb4890cf7a", + "https://deno.land/std@0.224.0/assert/assert_not_match.ts": "df30417240aa2d35b1ea44df7e541991348a063d9ee823430e0b58079a72242a", + "https://deno.land/std@0.224.0/assert/assert_not_strict_equals.ts": "37f73880bd672709373d6dc2c5f148691119bed161f3020fff3548a0496f71b8", + "https://deno.land/std@0.224.0/assert/assert_object_match.ts": "411450fd194fdaabc0089ae68f916b545a49d7b7e6d0026e84a54c9e7eed2693", + "https://deno.land/std@0.224.0/assert/assert_rejects.ts": "4bee1d6d565a5b623146a14668da8f9eb1f026a4f338bbf92b37e43e0aa53c31", + "https://deno.land/std@0.224.0/assert/assert_strict_equals.ts": "b4f45f0fd2e54d9029171876bd0b42dd9ed0efd8f853ab92a3f50127acfa54f5", + "https://deno.land/std@0.224.0/assert/assert_string_includes.ts": "496b9ecad84deab72c8718735373feb6cdaa071eb91a98206f6f3cb4285e71b8", + "https://deno.land/std@0.224.0/assert/assert_throws.ts": "c6508b2879d465898dab2798009299867e67c570d7d34c90a2d235e4553906eb", + "https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917", + "https://deno.land/std@0.224.0/assert/equal.ts": "bddf07bb5fc718e10bb72d5dc2c36c1ce5a8bdd3b647069b6319e07af181ac47", + "https://deno.land/std@0.224.0/assert/fail.ts": "0eba674ffb47dff083f02ced76d5130460bff1a9a68c6514ebe0cdea4abadb68", + "https://deno.land/std@0.224.0/assert/mod.ts": "48b8cb8a619ea0b7958ad7ee9376500fe902284bb36f0e32c598c3dc34cbd6f3", + "https://deno.land/std@0.224.0/assert/unimplemented.ts": "8c55a5793e9147b4f1ef68cd66496b7d5ba7a9e7ca30c6da070c1a58da723d73", + "https://deno.land/std@0.224.0/assert/unreachable.ts": "5ae3dbf63ef988615b93eb08d395dda771c96546565f9e521ed86f6510c29e19", + "https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5", + "https://deno.land/std@0.224.0/fmt/printf.ts": "8d01408076e2f956b03dd8377010c4974515d6cc909978d2edc5c8cd75077eeb", + "https://deno.land/std@0.224.0/internal/diff.ts": "6234a4b493ebe65dc67a18a0eb97ef683626a1166a1906232ce186ae9f65f4e6", + "https://deno.land/std@0.224.0/internal/format.ts": "0a98ee226fd3d43450245b1844b47003419d34d210fa989900861c79820d21c2", + "https://deno.land/std@0.224.0/internal/mod.ts": "534125398c8e7426183e12dc255bb635d94e06d0f93c60a297723abe69d3b22e", + "https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8", + "https://deno.land/std@0.224.0/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2", + "https://deno.land/std@0.224.0/path/_common/common.ts": "ef73c2860694775fe8ffcbcdd387f9f97c7a656febf0daa8c73b56f4d8a7bd4c", + "https://deno.land/std@0.224.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c", + "https://deno.land/std@0.224.0/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", + "https://deno.land/std@0.224.0/path/_common/format.ts": "92500e91ea5de21c97f5fe91e178bae62af524b72d5fcd246d6d60ae4bcada8b", + "https://deno.land/std@0.224.0/path/_common/from_file_url.ts": "d672bdeebc11bf80e99bf266f886c70963107bdd31134c4e249eef51133ceccf", + "https://deno.land/std@0.224.0/path/_common/glob_to_reg_exp.ts": "6cac16d5c2dc23af7d66348a7ce430e5de4e70b0eede074bdbcf4903f4374d8d", + "https://deno.land/std@0.224.0/path/_common/normalize.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8", + "https://deno.land/std@0.224.0/path/_common/normalize_string.ts": "33edef773c2a8e242761f731adeb2bd6d683e9c69e4e3d0092985bede74f4ac3", + "https://deno.land/std@0.224.0/path/_common/relative.ts": "faa2753d9b32320ed4ada0733261e3357c186e5705678d9dd08b97527deae607", + "https://deno.land/std@0.224.0/path/_common/strip_trailing_separators.ts": "7024a93447efcdcfeaa9339a98fa63ef9d53de363f1fbe9858970f1bba02655a", + "https://deno.land/std@0.224.0/path/_common/to_file_url.ts": "7f76adbc83ece1bba173e6e98a27c647712cab773d3f8cbe0398b74afc817883", + "https://deno.land/std@0.224.0/path/_interface.ts": "8dfeb930ca4a772c458a8c7bbe1e33216fe91c253411338ad80c5b6fa93ddba0", + "https://deno.land/std@0.224.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15", + "https://deno.land/std@0.224.0/path/basename.ts": "7ee495c2d1ee516ffff48fb9a93267ba928b5a3486b550be73071bc14f8cc63e", + "https://deno.land/std@0.224.0/path/common.ts": "03e52e22882402c986fe97ca3b5bb4263c2aa811c515ce84584b23bac4cc2643", + "https://deno.land/std@0.224.0/path/constants.ts": "0c206169ca104938ede9da48ac952de288f23343304a1c3cb6ec7625e7325f36", + "https://deno.land/std@0.224.0/path/dirname.ts": "85bd955bf31d62c9aafdd7ff561c4b5fb587d11a9a5a45e2b01aedffa4238a7c", + "https://deno.land/std@0.224.0/path/extname.ts": "593303db8ae8c865cbd9ceec6e55d4b9ac5410c1e276bfd3131916591b954441", + "https://deno.land/std@0.224.0/path/format.ts": "6ce1779b0980296cf2bc20d66436b12792102b831fd281ab9eb08fa8a3e6f6ac", + "https://deno.land/std@0.224.0/path/from_file_url.ts": "911833ae4fd10a1c84f6271f36151ab785955849117dc48c6e43b929504ee069", + "https://deno.land/std@0.224.0/path/glob_to_regexp.ts": "7f30f0a21439cadfdae1be1bf370880b415e676097fda584a63ce319053b5972", + "https://deno.land/std@0.224.0/path/is_absolute.ts": "4791afc8bfd0c87f0526eaa616b0d16e7b3ab6a65b62942e50eac68de4ef67d7", + "https://deno.land/std@0.224.0/path/is_glob.ts": "a65f6195d3058c3050ab905705891b412ff942a292bcbaa1a807a74439a14141", + "https://deno.land/std@0.224.0/path/join.ts": "ae2ec5ca44c7e84a235fd532e4a0116bfb1f2368b394db1c4fb75e3c0f26a33a", + "https://deno.land/std@0.224.0/path/join_globs.ts": "5b3bf248b93247194f94fa6947b612ab9d3abd571ca8386cf7789038545e54a0", + "https://deno.land/std@0.224.0/path/mod.ts": "f6bd79cb08be0e604201bc9de41ac9248582699d1b2ee0ab6bc9190d472cf9cd", + "https://deno.land/std@0.224.0/path/normalize.ts": "4155743ccceeed319b350c1e62e931600272fad8ad00c417b91df093867a8352", + "https://deno.land/std@0.224.0/path/normalize_glob.ts": "cc89a77a7d3b1d01053b9dcd59462b75482b11e9068ae6c754b5cf5d794b374f", + "https://deno.land/std@0.224.0/path/parse.ts": "77ad91dcb235a66c6f504df83087ce2a5471e67d79c402014f6e847389108d5a", + "https://deno.land/std@0.224.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d", + "https://deno.land/std@0.224.0/path/posix/basename.ts": "d2fa5fbbb1c5a3ab8b9326458a8d4ceac77580961b3739cd5bfd1d3541a3e5f0", + "https://deno.land/std@0.224.0/path/posix/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", + "https://deno.land/std@0.224.0/path/posix/constants.ts": "93481efb98cdffa4c719c22a0182b994e5a6aed3047e1962f6c2c75b7592bef1", + "https://deno.land/std@0.224.0/path/posix/dirname.ts": "76cd348ffe92345711409f88d4d8561d8645353ac215c8e9c80140069bf42f00", + "https://deno.land/std@0.224.0/path/posix/extname.ts": "e398c1d9d1908d3756a7ed94199fcd169e79466dd88feffd2f47ce0abf9d61d2", + "https://deno.land/std@0.224.0/path/posix/format.ts": "185e9ee2091a42dd39e2a3b8e4925370ee8407572cee1ae52838aed96310c5c1", + "https://deno.land/std@0.224.0/path/posix/from_file_url.ts": "951aee3a2c46fd0ed488899d024c6352b59154c70552e90885ed0c2ab699bc40", + "https://deno.land/std@0.224.0/path/posix/glob_to_regexp.ts": "76f012fcdb22c04b633f536c0b9644d100861bea36e9da56a94b9c589a742e8f", + "https://deno.land/std@0.224.0/path/posix/is_absolute.ts": "cebe561ad0ae294f0ce0365a1879dcfca8abd872821519b4fcc8d8967f888ede", + "https://deno.land/std@0.224.0/path/posix/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", + "https://deno.land/std@0.224.0/path/posix/join.ts": "7fc2cb3716aa1b863e990baf30b101d768db479e70b7313b4866a088db016f63", + "https://deno.land/std@0.224.0/path/posix/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25", + "https://deno.land/std@0.224.0/path/posix/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604", + "https://deno.land/std@0.224.0/path/posix/normalize.ts": "baeb49816a8299f90a0237d214cef46f00ba3e95c0d2ceb74205a6a584b58a91", + "https://deno.land/std@0.224.0/path/posix/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6", + "https://deno.land/std@0.224.0/path/posix/parse.ts": "09dfad0cae530f93627202f28c1befa78ea6e751f92f478ca2cc3b56be2cbb6a", + "https://deno.land/std@0.224.0/path/posix/relative.ts": "3907d6eda41f0ff723d336125a1ad4349112cd4d48f693859980314d5b9da31c", + "https://deno.land/std@0.224.0/path/posix/resolve.ts": "08b699cfeee10cb6857ccab38fa4b2ec703b0ea33e8e69964f29d02a2d5257cf", + "https://deno.land/std@0.224.0/path/posix/to_file_url.ts": "7aa752ba66a35049e0e4a4be5a0a31ac6b645257d2e031142abb1854de250aaf", + "https://deno.land/std@0.224.0/path/posix/to_namespaced_path.ts": "28b216b3c76f892a4dca9734ff1cc0045d135532bfd9c435ae4858bfa5a2ebf0", + "https://deno.land/std@0.224.0/path/relative.ts": "ab739d727180ed8727e34ed71d976912461d98e2b76de3d3de834c1066667add", + "https://deno.land/std@0.224.0/path/resolve.ts": "a6f977bdb4272e79d8d0ed4333e3d71367cc3926acf15ac271f1d059c8494d8d", + "https://deno.land/std@0.224.0/path/to_file_url.ts": "88f049b769bce411e2d2db5bd9e6fd9a185a5fbd6b9f5ad8f52bef517c4ece1b", + "https://deno.land/std@0.224.0/path/to_namespaced_path.ts": "b706a4103b104cfadc09600a5f838c2ba94dbcdb642344557122dda444526e40", + "https://deno.land/std@0.224.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808", + "https://deno.land/std@0.224.0/path/windows/basename.ts": "6bbc57bac9df2cec43288c8c5334919418d784243a00bc10de67d392ab36d660", + "https://deno.land/std@0.224.0/path/windows/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4", + "https://deno.land/std@0.224.0/path/windows/constants.ts": "5afaac0a1f67b68b0a380a4ef391bf59feb55856aa8c60dfc01bd3b6abb813f5", + "https://deno.land/std@0.224.0/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9", + "https://deno.land/std@0.224.0/path/windows/extname.ts": "165a61b00d781257fda1e9606a48c78b06815385e7d703232548dbfc95346bef", + "https://deno.land/std@0.224.0/path/windows/format.ts": "bbb5ecf379305b472b1082cd2fdc010e44a0020030414974d6029be9ad52aeb6", + "https://deno.land/std@0.224.0/path/windows/from_file_url.ts": "ced2d587b6dff18f963f269d745c4a599cf82b0c4007356bd957cb4cb52efc01", + "https://deno.land/std@0.224.0/path/windows/glob_to_regexp.ts": "e45f1f89bf3fc36f94ab7b3b9d0026729829fabc486c77f414caebef3b7304f8", + "https://deno.land/std@0.224.0/path/windows/is_absolute.ts": "4a8f6853f8598cf91a835f41abed42112cebab09478b072e4beb00ec81f8ca8a", + "https://deno.land/std@0.224.0/path/windows/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9", + "https://deno.land/std@0.224.0/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf", + "https://deno.land/std@0.224.0/path/windows/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25", + "https://deno.land/std@0.224.0/path/windows/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604", + "https://deno.land/std@0.224.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780", + "https://deno.land/std@0.224.0/path/windows/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6", + "https://deno.land/std@0.224.0/path/windows/parse.ts": "08804327b0484d18ab4d6781742bf374976de662f8642e62a67e93346e759707", + "https://deno.land/std@0.224.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7", + "https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972", + "https://deno.land/std@0.224.0/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e", + "https://deno.land/std@0.224.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c", + "https://deno.land/std@0.224.0/testing/asserts.ts": "d0cdbabadc49cc4247a50732ee0df1403fdcd0f95360294ad448ae8c240f3f5c", + "https://deno.land/x/cliui@v7.0.4-deno/build/lib/index.js": "fb6030c7b12602a4fca4d81de3ddafa301ba84fd9df73c53de6f3bdda7b482d5", + "https://deno.land/x/cliui@v7.0.4-deno/build/lib/string-utils.js": "b3eb9d2e054a43a3064af17332fb1839a7dadb205c5371af4789616afb1a117f", + "https://deno.land/x/cliui@v7.0.4-deno/deno.ts": "d07bc3338661f8011e3a5fd215061d17a52107a5383c29f40ce0c1ecb8bb8cc3", + "https://deno.land/x/escalade@v3.0.3/sync.ts": "493bc66563292c5c10c4a75a467a5933f24dad67d74b0f5a87e7b988fe97c104", + "https://deno.land/x/y18n@v5.0.0-deno/build/lib/index.js": "92c4624714aa508d33c6d21c0b0ffa072369a8b306e5f8c7727662f570bbd026", + "https://deno.land/x/y18n@v5.0.0-deno/deno.ts": "80997f0709a0b43d29931e2b33946f2bbc32b13fd82f80a5409628455427e28d", + "https://deno.land/x/y18n@v5.0.0-deno/lib/platform-shims/deno.ts": "8fa2c96ac03734966260cfd2c5bc240e41725c913e5b64a0297aede09f52b39d", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/argsert.js": "eb085555452eac3ff300935994a42f35d16e04cf698cb775cb5ad4f5653c0627", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/command.js": "6249ffd299e16a1e531ccff13a23aed7b7eef37e20b6e6ab7f254413aece6ca6", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/completion-templates.js": "d9bbed244af4394b786f8abce9efbbdc3777a73458ebd7b6bf23b2495ac11027", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/completion.js": "62e41220b5baa7c082f72638c7eab23a69fff46a78011f2c448e2a2f1fcfd05a", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/middleware.js": "6ab9c953a83264739aa50d7fa6b1ab693500336dfd593b9958865e12beb8bdeb", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/parse-command.js": "327242c0afae207b7aefa13133439e3b321d7db4229febc5b7bd5285770ac7f7", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/typings/common-types.js": "9618b81a86acb88a61fd9988e9bc3ec21c5250d94fc2231ba7d898e71500789d", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/usage.js": "31faaa7aa61e5a57a2cac5a269b773aa8b1fcab2db7cac2f8252396f3ccc2f5e", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/utils/apply-extends.js": "64640dce92669705abead3bdbe2c46c8318c8623843a55e4726fb3c55ff9dd1d", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/utils/is-promise.js": "be45baa3090c5106dd4e442cceef6b357a268783a2ee28ec10fe131a8cd8db72", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/utils/levenshtein.js": "d8638efc3376b5f794b1c8df6ef4f3d484b29d919127c7fdc242400e3cfded91", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/utils/maybe-async-result.js": "31cf4026279e14c87d16faa14ac758f35c8cc5795d29393c5ce07120f5a3caf6", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/utils/obj-filter.js": "5523fb2288d1e86ed48c460e176770b49587554df4ae2405b468c093786b040b", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/utils/set-blocking.js": "6fa8ffc3299f456e42902736bae35fbc1f2dc96b3905a02ba9629f5bd9f80af1", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/utils/which-module.js": "9267633b2c9f8990b2c699101b641e59ae59932e0dee5270613c0508bfa13c5d", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/validation.js": "af040834cb9201d4238bbeb8f673eb2ebaff9611857270524a7c86dfcf2ca51b", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/yargs-factory.js": "05326932b431801d7459d5b14b21f73f13ebd74a8a74e9b7b8cec5f99ba14819", + "https://deno.land/x/yargs@v17.7.2-deno/build/lib/yerror.js": "9729aaa8bce1a0d00c57f470efb2ad76ad2988661bb48f3769e496a3435b4462", + "https://deno.land/x/yargs@v17.7.2-deno/deno.ts": "f3df0bfd08ba367ec36dc59ef6cab1a391ace49ad44387ec5fe5d76289af08af", + "https://deno.land/x/yargs@v17.7.2-deno/lib/platform-shims/deno.ts": "1d3d490a7f3c6f971a44dd92e12a042f988f1b6496df3a9c43ccc69563032dff", + "https://deno.land/x/yargs_parser@v20.2.4-deno/build/lib/string-utils.js": "12fc056b23703bc370aae5b179dc5abee53fca277abc30eaf76f78d2546d6413", + "https://deno.land/x/yargs_parser@v20.2.4-deno/build/lib/tokenize-arg-string.js": "7e0875b11795b8e217386e45f14b24a6e501ebbc62e15aa469aa8829d4d0ee61", + "https://deno.land/x/yargs_parser@v20.2.4-deno/build/lib/yargs-parser.js": "453200a7dfbb002e605d8009b7dad30f2b1d93665e046ab89c073a4fe63dfd48", + "https://deno.land/x/yargs_parser@v20.2.4-deno/deno.ts": "ad53c0c82c3982c4fc5be9472384b259e0a32ce1f7ae0f68de7b2445df5642fc" + }, + "workspace": { + "dependencies": [ + "jsr:@std/assert@1", + "jsr:@std/expect@1", + "jsr:@std/testing@1" + ], + "members": { + "bench": { + "dependencies": [ + "jsr:@std/fs@*", + "jsr:@std/path@*", + "npm:@use-gpu/shader@0.12.0", + "npm:diff@*", + "npm:wgsl_reflect@1.0.16" + ] + }, + "bulk-test": { + "dependencies": [ + "jsr:@std/fs@*", + "jsr:@std/path@*", + "npm:diff@*" + ] + }, + "cli": { + "dependencies": [ + "npm:diff@*" + ] + }, + "packager": { + "dependencies": [ + "jsr:@std/fs@*", + "jsr:@std/path@*" + ] + } + } + } +} diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index 3d033b4cd..000000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,13 +0,0 @@ -import eslint from "@eslint/js"; -import tseslint from "typescript-eslint"; - -export default tseslint.config( - eslint.configs.recommended, - ...tseslint.configs.recommended, - { ignores: ["**/dist/", "**/bin/"] }, - { rules: { - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unused-vars": "warn", - } - }, -); diff --git a/packages/linker/src/AbstractElems.ts b/linker/AbstractElems.ts similarity index 96% rename from packages/linker/src/AbstractElems.ts rename to linker/AbstractElems.ts index 364064569..f916658d2 100644 --- a/packages/linker/src/AbstractElems.ts +++ b/linker/AbstractElems.ts @@ -1,7 +1,7 @@ /** Structures for the abstract syntax tree constructed by the parser. */ -import { ImportTree } from "./ImportTree.js"; -import { FoundRef } from "./TraverseRefs.js"; +import { ImportTree } from "./ImportTree.ts"; +import { FoundRef } from "./TraverseRefs.ts"; export type AbstractElem = | AliasElem diff --git a/packages/linker/src/GleamImport.ts b/linker/GleamImport.ts similarity index 95% rename from packages/linker/src/GleamImport.ts rename to linker/GleamImport.ts index 42a548b43..4d31de838 100644 --- a/packages/linker/src/GleamImport.ts +++ b/linker/GleamImport.ts @@ -17,17 +17,17 @@ import { tracing, withSepPlus, withTags, -} from "mini-parse"; -import { TreeImportElem } from "./AbstractElems.js"; +} from "@wesl/mini-parse"; +import { TreeImportElem } from "./AbstractElems.ts"; import { ImportTree, PathSegment, SegmentList, SimpleSegment, Wildcard, -} from "./ImportTree.js"; -import { digits, eol, word } from "./MatchWgslD.js"; -import { makeElem } from "./ParseSupport.js"; +} from "./ImportTree.ts"; +import { digits, eol, word } from "./MatchWgslD.ts"; +import { makeElem } from "./ParseSupport.ts"; const gleamImportSymbolSet = "/ { } , ( ) .. . * ;"; const gleamImportSymbol = matchOneOf(gleamImportSymbolSet); diff --git a/packages/linker/src/ImportResolutionMap.ts b/linker/ImportResolutionMap.ts similarity index 91% rename from packages/linker/src/ImportResolutionMap.ts rename to linker/ImportResolutionMap.ts index c0e236308..80ce9ad40 100644 --- a/packages/linker/src/ImportResolutionMap.ts +++ b/linker/ImportResolutionMap.ts @@ -1,15 +1,15 @@ -import { TreeImportElem } from "./AbstractElems.js"; +import { ExportElem, TreeImportElem } from "./AbstractElems.ts"; import { ImportTree, PathSegment, SegmentList, SimpleSegment, Wildcard, -} from "./ImportTree.js"; -import { ModuleExport } from "./ModuleRegistry.js"; -import { ParsedRegistry } from "./ParsedRegistry.js"; -import { TextModule } from "./ParseModule.js"; -import { dirname, normalize } from "./PathUtil.js"; +} from "./ImportTree.ts"; +import { ModuleExport } from "./ModuleRegistry.ts"; +import { ParsedRegistry } from "./ParsedRegistry.ts"; +import { TextModule } from "./ParseModule.ts"; +import { dirname, normalize } from "./PathUtil.ts"; /** * Maps to resolve imports to exports. @@ -53,10 +53,7 @@ class ExportPathToExport { } class ImportToExportPath { - constructor( - public importPath: string[], - public exportPath: string, - ) {} + constructor(public importPath: string[], public exportPath: string) {} } /** Expand all imports paths to their corresponding export paths @@ -71,14 +68,14 @@ export function importResolutionMap( imports: TreeImportElem[], registry: ParsedRegistry, ): ResolveMap { - const resolveEntries = imports.flatMap(imp => - resolveTreeImport(importingModule, imp, registry), + const resolveEntries = imports.flatMap((imp) => + resolveTreeImport(importingModule, imp, registry) ); const exportEntries: [string, ExportPathToExport][] = []; const pathEntries: [string[], string][] = []; - resolveEntries.forEach(e => { + resolveEntries.forEach((e) => { if (e instanceof ExportPathToExport) { exportEntries.push([e.exportPath, e]); } else { @@ -124,7 +121,7 @@ function resolveTreeImport( } if (segment instanceof SegmentList) { // resolve path with each element in the list - return segment.list.flatMap(elem => { + return segment.list.flatMap((elem) => { const rPath = [elem, ...rest]; return recursiveResolve(resolvedImportPath, resolvedExportPath, rPath); }); @@ -155,7 +152,7 @@ function resolveTreeImport( resolvedImportPath: string[], resolvedExportPath: string[], ): ResolvedEntry[] { - return m.exports.flatMap(exp => { + return m.exports.flatMap((exp) => { const expPath = [...resolvedExportPath, exp.ref.name]; const impPath = [...resolvedImportPath, exp.ref.name]; const modExp = { kind: m.kind, module: m, exp } as ModuleExport; diff --git a/packages/linker/src/ImportTree.ts b/linker/ImportTree.ts similarity index 100% rename from packages/linker/src/ImportTree.ts rename to linker/ImportTree.ts diff --git a/packages/linker/Internals.md b/linker/Internals.md similarity index 100% rename from packages/linker/Internals.md rename to linker/Internals.md diff --git a/packages/linker/src/LinkedElems.ts b/linker/LinkedElems.ts similarity index 60% rename from packages/linker/src/LinkedElems.ts rename to linker/LinkedElems.ts index 324e2550d..c2182ce59 100644 --- a/packages/linker/src/LinkedElems.ts +++ b/linker/LinkedElems.ts @@ -1,5 +1,5 @@ -import { CallElem, FnElem } from "./AbstractElems.js"; -import { TextModule } from "./ParseModule.js"; +import { CallElem, FnElem } from "./AbstractElems.ts"; +import { TextModule } from "./ParseModule.ts"; /** this is starting to look a lot like a FoundRef */ export interface LinkedCall { diff --git a/packages/linker/src/Linker.ts b/linker/Linker.ts similarity index 96% rename from packages/linker/src/Linker.ts rename to linker/Linker.ts index c7056025d..55205b333 100644 --- a/packages/linker/src/Linker.ts +++ b/linker/Linker.ts @@ -6,12 +6,12 @@ import { StructMemberElem, TypeRefElem, VarElem, -} from "./AbstractElems.js"; -import { RegistryParams } from "./ModuleRegistry.js"; -import { ParsedRegistry } from "./ParsedRegistry.js"; -import { TextModule } from "./ParseModule.js"; -import { SliceReplace, sliceReplace } from "./Slicer.js"; -import { FoundRef, TextRef, traverseRefs } from "./TraverseRefs.js"; +} from "./AbstractElems.ts"; +import { RegistryParams } from "./ModuleRegistry.ts"; +import { ParsedRegistry } from "./ParsedRegistry.ts"; +import { TextModule } from "./ParseModule.ts"; +import { SliceReplace, sliceReplace } from "./Slicer.ts"; +import { FoundRef, TextRef, traverseRefs } from "./TraverseRefs.ts"; type DirectiveRef = { kind: "dir"; diff --git a/packages/linker/src/LinkerLogging.ts b/linker/LinkerLogging.ts similarity index 73% rename from packages/linker/src/LinkerLogging.ts rename to linker/LinkerLogging.ts index 9ee880ea2..34a290317 100644 --- a/packages/linker/src/LinkerLogging.ts +++ b/linker/LinkerLogging.ts @@ -1,7 +1,7 @@ -import { srcLog } from "mini-parse"; -import { AbstractElem } from "./AbstractElems.js"; -import { TextModule } from "./ParseModule.js"; -import { FoundRef } from "./TraverseRefs.js"; +import { logger, srcLog } from "@wesl/mini-parse"; +import { AbstractElem } from "./AbstractElems.ts"; +import { TextModule } from "./ParseModule.ts"; +import { FoundRef } from "./TraverseRefs.ts"; export function refLog(ref: FoundRef, ...msgs: any[]): void { moduleLog(ref.expMod, [ref.elem.start, ref.elem.end], ...msgs); diff --git a/packages/linker/src/LogResolveMap.ts b/linker/LogResolveMap.ts similarity index 93% rename from packages/linker/src/LogResolveMap.ts rename to linker/LogResolveMap.ts index e35f1c426..0edc31055 100644 --- a/packages/linker/src/LogResolveMap.ts +++ b/linker/LogResolveMap.ts @@ -1,4 +1,4 @@ -import { ResolveMap } from "./ImportResolutionMap.js"; +import { ResolveMap } from "./ImportResolutionMap.ts"; export function logResolveMap(resolveMap: ResolveMap): void { const pathEntries = pathsToStrings(resolveMap); @@ -19,4 +19,4 @@ export function exportsToStrings(resolveMap: ResolveMap): string[] { const expPath = `${modulePath}/${exp.modExp.exp.ref.name}`; return `${imp} -> ${expPath}`; }); -} \ No newline at end of file +} diff --git a/linker/MatchWgslD.ts b/linker/MatchWgslD.ts new file mode 100644 index 000000000..acb499151 --- /dev/null +++ b/linker/MatchWgslD.ts @@ -0,0 +1,109 @@ +import { matchOneOf, tokenMatcher } from "@wesl/mini-parse"; + +// https://www.w3.org/TR/WGSL/#blankspace-and-line-breaks +/** New lines */ +export const eol = /[\n\v\f\u{0085}\u{2028}\u{2029}]|\r\n?/u; +/** Whitespaces including new lines */ +export const blankspaces = + /[ \t\n\v\f\r\u{0085}\u{200E}\u{200F}\u{2028}\u{2029}]+/u; + +/** Parser simplification: Bigger WGSL symbols consist of individual tokens, sequenced one after another */ +const symbolSet = "& @ / ! [ ] { } : , = ! > < % - . + | ( ) ; * ~ ^ // /* */"; +const symbol = matchOneOf(symbolSet); +const quote = /["']/; + +const longIdent = + /(?:(?:[_\p{XID_Start}][\p{XID_Continue}.:]+)|(?:[\p{XID_Start}]))/u; // identifier that can include module path +export const word = + /(?:(?:[_\p{XID_Start}][\p{XID_Continue}]+)|(?:[\p{XID_Start}]))/u; +export const digits = new RegExp( + // decimal_int_literal + /(?:0[iu]?)|(?:[1-9][0-9]*[iu]?)/.source + + // hex_int_literal + /|(?:0[xX][0-9a-fA-F]+[iu]?)/.source + + // decimal_float_literal + /|(?:0[fh])|(?:[1-9][0-9]*[fh])/.source + + /|(?:[0-9]*\.[0-9]+(?:[eE][+-]?[0-9]+)?[fh]?)/.source + + /|(?:[0-9]+\.[0-9]*(?:[eE][+-]?[0-9]+)?[fh]?)/.source + + /|(?:[0-9]+[eE][+-]?[0-9]+[fh]?)/.source + + // hex_float_literal + /|(?:0[xX][0-9a-fA-F]*\.[0-9a-fA-F]+(?:[pP][+-]?[0-9]+[fh]?)?)/.source + + /|(?:0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*(?:[pP][+-]?[0-9]+[fh]?)?)/.source + + /|(?:0[xX][0-9a-fA-F]+[pP][+-]?[0-9]+[fh]?)/.source, +); + +/** matching tokens at wgsl root level */ +export const mainTokens = tokenMatcher( + { + word, + digits, + symbol, + quote, + ws: blankspaces, + }, + "main", +); + +export const identTokens = tokenMatcher( + { + longIdent, + ws: blankspaces, + symbol, + digits, + quote, + }, + "longIdent", +); + +export const moduleTokens = tokenMatcher( + { + ws: blankspaces, + moduleName: /[a-zA-Z_][\w./:-]*/, + }, + "moduleName", +); + +/** matching tokens at the start of a '//' line comment that might contain #directives */ +export const lineCommentTokens = tokenMatcher( + { + ws: /[ \t]+/, // note ws must be before notEol + notEol: /[^\n]+/, + eol, + }, + "lineComment", +); + +/** matching tokens while parsing directive parameters #export foo(param1, param2) */ +export const argsTokens = tokenMatcher( + { + quote, + relPath: /[.][/\w._-]+/, + arg: /[\w._-]+/, + symbol, + ws: /[ \t]+/, // don't include \n, so we can find eol separately + eol, + }, + "argsTokens", +); + +const treeImportSymbolSet = ":: { } , ( ) _ . ; *"; +const importSymbol = matchOneOf(treeImportSymbolSet); + +export const treeImportTokens = tokenMatcher( + { + quote, + ws: blankspaces, + importSymbol, + word, + digits, + }, + "treeTokens", +); + +export const rootWs = tokenMatcher( + { + blanks: /\s+/, + other: /[^\s]+/, + }, + "rootWs", +); diff --git a/packages/linker/src/ModuleRegistry.ts b/linker/ModuleRegistry.ts similarity index 91% rename from packages/linker/src/ModuleRegistry.ts rename to linker/ModuleRegistry.ts index 751a38038..ebc8aca4a 100644 --- a/packages/linker/src/ModuleRegistry.ts +++ b/linker/ModuleRegistry.ts @@ -1,7 +1,7 @@ -import { ParsedRegistry } from "./ParsedRegistry.js"; -import { TextExport, TextModule } from "./ParseModule.js"; -import { normalize } from "./PathUtil.js"; -import { WgslBundle } from "./WgslBundle.js"; +import { ParsedRegistry } from "./ParsedRegistry.ts"; +import { TextExport, TextModule } from "./ParseModule.ts"; +import { normalize } from "./PathUtil.ts"; +import { WgslBundle } from "./WgslBundle.ts"; /** a single export from a module */ export type ModuleExport = TextModuleExport; diff --git a/packages/linker/src/ParseDirective.ts b/linker/ParseDirective.ts similarity index 61% rename from packages/linker/src/ParseDirective.ts rename to linker/ParseDirective.ts index cc4b4ab71..8bf364402 100644 --- a/packages/linker/src/ParseDirective.ts +++ b/linker/ParseDirective.ts @@ -8,16 +8,14 @@ import { seq, setTraceNames, tokens, - tracing -} from "mini-parse"; -import { gleamImport } from "./GleamImport.js"; + tracing, +} from "@wesl/mini-parse"; +import { gleamImport } from "./GleamImport.ts"; import { argsTokens, - lineCommentTokens, - mainTokens, moduleTokens, -} from "./MatchWgslD.js"; -import { eolf, makeElem } from "./ParseSupport.js"; +} from "./MatchWgslD.ts"; +import { eolf, makeElem } from "./ParseSupport.ts"; /* parse #directive enhancements to wgsl: #export, etc. */ @@ -30,13 +28,12 @@ const fromClause = seq( ); /** #export <(a,b)> EOL */ -export const exportDirective = seq( - or("#export", "export"), - opt(eolf) -).map(r => { - const e = makeElem("export", r, ["args"]); - r.app.state.push(e); -}); +export const exportDirective = seq(or("#export", "export"), opt(eolf)).map( + r => { + const e = makeElem("export", r, ["args"]); + r.app.state.push(e); + }, +); const moduleDirective = seq( or("module", "#module"), @@ -58,23 +55,13 @@ function normalizeModulePath(name: string): string { export const directive = tokens( argsTokens, - seq( - repeat("\n"), - or(exportDirective, gleamImport, moduleDirective), - ), + seq(repeat("\n"), or(exportDirective, gleamImport, moduleDirective)), ); -const skipToEol = tokens(lineCommentTokens, anyThrough(eolf)); - -/** parse a line comment */ -export const lineComment = seq(tokens(mainTokens, "//"), skipToEol); - if (tracing) { setTraceNames({ fromClause, exportDirective, - skipToEol, - lineComment, moduleDirective, directive, }); diff --git a/packages/linker/src/ParseModule.ts b/linker/ParseModule.ts similarity index 95% rename from packages/linker/src/ParseModule.ts rename to linker/ParseModule.ts index d2cd81a4f..92036afe9 100644 --- a/packages/linker/src/ParseModule.ts +++ b/linker/ParseModule.ts @@ -1,4 +1,4 @@ -import { srcLog, SrcMap } from "mini-parse"; +import { srcLog, SrcMap } from "@wesl/mini-parse"; import { AbstractElem, AliasElem, @@ -9,8 +9,8 @@ import { StructElem, TreeImportElem, VarElem, -} from "./AbstractElems.js"; -import { parseWgslD } from "./ParseWgslD.js"; +} from "./AbstractElems.ts"; +import { parseWgslD } from "./ParseWgslD.ts"; /** module with exportable text fragments that are optionally transformed by a templating engine */ export interface TextModule { diff --git a/packages/linker/src/ParseSupport.ts b/linker/ParseSupport.ts similarity index 74% rename from packages/linker/src/ParseSupport.ts rename to linker/ParseSupport.ts index 893de7a45..38e87892f 100644 --- a/packages/linker/src/ParseSupport.ts +++ b/linker/ParseSupport.ts @@ -1,6 +1,7 @@ import { any, anyNot, + anyThrough, disablePreParse, ExtendedResult, kind, @@ -12,19 +13,21 @@ import { resultLog, seq, setTraceName, + tokens, tracing, withSep, -} from "mini-parse"; -import { AbstractElem, AbstractElemBase } from "./AbstractElems.js"; -import { argsTokens, mainTokens } from "./MatchWgslD.js"; -import { lineComment } from "./ParseDirective.js"; +} from "@wesl/mini-parse"; +import type { AbstractElem, AbstractElemBase } from "./AbstractElems.ts"; +import { argsTokens, lineCommentTokens, mainTokens } from "./MatchWgslD.ts"; /* Basic parsing functions for comment handling, eol, etc. */ export const word = kind(mainTokens.word); -export const wordNum = or(word, kind(mainTokens.digits)); +export const literal = or("true", "false", kind(mainTokens.digits)); +/** WGSL combined tokens consist of individual tokens, one after another. */ +export const op = (tokens: string) => seq(...tokens.split("")); -export const unknown = any().map(r => { +export const unknown = any().map((r) => { const { kind, text } = r.value; const deepName = r.ctx._debugNames.join(" > "); @@ -38,20 +41,19 @@ export const blockComment: Parser = seq( req("*/"), ); -export const comment = or(() => lineComment, blockComment).trace({ - hide: true, -}); - export const eolf: Parser = disablePreParse( makeEolf(argsTokens, argsTokens.ws), ); -/** ( a1, b1* ) with optional comments */ -export const wordNumArgs: Parser = seq( - "(", - withSep(",", wordNum), - req(")"), -).map(r => r.value[1]); +const skipToEol = tokens(lineCommentTokens, anyThrough(eolf)); + +/** parse a line comment */ +export const lineComment = seq(tokens(mainTokens, "//"), skipToEol); + +export const comment = or(() => lineComment, blockComment); +// .trace({ +// hide: true, +// }); type ByKind = U extends { kind: T } ? U : never; @@ -89,7 +91,7 @@ function mapIfDefined( array: Partial>, firstElemOnly?: boolean, ): Partial> { - const entries = keys.flatMap(k => { + const entries = keys.flatMap((k) => { const ak = array[k]; const v = firstElemOnly ? ak?.[0] : ak; @@ -103,7 +105,6 @@ if (tracing) { const names: Record> = { skipBlockComment: blockComment, comment, - wordNumArgs, }; Object.entries(names).forEach(([name, parser]) => { diff --git a/linker/ParseWgslD.ts b/linker/ParseWgslD.ts new file mode 100644 index 000000000..4dc0e163b --- /dev/null +++ b/linker/ParseWgslD.ts @@ -0,0 +1,483 @@ +import { + anyNot, + anyThrough, + eof, + ExtendedResult, + kind, + matchingLexer, + opt, + or, + Parser, + ParserContext, + ParserInit, + preParse, + repeat, + repeatPlus, + req, + seq, + setTraceName, + simpleParser, + SrcMap, + tokens, + tracing, + withSep, +} from "@wesl/mini-parse"; +import { AbstractElem, TypeNameElem, TypeRefElem } from "./AbstractElems.ts"; +import { identTokens, mainTokens } from "./MatchWgslD.ts"; +import { directive } from "./ParseDirective.ts"; +import { + comment, + literal, + makeElem, + op, + unknown, + word, +} from "./ParseSupport.ts"; + +/** parser that recognizes key parts of WGSL and also directives like #import */ + +const longIdent = kind(identTokens.longIdent); + +// prettier gets confused if we leave the quoted parens inline so make consts for them here +const lParen = "("; +const rParen = ")"; + +export interface ParseState { + params: Record; // user provided params to templates, code gen and #if directives +} + +// TODO: Check the following +// - translation_unit +// - global_decl +// - global_value_decl +// - global_directive +// - diagnostic_rule_name +// - diagnostic_control + +const attribute = seq( + "@", + req(or( + // These attributes have no arguments + or("compute", "const", "fragment", "invariant", "must_use", "vertex"), + // These attributes have arguments, but the argument doesn't have any identifiers + seq( + or("interpolate", "builtin", "diagnostic"), + req(() => argument_expression_list), // TODO: Throw away the identifiers + ), + // These are normal attributes + seq( + or( + "workgroup_size", + "align", + "binding", + "blend_src", + "group", + "id", + "location", + "size", + ), + req(() => argument_expression_list), + ), + // Everything else is also a normal attribute, it might have an expression list + seq(kind(mainTokens.word), opt(() => argument_expression_list)), + )), +); + +const argument_expression_list = seq( + "(", + withSep(",", () => expression), + req(")"), +); + +const opt_attributes = repeat(attribute); +const possibleTypeRef = Symbol("typeRef"); + +const globalDirectiveOrAssert = seq( + or("diagnostic", "enable", "requires", "const_assert"), + req(anyThrough(";")), +).map((r) => { + const e = makeElem("globalDirective", r); + r.app.state.push(e); +}); + +/** parse an identifier into a TypeNameElem */ +export const typeNameDecl = req(word.tag("name")).map((r) => { + return makeElem("typeName", r, ["name"]) as TypeNameElem; // fix? +}); + +/** parse an identifier into a TypeNameElem */ +export const fnNameDecl = req(word.tag("name"), "missing fn name").map((r) => { + return makeElem("fnName", r, ["name"]); +}); + +/** find possible references to user structs in this type specifier and any templates */ +export const type_specifier: Parser = seq( + tokens(identTokens, longIdent.tag(possibleTypeRef)), + () => opt_template_args, +).map((r) => + r.tags[possibleTypeRef].map((name) => { + const e = makeElem("typeRef", r as ExtendedResult); + e.name = name; + return e as Required; + }) +); + +const optionally_typed_ident = seq( + word, + opt(seq(":", type_specifier.tag("typeRefs"))), +); + +export const structMember = seq( + opt_attributes, + word.tag("name"), + ":", + req(type_specifier.tag("typeRefs")), +).map((r) => { + return makeElem("member", r, ["name", "typeRefs"]); +}); + +export const structDecl = seq( + "struct", + req(typeNameDecl).tag("nameElem"), + req("{"), + withSep(",", structMember).tag("members"), + req("}"), +).map((r) => { + const e = makeElem("struct", r, ["members"]); + const nameElem = r.tags.nameElem[0]; + e.nameElem = nameElem; + e.name = nameElem.name; + r.app.state.push(e); +}); + +/** Also covers func_call_statement.post.ident */ +export const fn_call = seq( + longIdent + .tag("name") + .map((r) => makeElem("call", r, ["name"])) + .tag("calls"), // we collect this in fnDecl, to attach to FnElem + () => opt_template_args, + argument_expression_list, +); + +// prettier-ignore +const fnParam = seq( + opt_attributes, + word, + opt(seq(":", req(type_specifier.tag("typeRefs")))), +); + +const fnParamList = seq(lParen, withSep(",", fnParam), rParen); + +/** Covers variable_decl and the 'var' case in global_decl */ +const variable_decl = seq( + "var", + () => opt_template_args, + optionally_typed_ident, + opt(seq("=", () => expression)), +); + +/** Aka template_elaborated_ident.post.ident */ +const opt_template_args = opt( + seq( + "<", + withSep(",", () => template_arg_expression, { + requireOne: true, + }), + ">", + ).tag("template"), +); + +const primary_expression = or( + literal, + seq( + word.tag("ident"), + opt_template_args, + opt( + argument_expression_list, + ), + ), + seq("(", () => expression, req(")")), +); +const component_or_swizzle = repeatPlus( + or( + seq(".", word), + seq("[", () => expression, req("]")), + ), +); + +/** + * bitwise_expression.post.unary_expression + * & ^ | + * expression + * && || + * relational_expression.post.unary_expression + * > >= < <= != == + * shift_expression.post.unary_expression + * % * / + - << >> + */ +const makeExpressionOperator = (isTemplate: boolean) => { + const allowedOps = ( + "& | ^ << <= < != == % * / + -" + (isTemplate ? "" : " && || >> >= >") + ).split(" ").map(op); + return or(...allowedOps) + .traceName("operator") + .trace({ + shallow: true, + }); +}; +const unary_expression: Parser = or( + seq( + or(..."! & * - ~".split(" ")) + .traceName("unary_op") + .trace({ + shallow: true, + }), + () => unary_expression, + ), + seq(primary_expression, opt(component_or_swizzle)), +); +const makeExpression = (isTemplate: boolean) => { + return seq( + unary_expression, + repeat(seq(makeExpressionOperator(isTemplate), unary_expression)), + ); +}; + +export const expression = makeExpression(false); +const template_arg_expression = makeExpression(true); + +const compound_statement = seq( + opt_attributes, + "{", + repeat(() => statement), + "}", +); + +const for_init = or( + fn_call, + () => variable_or_value_statement, + () => variable_updating_statement, +); + +const for_update = or( + fn_call, + () => variable_updating_statement, +); + +const for_statement = seq( + opt_attributes, + "for", + req(seq("(", opt(for_init), ";", opt(expression), ";", opt(for_update), ")")), +); +const if_statement = seq( + opt_attributes, + "if", + req(seq(expression, compound_statement)), + repeat( + seq("else", "if", req(seq(expression, compound_statement))), + ), + opt(seq("else", req(compound_statement))), +); +const loop_statement = seq( + opt_attributes, + "loop", + opt_attributes, + req(seq( + "{", + repeat(() => statement), + opt( + seq( + "continuing", + opt_attributes, + "{", + repeat(() => statement), + opt(seq("break", "if", expression, ";")), + "}", + ), + ), + "}", + )), +); + +const case_selector = or("default", expression); +const switch_clause = or( + seq( + "case", + withSep(",", case_selector, { requireOne: true }), + opt(":"), + compound_statement, + ), + seq("default", opt(":"), compound_statement), +); + +const switch_body = seq(opt_attributes, "{", repeatPlus(switch_clause), "}"); +const switch_statement = seq(opt_attributes, "switch", expression, switch_body); + +const while_statement = seq( + opt_attributes, + "while", + expression, + compound_statement, +); + +const statement: Parser = or( + for_statement, + if_statement, + loop_statement, + switch_statement, + while_statement, + compound_statement, + seq("break", ";"), + seq("continue", ";"), + seq(";"), + seq("const_assert", expression, ";"), + seq("discard", ";"), + seq("return", opt(expression), ";"), + seq(fn_call, ";"), + seq(() => variable_or_value_statement, ";"), + seq(() => variable_updating_statement, ";"), +); + +const lhs_expression: Parser = or( + seq( + word.tag("ident"), + opt(component_or_swizzle), + ), + seq("(", () => lhs_expression, ")", opt(component_or_swizzle)), + seq("&", () => lhs_expression), + seq("*", () => lhs_expression), +); + +const variable_or_value_statement = or( + // Also covers the = expression case + variable_decl, + seq("const", optionally_typed_ident, "=", expression), + seq("let", optionally_typed_ident, "=", expression), +); +const variable_updating_statement = or( + seq( + lhs_expression, + or( + "=", + op("<<="), + op(">>="), + op("%="), + op("&="), + op("*="), + op("+="), + op("-="), + op("/="), + op("^="), + op("|="), + ), + expression, + ), + seq(lhs_expression, or(op("++"), op("--"))), + seq("_", "=", expression), +); + +export const fn_decl = seq( + opt_attributes, + "fn", + req(fnNameDecl).tag("nameElem"), + req(fnParamList), + opt(seq("->", opt_attributes, type_specifier.tag("typeRefs"))), + req(compound_statement), +).trace().map((r) => { + const e = makeElem("fn", r); + const nameElem = r.tags.nameElem[0]; + e.nameElem = nameElem as Required; + e.name = nameElem.name; + e.calls = r.tags.calls || []; + e.typeRefs = r.tags.typeRefs?.flat() || []; + r.app.state.push(e); +}); + +export const globalVar = seq( + opt_attributes, + or("const", "override", "var"), + opt_template_args, + word.tag("name"), + opt(seq(":", req(type_specifier.tag("typeRefs")))), + req(anyThrough(";")), +).map((r) => { + const e = makeElem("var", r, ["name"]); + e.typeRefs = r.tags.typeRefs?.flat() || []; + r.app.state.push(e); +}); + +export const globalAlias = seq( + "alias", + req(word.tag("name")), + req("="), + req(type_specifier).tag("typeRefs"), + req(";"), +).map((r) => { + const e = makeElem("alias", r, ["name", "typeRefs"]); + r.app.state.push(e); +}); + +const globalDecl = or(fn_decl, globalVar, globalAlias, structDecl, ";"); + +const rootDecl = or(globalDirectiveOrAssert, globalDecl, directive, unknown); + +const root = preParse(comment, seq(repeat(rootDecl), eof())); + +export function parseWgslD( + src: string, + srcMap?: SrcMap, + params: Record = {}, + maxParseCount: number | undefined = undefined, + grammar = root, +): AbstractElem[] { + const lexer = matchingLexer(src, mainTokens); + const state: AbstractElem[] = []; + const context: ParseState = { params }; + const app = { + context, + state, + }; + const init: ParserInit = { + lexer, + app, + srcMap, + maxParseCount, + }; + + grammar.parse(init); + + return app.state; +} + +if (tracing) { + const names: Record> = { + globalDirectiveOrAssert, + type_specifier, + structMember, + structDecl, + fn_call, + fnParam, + fnParamList, + opt_template_args, + primary_expression, + component_or_swizzle, + expression, + statement, + compound_statement, + case_selector, + switch_clause, + switch_body, + switch_statement, + fn_decl, + globalVar, + globalAlias, + globalDecl, + rootDecl, + root, + }; + + Object.entries(names).forEach(([name, parser]) => { + setTraceName(parser, name); + }); +} diff --git a/packages/linker/src/ParsedRegistry.ts b/linker/ParsedRegistry.ts similarity index 94% rename from packages/linker/src/ParsedRegistry.ts rename to linker/ParsedRegistry.ts index 5cf2fe319..0671b8102 100644 --- a/packages/linker/src/ParsedRegistry.ts +++ b/linker/ParsedRegistry.ts @@ -1,14 +1,14 @@ -import { TreeImportElem } from "./AbstractElems.js"; -import { importResolutionMap, ResolveMap } from "./ImportResolutionMap.js"; -import { linkWgslModule } from "./Linker.js"; +import { TreeImportElem } from "./AbstractElems.ts"; +import { importResolutionMap, ResolveMap } from "./ImportResolutionMap.ts"; +import { linkWgslModule } from "./Linker.ts"; import { ModuleExport, ModuleRegistry, relativeToAbsolute, TextModuleExport, -} from "./ModuleRegistry.js"; -import { parseModule, TextExport, TextModule } from "./ParseModule.js"; -import { dirname, normalize, noSuffix } from "./PathUtil.js"; +} from "./ModuleRegistry.ts"; +import { parseModule, TextExport, TextModule } from "./ParseModule.ts"; +import { dirname, normalize, noSuffix } from "./PathUtil.ts"; /** parse wgsl files and provided indexed access to modules and exports */ export class ParsedRegistry { diff --git a/packages/linker/src/PathUtil.ts b/linker/PathUtil.ts similarity index 100% rename from packages/linker/src/PathUtil.ts rename to linker/PathUtil.ts diff --git a/packages/linker/src/RefDebug.ts b/linker/RefDebug.ts similarity index 85% rename from packages/linker/src/RefDebug.ts rename to linker/RefDebug.ts index fa9c7d4f0..817f65951 100644 --- a/packages/linker/src/RefDebug.ts +++ b/linker/RefDebug.ts @@ -1,11 +1,11 @@ -import { dlog } from "berry-pretty"; -import { AbstractElem, CallElem, FnElem } from "./AbstractElems.js"; -import { FoundRef, TextRef } from "./TraverseRefs.js"; +import { FoundRef, TextRef } from "./TraverseRefs.ts"; +import { AbstractElem, CallElem, FnElem } from "./AbstractElems.ts"; +import { FoundRef, TextRef } from "./TraverseRefs.ts"; export function printRef(r: FoundRef, msg = ""): void { const { kind, elem, rename } = r as TextRef; const renameFields = rename ? { rename } : {}; - dlog( + console.log( msg, { kind, diff --git a/packages/linker/src/ResolveImport.ts b/linker/ResolveImport.ts similarity index 89% rename from packages/linker/src/ResolveImport.ts rename to linker/ResolveImport.ts index 9f8d02903..e2679e2a9 100644 --- a/packages/linker/src/ResolveImport.ts +++ b/linker/ResolveImport.ts @@ -1,7 +1,7 @@ -import { ResolveMap } from "./ImportResolutionMap.js"; -import { ModuleExport } from "./ModuleRegistry.js"; -import { StringPairs } from "./TraverseRefs.js"; -import { overlapTail } from "./Util.js"; +import { ResolveMap } from "./ImportResolutionMap.ts"; +import { ModuleExport } from "./ModuleRegistry.ts"; +import { StringPairs } from "./TraverseRefs.ts"; +import { overlapTail } from "./Util.ts"; export interface ResolvedImport { modExp: ModuleExport; diff --git a/packages/linker/src/Slicer.ts b/linker/Slicer.ts similarity index 97% rename from packages/linker/src/Slicer.ts rename to linker/Slicer.ts index a38373dbf..b30992e8e 100644 --- a/packages/linker/src/Slicer.ts +++ b/linker/Slicer.ts @@ -1,5 +1,5 @@ -import { SrcMap, SrcMapEntry } from "mini-parse"; -import { last, scan } from "./Util.js"; +import { SrcMap, SrcMapEntry } from "@wesl/mini-parse"; +import { last, scan } from "./Util.ts"; /** specify a start,end portion of a string to be replaced */ export interface SliceReplace { diff --git a/packages/linker/src/TraverseRefs.ts b/linker/TraverseRefs.ts similarity index 96% rename from packages/linker/src/TraverseRefs.ts rename to linker/TraverseRefs.ts index 98beeba3f..fdf597c12 100644 --- a/packages/linker/src/TraverseRefs.ts +++ b/linker/TraverseRefs.ts @@ -7,13 +7,13 @@ import { TreeImportElem, TypeRefElem, VarElem, -} from "./AbstractElems.js"; -import { refFullName } from "./Linker.js"; -import { moduleLog } from "./LinkerLogging.js"; -import { ParsedRegistry } from "./ParsedRegistry.js"; -import { TextExport, TextModule } from "./ParseModule.js"; -import { resolveImport } from "./ResolveImport.js"; -import { groupBy, last } from "./Util.js"; +} from "./AbstractElems.ts"; +import { refFullName } from "./Linker.ts"; +import { moduleLog } from "./LinkerLogging.ts"; +import { ParsedRegistry } from "./ParsedRegistry.ts"; +import { TextExport, TextModule } from "./ParseModule.ts"; +import { resolveImport } from "./ResolveImport.ts"; +import { groupBy, last } from "./Util.ts"; /** * A wrapper around a wgsl element targeted for inclusion in the link diff --git a/packages/linker/src/Util.ts b/linker/Util.ts similarity index 100% rename from packages/linker/src/Util.ts rename to linker/Util.ts diff --git a/packages/linker/src/WgslBundle.ts b/linker/WgslBundle.ts similarity index 100% rename from packages/linker/src/WgslBundle.ts rename to linker/WgslBundle.ts diff --git a/linker/deno.json b/linker/deno.json new file mode 100644 index 000000000..3ffa30331 --- /dev/null +++ b/linker/deno.json @@ -0,0 +1,10 @@ +{ + "name": "@wesl/linker", + "version": "0.1.0", + "exports": "./mod.ts", + "tasks": { + "dev": "deno test --allow-read --watch" + }, + "license": "MIT", + "imports": {} +} \ No newline at end of file diff --git a/linker/mod.ts b/linker/mod.ts new file mode 100644 index 000000000..275abb167 --- /dev/null +++ b/linker/mod.ts @@ -0,0 +1,6 @@ +export * from "./Linker.ts"; +export * from "./ModuleRegistry.ts"; +export * from "./ParseWgslD.ts"; +export * from "./PathUtil.ts"; +export * from "./Util.ts"; +export * from "./WgslBundle.ts"; diff --git a/linker/templates/index.ts b/linker/templates/index.ts new file mode 100644 index 000000000..a43f7297b --- /dev/null +++ b/linker/templates/index.ts @@ -0,0 +1 @@ +export * from "./SimpleTemplate.ts"; diff --git a/linker/test/GleamImport.test.ts b/linker/test/GleamImport.test.ts new file mode 100644 index 000000000..808a2d7a8 --- /dev/null +++ b/linker/test/GleamImport.test.ts @@ -0,0 +1,41 @@ +import { testParse, TestParseResult } from "@wesl/mini-parse/test-util"; +import { expect, test } from "vitest"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { gleamImport } from "../GleamImport.ts"; + +function expectParses(ctx: Deno.TestContext): TestParseResult { + const result = testParse(gleamImport, ctx.name); + expect(result.parsed).not.toBeNull(); + return result; +} +/* ------ success cases ------- */ + +test("import ./foo/bar;", (ctx) => { + const result = expectParses(ctx); + expect(result.position).toBe(ctx.name.length); // consume semicolon (so that linking will remove it) +}); + +test("import foo-bar/boo", (ctx) => { + expectParses(ctx); +}); + +/** ----- extraction tests ----- */ +test("import foo/bar", async (ctx) => { + const { appState } = expectParses(ctx); + await assertSnapshot(ctx, appState); +}); + +test("import foo/* as b", async (ctx) => { + const { appState } = expectParses(ctx); + await assertSnapshot(ctx, appState); +}); + +test(`import a/{ b, c/{d, e}, f/* }`, async (ctx) => { + const { appState } = expectParses(ctx); + await assertSnapshot(ctx, appState); +}); + +test("import ./foo/bar", async (ctx) => { + const { appState } = expectParses(ctx); + await assertSnapshot(ctx, appState); +}); diff --git a/linker/test/ImportCases.test.ts b/linker/test/ImportCases.test.ts new file mode 100644 index 000000000..c1dd83a1e --- /dev/null +++ b/linker/test/ImportCases.test.ts @@ -0,0 +1,301 @@ +import { expect, test } from "vitest"; +import { importCases } from "wesl-testsuite"; + +import { ModuleRegistry } from "../ModuleRegistry.ts"; +import { trimSrc } from "./shared/StringUtil.ts"; + +interface LinkExpectation { + includes?: string[]; + excludes?: string[]; + linked?: string; +} + +// wgsl example src, indexed by name +const examplesByName = new Map(importCases.map((t) => [t.name, t.src])); +const testNameSet = new Set(); + +linkTest("import ./bar/foo", { + linked: ` + fn main() { + foo(); + } + + fn foo() { } + `, +}); + +linkTest("main has other root elements", { + linked: ` + struct Uniforms { + a: u32 + } + + @group(0) @binding(0) var u: Uniforms; + + fn main() { } + `, +}); + +linkTest("import foo as bar", { + linked: ` + fn main() { + bar(); + } + + fn bar() { /* fooImpl */ } + `, +}); + +linkTest("import twice doesn't get two copies", { + linked: ` + fn main() { + foo(); + bar(); + } + + fn foo() { /* fooImpl */ } + + fn bar() { foo(); } + `, +}); + +linkTest("imported fn calls support fn with root conflict", { + linked: ` + fn main() { foo(); } + + fn conflicted() { } + + fn foo() { + conflicted0(0); + conflicted0(1); + } + + fn conflicted0(a:i32) {} + `, +}); + +linkTest("import twice with two as names", { + linked: ` + fn main() { bar(); bar(); } + + fn bar() { } + `, +}); + +linkTest("import transitive conflicts with main", { + linked: ` + fn main() { + mid(); + } + + fn grand() { + /* main impl */ + } + + fn mid() { grand0(); } + + fn grand0() { /* grandImpl */ } + `, +}); + +linkTest("multiple exports from the same module", { + linked: ` + fn main() { + foo(); + bar(); + } + + fn foo() { } + + fn bar() { } + `, +}); + +linkTest("import and resolve conflicting support function", { + linked: ` + fn support() { + bar(); + } + + fn bar() { + support0(); + } + + fn support0() { } + `, +}); + +linkTest("import support fn that references another import", { + linked: ` + fn support() { + foo(); + } + + fn foo() { + support0(); + bar(); + } + + fn support0() { } + + fn bar() { + support1(); + } + + fn support1() { } + `, +}); + +linkTest("import support fn from two exports", { + linked: ` + fn main() { + foo(); + bar(); + } + + fn foo() { + support(); + } + + fn bar() { + support(); + } + + fn support() { } + `, +}); + +linkTest("import a struct", { + linked: ` + fn main() { + let a = AStruct(1u); + } + + struct AStruct { + x: u32 + } + `, +}); + +linkTest("import fn with support struct constructor", { + linked: ` + fn main() { + let ze = elemOne(); + } + + fn elemOne() -> Elem { + return Elem(1u); + } + + struct Elem { + sum: u32 + } + `, +}); + +linkTest("import a transitive struct", { + linked: ` + struct SrcStruct { + a: AStruct + } + + struct AStruct { + s: BStruct + } + + struct BStruct { + x: u32 + } + `, +}); + +linkTest("'import as' a struct", { + linked: ` + fn foo (a: AA) { } + + struct AA { + x: u32 + } + `, +}); + +linkTest("import a struct with name conflicting support struct", { + linked: ` + struct Base { + b: i32 + } + + fn foo() -> AStruct {let a:AStruct; return a;} + + struct AStruct { + x: Base0 + } + + struct Base0 { + x: u32 + } + `, +}); + +linkTest("copy alias to output", { + linked: ` + alias MyType = u32; + `, +}); + +linkTest("copy diagnostics to output", { + linked: ` + diagnostic(off,derivative_uniformity); + `, +}); + +function linkTest(name: string, expectation: LinkExpectation): void { + testNameSet.add(name); + test(name, () => { + const exampleSrc = examplesByName.get(name); + if (!exampleSrc) { + throw new Error(`Skipping test "${name}"\nNo example found.`); + } + const srcs = Object.entries(exampleSrc).map(([name, wgsl]) => { + const trimmedSrc = trimSrc(wgsl); + return [name, trimmedSrc] as [string, string]; + }); + const main = srcs[0][0]; + const wgsl = Object.fromEntries(srcs); + const registry = new ModuleRegistry({ wgsl }); + const result = registry.link(main); + + const { linked, includes, excludes } = expectation; + + if (linked !== undefined) { + const expectTrimmed = trimSrc(linked); + const resultTrimmed = trimSrc(result); + if (resultTrimmed !== expectTrimmed) { + const expectLines = expectTrimmed.split("\n"); + const resultLines = result.split("\n"); + expectLines.forEach((line, i) => { + expect(resultLines[i]).toBe(line); + }); + } + } + if (includes !== undefined) { + includes.forEach((inc) => { + expect(result).toContain(inc); + }); + } + if (excludes !== undefined) { + excludes.forEach((exc) => { + expect(result).not.toContain(exc); + }); + } + }); +} + +(() => { + const cases = importCases.map((c) => c.name); + const missing = cases.filter((name) => !testNameSet.has(name)); + if (missing.length > 0) { + console.error("Missing tests for cases:", missing); + expect("missing test: " + missing.toString()).toBe(""); + } +}); diff --git a/packages/linker/src/test/ImportResolutionMap.test.ts b/linker/test/ImportResolutionMap.test.ts similarity index 89% rename from packages/linker/src/test/ImportResolutionMap.test.ts rename to linker/test/ImportResolutionMap.test.ts index 92df16023..3e44ef0ea 100644 --- a/packages/linker/src/test/ImportResolutionMap.test.ts +++ b/linker/test/ImportResolutionMap.test.ts @@ -1,12 +1,12 @@ import { expect, test } from "vitest"; -import { importResolutionMap } from "../ImportResolutionMap.js"; +import { importResolutionMap } from "../ImportResolutionMap.ts"; import { exportsToStrings, logResolveMap, pathsToStrings, -} from "../LogResolveMap.js"; -import { ModuleRegistry } from "../ModuleRegistry.js"; -import { TextExport } from "../ParseModule.js"; +} from "../LogResolveMap.ts"; +import { ModuleRegistry } from "../ModuleRegistry.ts"; +import { TextExport } from "../ParseModule.ts"; test("simple tree", () => { const registry = new ModuleRegistry({ @@ -69,7 +69,7 @@ test("tree with path segment list", () => { }); // TODO fixme wildcards -test.skip("tree with trailing wildcard", () => { +test.ignore("tree with trailing wildcard", () => { const registry = new ModuleRegistry({ wgsl: { "main.wgsl": ` @@ -97,4 +97,4 @@ test.skip("tree with trailing wildcard", () => { // ]); }); -test.skip("tree with segment list of trees"); +test.ignore("tree with segment list of trees", () => {}); diff --git a/packages/linker/src/test/ImportSyntaxCases.test.ts b/linker/test/ImportSyntaxCases.test.ts similarity index 70% rename from packages/linker/src/test/ImportSyntaxCases.test.ts rename to linker/test/ImportSyntaxCases.test.ts index 711744f62..9b041d692 100644 --- a/packages/linker/src/test/ImportSyntaxCases.test.ts +++ b/linker/test/ImportSyntaxCases.test.ts @@ -1,17 +1,16 @@ -import { testParse, TestParseResult } from "mini-parse/test-util"; +import { testParse } from "@wesl/mini-parse/test-util"; import { expect, test } from "vitest"; import { importSyntaxCases } from "wesl-testsuite"; -import { gleamImport } from "../GleamImport.js"; +import { gleamImport } from "../GleamImport.ts"; function expectParseFail(src: string): void { const result = testParse(gleamImport, src); expect(result.parsed).toBeNull(); } -function expectParses(src: string): TestParseResult { +function expectParses(src: string) { const result = testParse(gleamImport, src); expect(result.parsed).not.toBeNull(); - return result; } importSyntaxCases.forEach(c => { diff --git a/packages/linker/src/test/Linker.test.ts b/linker/test/Linker.test.ts similarity index 89% rename from packages/linker/src/test/Linker.test.ts rename to linker/test/Linker.test.ts index 3e1aa79c8..a9977c15e 100644 --- a/packages/linker/src/test/Linker.test.ts +++ b/linker/test/Linker.test.ts @@ -1,6 +1,8 @@ +import { _withBaseLogger } from "@wesl/mini-parse"; +import { logCatch } from "@wesl/mini-parse/test-util"; import { expect, test } from "vitest"; -import { ModuleRegistry } from "../ModuleRegistry.js"; -import { expectNoLog, linkTest, linkTestOpts } from "./TestUtil.js"; +import { ModuleRegistry } from "../ModuleRegistry.ts"; +import { expectNoLog, linkTest, linkTestOpts } from "./TestUtil.ts"; /* --- these tests rely on features not yet portable in wesl --- */ diff --git a/packages/linker/src/test/MatchWgslD.test.ts b/linker/test/MatchWgslD.test.ts similarity index 56% rename from packages/linker/src/test/MatchWgslD.test.ts rename to linker/test/MatchWgslD.test.ts index f868d2705..9fcfba33b 100644 --- a/packages/linker/src/test/MatchWgslD.test.ts +++ b/linker/test/MatchWgslD.test.ts @@ -1,16 +1,16 @@ -import { matchingLexer } from "mini-parse"; +import { matchingLexer } from "@wesl/mini-parse"; import { expect, test } from "vitest"; -import { mainTokens } from "../MatchWgslD.js"; +import { mainTokens } from "../MatchWgslD.ts"; -test("lex #import foo", () => { +test.ignore("lex #import foo", () => { const lexer = matchingLexer(`#import foo`, mainTokens); const tokens = [1, 2].map(lexer.next); - expect(tokens.map(t => t?.kind)).toEqual(["directive", "word"]); + expect(tokens.map((t) => t?.kind)).toEqual(["directive", "word"]); }); test("/* foo */", () => { const lexer = matchingLexer(`/* foo */`, mainTokens); const tokens = [1, 2, 3].map(lexer.next); - const tokenKinds = tokens.map(t => t?.kind); + const tokenKinds = tokens.map((t) => t?.kind); expect(tokenKinds).toEqual(["symbol", "word", "symbol"]); }); diff --git a/packages/linker/src/test/ModuleRegistry.test.ts b/linker/test/ModuleRegistry.test.ts similarity index 93% rename from packages/linker/src/test/ModuleRegistry.test.ts rename to linker/test/ModuleRegistry.test.ts index 7ff831cc6..81db46c73 100644 --- a/packages/linker/src/test/ModuleRegistry.test.ts +++ b/linker/test/ModuleRegistry.test.ts @@ -1,5 +1,5 @@ import { expect, test } from "vitest"; -import { ModuleRegistry } from "../ModuleRegistry.js"; +import { ModuleRegistry } from "../ModuleRegistry.ts"; test("findTextModule", () => { const registry = new ModuleRegistry({ diff --git a/packages/linker/src/test/ParseComments.test.ts b/linker/test/ParseComments.test.ts similarity index 59% rename from packages/linker/src/test/ParseComments.test.ts rename to linker/test/ParseComments.test.ts index fe24db8f6..00b53eb92 100644 --- a/packages/linker/src/test/ParseComments.test.ts +++ b/linker/test/ParseComments.test.ts @@ -1,11 +1,11 @@ -import { preParse } from "mini-parse"; -import { expectNoLogErr } from "mini-parse/test-util"; +import { preParse } from "@wesl/mini-parse"; +import { expectNoLogErr } from "@wesl/mini-parse/test-util"; import { expect, test } from "vitest"; -import { lineComment } from "../ParseDirective.js"; -import { blockComment, comment, wordNumArgs } from "../ParseSupport.js"; -import { parseWgslD } from "../ParseWgslD.js"; -import { testAppParse } from "./TestUtil.js"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { blockComment, comment, lineComment } from "../ParseSupport.ts"; +import { parseWgslD } from "../ParseWgslD.ts"; +import { testAppParse } from "./TestUtil.ts"; test("lineComment parse // foo bar", () => { const src = "// foo bar"; @@ -19,11 +19,11 @@ test("lineComment parse // foo bar \\n", () => { expect(position).toBe(src.length); }); -test("blockComment parses /* comment */", () => { +test("blockComment parses /* comment */", async (ctx) => { const src = "/* comment */"; - expectNoLogErr(() => { + await expectNoLogErr(async () => { const { parsed } = testAppParse(blockComment, src); - expect(parsed).toMatchSnapshot(); + await assertSnapshot(ctx, parsed); }); }); @@ -34,15 +34,15 @@ test("skipBlockComment parses nested comment", () => { }); }); -test("parse fn with line comment", () => { +test("parse fn with line comment", async (ctx) => { const src = ` fn binaryOp() { // binOpImpl }`; const parsed = parseWgslD(src); - expect(parsed).toMatchSnapshot(); + await assertSnapshot(ctx, parsed); }); -test("wordNumArgs parses (a, b, 1) with line comments everywhere", () => { +test.ignore("wordNumArgs parses (a, b, 1) with line comments everywhere", async (ctx) => { const src = `( // aah a, @@ -52,8 +52,8 @@ test("wordNumArgs parses (a, b, 1) with line comments everywhere", () => { 1 // satsified )`; - const { parsed } = testAppParse(preParse(comment, wordNumArgs), src); - expect(parsed?.value).toMatchSnapshot(); + // const { parsed } = testAppParse(preParse(comment, ), src); + // await assertSnapshot(ctx, parsed?.value); }); test("parse empty line comment", () => { @@ -63,7 +63,7 @@ test("parse empty line comment", () => { expectNoLogErr(() => parseWgslD(src)); }); -test.skip("parse line comment with #replace", () => { +test.ignore("parse line comment with #replace", () => { const src = ` const workgroupThreads= 4; // #replace 4=workgroupThreads `; diff --git a/packages/linker/src/test/ParseDirectives.test.ts b/linker/test/ParseDirectives.test.ts similarity index 52% rename from packages/linker/src/test/ParseDirectives.test.ts rename to linker/test/ParseDirectives.test.ts index e0a86bd18..5f4488801 100644 --- a/packages/linker/src/test/ParseDirectives.test.ts +++ b/linker/test/ParseDirectives.test.ts @@ -1,23 +1,24 @@ import { expect, test } from "vitest"; -import { ModuleElem, TreeImportElem } from "../AbstractElems.js"; -import { treeToString } from "../ImportTree.js"; -import { directive } from "../ParseDirective.js"; -import { parseWgslD } from "../ParseWgslD.js"; -import { testAppParse } from "./TestUtil.js"; +import { ModuleElem, TreeImportElem } from "../AbstractElems.ts"; +import { treeToString } from "../ImportTree.ts"; +import { directive } from "../ParseDirective.ts"; +import { parseWgslD } from "../ParseWgslD.ts"; +import { testAppParse } from "./TestUtil.ts"; +import { assertSnapshot } from "@std/testing/snapshot"; -test("directive parses #export", () => { +test.ignore("directive parses #export", () => { const { appState } = testAppParse(directive, "#export"); expect(appState[0].kind).toBe("export"); }); -test("parse #export", () => { +test.ignore("parse #export", () => { const parsed = parseWgslD("#export"); expect(parsed[0].kind).toBe("export"); }); -test("parse import foo/bar", () => { +test("parse import foo/bar", async (ctx) => { const parsed = parseWgslD("import foo/bar"); - expect(parsed).toMatchSnapshot(); + await assertSnapshot(ctx, parsed); }); test("parse module foo.bar.ca", () => { @@ -27,41 +28,41 @@ test("parse module foo.bar.ca", () => { expect((appState[0] as ModuleElem).name).toBe("foo.bar.ca"); }); -test("module foo.bar.ca", ctx => { - const appState = parseWgslD(ctx.task.name); +test("module foo.bar.ca", (ctx) => { + const appState = parseWgslD(ctx.name); expect(appState[0].kind).toBe("module"); expect((appState[0] as ModuleElem).name).toBe("foo.bar.ca"); }); -test("module foo::bar::ba", ctx => { - const appState = parseWgslD(ctx.task.name); +test("module foo::bar::ba", (ctx) => { + const appState = parseWgslD(ctx.name); expect(appState[0].kind).toBe("module"); expect((appState[0] as ModuleElem).name).toBe("foo/bar/ba"); }); -test("module foo/bar/ba", ctx => { - const appState = parseWgslD(ctx.task.name); +test("module foo/bar/ba", (ctx) => { + const appState = parseWgslD(ctx.name); expect(appState[0].kind).toBe("module"); expect((appState[0] as ModuleElem).name).toBe("foo/bar/ba"); }); -test("import ./util/foo;", ctx => { - const appState = parseWgslD(ctx.task.name); +test("import ./util/foo;", (ctx) => { + const appState = parseWgslD(ctx.name); const importElem = appState[0] as TreeImportElem; const segments = treeToString(importElem.imports); expect(segments).toBe("./util/foo"); }); -test("import ./bar/foo;", ctx => { - const appState = parseWgslD(ctx.task.name); +test("import ./bar/foo;", (ctx) => { + const appState = parseWgslD(ctx.name); const importElem = appState[0] as TreeImportElem; const segments = treeToString(importElem.imports); expect(segments).toBe("./bar/foo"); }); -test("import ./bar/{foo,bar};", ctx => { - const appState = parseWgslD(ctx.task.name); - const imports = appState.filter(e => e.kind === "treeImport"); - const segments = imports.map(i => treeToString(i.imports)); +test("import ./bar/{foo,bar};", (ctx) => { + const appState = parseWgslD(ctx.name); + const imports = appState.filter((e) => e.kind === "treeImport"); + const segments = imports.map((i) => treeToString(i.imports)); expect(segments).toContain("./bar/{(foo), (bar)}"); }); diff --git a/linker/test/ParseExpression.test.ts b/linker/test/ParseExpression.test.ts new file mode 100644 index 000000000..dbf3fcc6a --- /dev/null +++ b/linker/test/ParseExpression.test.ts @@ -0,0 +1,17 @@ +import { expect, test } from "vitest"; +import { testAppParse } from "./TestUtil.ts"; +import { expression } from "../ParseWgslD.ts"; +import { eof, seq } from "@wesl/mini-parse"; + +Deno.test("parse number", () => { + const src = `3`; + const { parsed } = testAppParse(seq(expression, eof), src); + expect(parsed).not.toBeNull(); + expect(parsed!.tags.ident).toBeUndefined(); +}); + +Deno.test("parse comparisons with && ||", () => { + const src = `array<3 && 4>(5)`; + const { parsed } = testAppParse(seq(expression, eof), src); + expect(parsed!.tags.ident).toEqual(["array"]); +}); diff --git a/packages/linker/src/test/ParseModule.test.ts b/linker/test/ParseModule.test.ts similarity index 76% rename from packages/linker/src/test/ParseModule.test.ts rename to linker/test/ParseModule.test.ts index d8940a67e..61e195d5c 100644 --- a/packages/linker/src/test/ParseModule.test.ts +++ b/linker/test/ParseModule.test.ts @@ -1,7 +1,8 @@ import { expect, test } from "vitest"; -import { parseModule, TextModule } from "../ParseModule.js"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { parseModule, TextModule } from "../ParseModule.ts"; -test("simple fn export", () => { +test("simple fn export", async (ctx) => { const src = ` export fn one() -> i32 { @@ -10,10 +11,10 @@ test("simple fn export", () => { `; const module = testParseModule(src); expect(module.exports.length).toBe(1); - expect(module.exports).toMatchSnapshot(); + await assertSnapshot(ctx, module); }); -test("simple fn import", () => { +test("simple fn import", async (ctx) => { const src = ` import bar/foo @@ -21,7 +22,7 @@ test("simple fn import", () => { `; const module = testParseModule(src); expect(module.imports.length).toBe(1); - expect(module.imports).toMatchSnapshot(); + await assertSnapshot(ctx, module); }); test("read simple struct export", () => { @@ -48,14 +49,14 @@ test("read #module", () => { // test.skip("parse error shows correct line after @if", () => {}); -test("import gleam style", () => { +test("import gleam style", async (ctx) => { const src = ` import my/foo fn bar() { foo(); } `; const module = testParseModule(src); - expect(module.imports).toMatchSnapshot(); + await assertSnapshot(ctx, module.imports); }); function testParseModule(src: string): TextModule { diff --git a/packages/linker/src/test/ParseWgslD.test.ts b/linker/test/ParseWgslD.test.ts similarity index 58% rename from packages/linker/src/test/ParseWgslD.test.ts rename to linker/test/ParseWgslD.test.ts index 6ce7f27b4..d9d1fad29 100644 --- a/packages/linker/src/test/ParseWgslD.test.ts +++ b/linker/test/ParseWgslD.test.ts @@ -1,216 +1,168 @@ -import { or, repeat, _withBaseLogger } from "mini-parse"; -import { expectNoLogErr, logCatch } from "mini-parse/test-util"; +import { _withBaseLogger, or, repeat } from "@wesl/mini-parse"; +import { expectNoLogErr, logCatch } from "@wesl/mini-parse/test-util"; -import { dlog } from "berry-pretty"; import { expect, test } from "vitest"; -import { AbstractElem, FnElem, StructElem, VarElem } from "../AbstractElems.js"; -import { filterElems } from "../ParseModule.js"; -import { unknown, wordNumArgs } from "../ParseSupport.js"; +import { assertSnapshot } from "@std/testing/snapshot"; +import type { + AbstractElem, + FnElem, + StructElem, + VarElem, +} from "../AbstractElems.ts"; +import { filterElems } from "../ParseModule.ts"; +import { unknown } from "../ParseSupport.ts"; import { - fnDecl, + fn_decl, globalVar, parseWgslD, structDecl, - typeSpecifier, -} from "../ParseWgslD.js"; -import { testAppParse } from "./TestUtil.js"; + type_specifier, +} from "../ParseWgslD.ts"; +import { testAppParse } from "./TestUtil.ts"; +import { enableTracing } from "../../mini-parse/ParserTracing.ts"; function testParseWgsl(src: string): AbstractElem[] { return parseWgslD(src, undefined, {}, 500); } -test("parse empty string", () => { +test("parse empty string", async (ctx) => { const parsed = testParseWgsl(""); - expect(parsed).toMatchSnapshot(); + await assertSnapshot(ctx, parsed); }); -test("parse fn foo() { }", () => { +test("parse fn foo() { }", async (ctx) => { const src = "fn foo() { }"; const parsed = testParseWgsl(src); - expect(parsed).toMatchSnapshot(); + await assertSnapshot(ctx, parsed); }); -test("parse fn with calls", () => { +Deno.test("parse fn with calls", async (ctx) => { + enableTracing(true); const src = "fn foo() { foo(); bar(); }"; const parsed = testParseWgsl(src); - expect(parsed).toMatchSnapshot(); + await assertSnapshot(ctx, parsed); }); test("structDecl parses struct member types", () => { const src = "struct Foo { a: f32, b: i32 }"; const { appState } = testAppParse(structDecl, src); const { members } = appState[0] as StructElem; - const typeNames = members.flatMap(m => m.typeRefs.map(t => t.name)); + const typeNames = members.flatMap((m) => m.typeRefs.map((t) => t.name)); expect(typeNames).toEqual(["f32", "i32"]); }); -test("parse struct", () => { +test("parse struct", async (ctx) => { const src = "struct Foo { a: f32, b: i32 }"; const parsed = testParseWgsl(src); - expect(parsed).toMatchInlineSnapshot(` - [ - { - "end": 29, - "kind": "struct", - "members": [ - { - "end": 19, - "kind": "member", - "name": "a", - "start": 13, - "typeRefs": [ - { - "end": 19, - "kind": "typeRef", - "name": "f32", - "start": 16, - }, - ], - }, - { - "end": 27, - "kind": "member", - "name": "b", - "start": 21, - "typeRefs": [ - { - "end": 27, - "kind": "typeRef", - "name": "i32", - "start": 24, - }, - ], - }, - ], - "name": "Foo", - "nameElem": { - "end": 10, - "kind": "typeName", - "name": "Foo", - "start": 7, - }, - "start": 0, - }, - ] - `); -}); - -test("parse @attribute before fn", () => { + await assertSnapshot(ctx, parsed); +}); + +test("parse @attribute before fn", async (ctx) => { const src = ` @compute fn main() {} `; const parsed = testParseWgsl(src); - expect(parsed).toMatchSnapshot(); -}); - -test("wordNumArgs parses (a, b, 1)", () => { - const src = `(a, b, 1)`; - const { parsed } = testAppParse(wordNumArgs, src); - expect(parsed?.value).toMatchSnapshot(); + await assertSnapshot(ctx, parsed); }); -test("parse @compute @workgroup_size(a, b, 1) before fn", () => { +test("parse @compute @workgroup_size(a, b, 1) before fn", async (ctx) => { const src = ` @compute @workgroup_size(a, b, 1) fn main() {} `; const parsed = testParseWgsl(src); - expect(parsed).toMatchSnapshot(); + await assertSnapshot(ctx, parsed); }); -test("parse global diagnostic", () => { +test("parse global diagnostic", async (ctx) => { const src = ` diagnostic(off,derivative_uniformity); fn main() {} `; - expectNoLogErr(() => { + await expectNoLogErr(async () => { const parsed = testParseWgsl(src); - expect(parsed).toMatchSnapshot(); + await assertSnapshot(ctx, parsed); }); }); -test("parse const_assert", () => { +test("parse const_assert", async (ctx) => { const src = ` const_assert x < y; fn main() {} `; - expectNoLogErr(() => { + await expectNoLogErr(async () => { const parsed = testParseWgsl(src); - expect(parsed).toMatchSnapshot(); + await assertSnapshot(ctx, parsed); }); }); -test("parse top level var", () => { +test("parse top level var", async (ctx) => { const src = ` @group(0) @binding(0) var u: Uniforms; fn main() {} `; - expectNoLogErr(() => { + await expectNoLogErr(async () => { const parsed = testParseWgsl(src); - expect(parsed).toMatchSnapshot(); + await assertSnapshot(ctx, parsed); }); }); -test("parse top level override and const", () => { +test("parse top level override and const", async (ctx) => { const src = ` override x = 21; const y = 1; fn main() {} `; - expectNoLogErr(() => { + await expectNoLogErr(async () => { const parsed = testParseWgsl(src); - expect(parsed).toMatchSnapshot(); + await assertSnapshot(ctx, parsed); }); }); -test("parse root level ;;", () => { +test("parse root level ;;", async (ctx) => { const src = ";;"; - expectNoLogErr(() => { + await expectNoLogErr(async () => { const parsed = testParseWgsl(src); - expect(parsed).toMatchSnapshot(); + await assertSnapshot(ctx, parsed); }); }); -test("parse simple alias", () => { +test("parse simple alias", async (ctx) => { const src = `alias NewType = OldType;`; - expectNoLogErr(() => { + await expectNoLogErr(async () => { const parsed = testParseWgsl(src); - expect(parsed).toMatchSnapshot(); + await assertSnapshot(ctx, parsed); }); }); -test("parse array alias", () => { +test("parse array alias", async (ctx) => { const src = ` alias Points3 = array; `; - expectNoLogErr(() => { + await expectNoLogErr(async () => { const parsed = testParseWgsl(src); - expect(parsed).toMatchSnapshot(); + await assertSnapshot(ctx, parsed); }); }); -test("unexpected token", () => { +test("unexpected token", async (ctx) => { const p = repeat(or("a", unknown)); const { log, logged } = logCatch(); _withBaseLogger(log, () => testAppParse(p, "a b")); - expect(logged()).toMatchInlineSnapshot(` - "??? word: 'b' repeat > or > map - a b Ln 1 - ^" - `); + await assertSnapshot(ctx, logged()); }); test("fnDecl parses fn with return type", () => { const src = ` fn foo() -> MyType { } `; - const { appState } = testAppParse(fnDecl, src); + const { appState } = testAppParse(fn_decl, src); expect((appState[0] as FnElem).typeRefs[0].name).toBe("MyType"); }); @@ -218,7 +170,7 @@ test("fnDecl parses :type specifier in fn args", () => { const src = ` fn foo(a: MyType) { } `; - const { appState } = testAppParse(fnDecl, src); + const { appState } = testAppParse(fn_decl, src); const { typeRefs } = appState[0] as FnElem; expect(typeRefs[0].name).toBe("MyType"); }); @@ -229,7 +181,7 @@ test("fnDecl parses :type specifier in fn block", () => { var b:MyType; } `; - const { appState } = testAppParse(fnDecl, src); + const { appState } = testAppParse(fn_decl, src); expect((appState[0] as FnElem).typeRefs[0].name).toBe("MyType"); }); @@ -237,7 +189,7 @@ test("parse type in