import { createHash } from "node:crypto"; import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { spawnSync } from "node:child_process"; import { describe, expect, it } from "vitest"; import { generatePcre2C, type Pcre2CGenerationRequest, } from "../src/regex/codegen/pcre2-c"; function request(operation: "match" | "replace"): Pcre2CGenerationRequest { return { flavour: "pcre2", flavourVersion: "PCRE2 10.47 8-bit WebAssembly", operation, pattern: String.raw`(?\p{L}+)-(\d+)`, flags: ["g"], options: { matchLimit: 1_000_000, depthLimit: 1_000, heapLimitKib: 32_768, }, subject: "x Grüße-42 y alpha-7", scanAll: true, ...(operation === "replace" ? { replacement: "$2:$" } : {}), maximumMatches: 100, maximumCaptureRows: 1_000, maximumOutputBytes: 4_096, }; } function sha256(value: string): string { return createHash("sha256").update(value).digest("hex"); } const exactToolchainPrefix = process.env.PCRE2_CODEGEN_PREFIX; const compileWithExactPcre2 = exactToolchainPrefix ? it : it.skip; describe("PCRE2 C code-generation golden", () => { it("retains a reviewed deterministic source identity", () => { expect(sha256(generatePcre2C(request("match")).source)).toBe( "22b91d578a449b0ed5c30e6eadad30f19a2bcb26ac9db355c8ec7a69328675da", ); expect(sha256(generatePcre2C(request("replace")).source)).toBe( "ab90a0d9831ea7b7145aa0072ac352b42baf9a795319d81f531b0f341b04a636", ); }); compileWithExactPcre2( "compiles and executes against the explicitly supplied PCRE2 10.47 C toolchain", async () => { if (!exactToolchainPrefix) return; const header = path.join(exactToolchainPrefix, "include", "pcre2.h"); const library = process.env.PCRE2_CODEGEN_LIBRARY ?? path.join(exactToolchainPrefix, "lib64", "libpcre2-8.a"); await expect(readFile(header)).resolves.toBeInstanceOf(Buffer); await expect(readFile(library)).resolves.toBeInstanceOf(Buffer); const directory = await mkdtemp( path.join(tmpdir(), "regex-tools-codegen-"), ); try { for (const operation of ["match", "replace"] as const) { const generated = generatePcre2C(request(operation)); const source = path.join(directory, generated.fileName); const executable = path.join(directory, `pcre2-${operation}`); await writeFile(source, generated.source); const compiled = spawnSync( process.env.CC ?? "cc", [ "-std=c17", "-Wall", "-Wextra", "-Werror", `-I${path.join(exactToolchainPrefix, "include")}`, source, library, "-o", executable, ], { encoding: "utf8" }, ); expect( `${compiled.stdout}${compiled.stderr}`, `${operation} fixture did not compile`, ).toBe(""); expect(compiled.status).toBe(0); const executed = spawnSync(executable, [], { encoding: "utf8" }); expect( executed.status, `${operation} fixture failed: ${executed.stdout}${executed.stderr}`, ).toBe(0); if (operation === "match") { expect(executed.stdout).toContain("match 1: UTF-8 bytes 2..12"); expect(executed.stdout).toContain("group 1 / word"); expect(executed.stdout).toContain( "completed: 2 match(es), 4 capture row(s)", ); } else { expect(executed.stdout).toBe("x 42:Grüße y 7:alpha"); expect(executed.stderr).toContain("completed: 2 substitution(s)"); } } } finally { await rm(directory, { recursive: true, force: true }); } }, ); });