open-standards/wai/tools/tree-sitter-openqasm/script/check-programs.mjs
Transaction Science c4bda95cca wai-quantum 0.7.0 integrated: the generated material on the final tree, the pins it moves, and what replaying every job found
Generated on the final tree: the views page, rebuilt (802 495 bytes, under
1 MiB) from its sources; the notebooks' stored outputs; the agent docs. The
pins that move: wai-web's course check names its engine now, so its digest
moves (374b400e... to 969b2c1c..., and with every release from here), and the
page's wasm32 count is 18 (native 26, as pinned). wai-quantum-qpu's library
count is 34, as the cherry-picked pin says.

What replaying each workflow's jobs found, each fixed here:
- the views page's one line carried the estimate pane's English note, "no
  other beats on both", which the comparative-prose check reads across the
  line; the note says "none of the others beats on both";
- the docs runner's broken.qasm is broken on purpose, so the editors check
  requires it to fail to parse, as the intent set's broken programs;
- the command line's parse test named the service's submit and task in a
  build without the service; those checks are a test of their own, built
  with it;
- the set-only Merkle helpers warned in a build without receipts;
- a one-element loop in the service's strict test;
- the energy job looked for the live counter's line at the start of a line,
  where --nocapture puts it after the test's name;
- the Python test comparing a run on the service with one here now leaves out
  the provenance the service adds, and checks it is there.

The release heading's caller list is the final API check's: the frame text's
new fields and variant, and the request's compile passes and mitigation.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-28 08:43:06 -04:00

112 lines
5.4 KiB
JavaScript

#!/usr/bin/env node
// Every OpenQASM program in the repository's conformance material and
// examples, parsed by this grammar: none may hold an error or a missing node.
//
// node script/check-programs.mjs [--out DIR] [--list]
//
// The programs are every tracked .qasm file under wai/ (the conformance
// artifacts, the workbench's examples, the editor corpus), the Studio's worked
// examples, the sample programs the language service offers, every
// here-document holding a program in the quantum command line's and service's
// READMEs and smoke scripts, and every ```qasm block in wai/'s Markdown. With
// --out, each is also written to DIR, numbered, for other checks to read.
import { execFileSync, spawnSync } from 'node:child_process';
import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
const here = dirname(fileURLToPath(import.meta.url));
const grammar = resolve(here, '..');
const repo = resolve(grammar, '../../..');
const tracked = execFileSync('git', ['ls-files', 'wai'], { cwd: repo, encoding: 'utf8' }).split('\n').filter(Boolean);
const read = (f) => readFileSync(join(repo, f), 'utf8');
const programs = [];
const add = (name, text, broken = false) => programs.push({ name, text, broken });
// The intent set repairs some programs from a syntax error (its expected repairs
// name a code from Q001 to Q005, the lexer's and the parser's): each must hold
// that error here too, as the broken program below does.
const intents = 'wai/quantum-conformance/intents';
const syntax = new Set();
if (tracked.includes(`${intents}/manifest.json`)) {
for (const e of JSON.parse(read(`${intents}/manifest.json`)).cases ?? []) {
if ((e.expect?.repairs ?? []).some((c) => /^Q00[1-5]$/.test(c))) syntax.add(`${intents}/${e.program}`);
}
}
// The documentation runner's broken.qasm is broken on purpose too (a missing
// `;`): the command line's README repairs it.
syntax.add('wai/tools/quantum-docs/fixtures/broken.qasm');
for (const f of tracked.filter((f) => f.endsWith('.qasm'))) add(f, read(f), syntax.has(f));
const { EXAMPLES } = await import(pathToFileURL(join(repo, 'wai/quantum-studio/src/examples.js')).href);
for (const e of EXAMPLES) add(`wai/quantum-studio/src/examples.js: ${e.id}`, e.job.qasm);
// The language service's samples, as Rust string literals in its source.
const service = 'wai/wai-quantum/src/engine/editor/mod.rs';
const samples = read(service).split('pub const SAMPLES')[1].split('\n];')[0];
for (const m of samples.matchAll(/\(\s*"([a-z]+)",\s*"[^"]*",\s*"((?:[^"\\]|\\.)*)",?\s*\)/g)) {
add(`${service}: ${m[1]}`, JSON.parse(`"${m[2]}"`));
}
const heredoc = /<<-?'?([A-Z_]+)'?\n([\s\S]*?)\n\1\n/g;
for (const f of tracked.filter((f) => /^wai\/wai-quantum(-cli|-service)\/[^/]*\.(md|sh)$/.test(f))) {
for (const m of read(f).matchAll(heredoc)) if (m[2].trimStart().startsWith('OPENQASM')) add(`${f}: here-document`, m[2]);
}
const fence = /```(?:qasm|openqasm)\n([\s\S]*?)```/g;
for (const f of tracked.filter((f) => f.endsWith('.md'))) {
for (const m of read(f).matchAll(fence)) add(`${f}: qasm block`, m[1]);
}
const dir = process.argv.includes('--out') ? resolve(process.argv[process.argv.indexOf('--out') + 1]) : mkdtempSync(join(tmpdir(), 'openqasm-programs-'));
mkdirSync(dir, { recursive: true });
// A program with a missing `;` must fail, or a clean result would say nothing.
programs.push({ name: 'a broken program (must fail)', text: 'OPENQASM 3.0;\nqubit q\nh q;\n', broken: true });
const scratch = mkdtempSync(join(tmpdir(), 'openqasm-broken-'));
const files = programs.map((p, i) => {
const f = join(p.broken ? scratch : dir, `${String(i).padStart(3, '0')}.qasm`);
writeFileSync(f, p.text);
return f;
});
// One run for them all, so the parser is built once.
const r = spawnSync('npx', ['--no-install', 'tree-sitter', 'parse', '--quiet', '--json-summary', ...files], { cwd: grammar, encoding: 'utf8', maxBuffer: 64 << 20 });
const out = r.stdout || '';
const at = out.search(/^\{/m);
if (at < 0) {
console.error(`the parser gave no summary (status ${r.status})\n${r.stderr}`);
process.exit(1);
}
const summaries = JSON.parse(out.slice(at)).parse_summaries;
if (summaries.length !== files.length) {
console.error(`${files.length} programs, ${summaries.length} parsed`);
process.exit(1);
}
let failed = 0;
for (const s of summaries) {
const i = files.indexOf(s.file);
if (programs[i]?.broken) {
if (s.successful) {
console.error(`${programs[i].name} parsed without an error, but it is broken on purpose: the check cannot see errors`);
process.exit(1);
}
continue;
}
if (s.successful) continue;
failed += 1;
const why = out.slice(0, at).split('\n').filter((l) => l.startsWith(s.file)).join('\n');
console.error(`FAILED ${i >= 0 ? programs[i].name : s.file}: ${why}`);
}
if (process.argv.includes('--list')) for (const p of programs) console.log(p.name);
const real = programs.filter((p) => !p.broken);
const kinds = new Set(real.map((p) => p.name.split(':')[0]));
console.log(`${real.length - failed} of ${real.length} programs parse without an error (from ${kinds.size} files); ${programs.length - real.length} broken on purpose, each refused`);
if (real.length < 20 || !real.some((p) => p.name.startsWith(service))) {
console.error('fewer programs than the repository holds: the collection is broken');
process.exit(1);
}
process.exit(failed ? 1 : 0);