diff --git a/Cargo.lock b/Cargo.lock index 6370265d..6aeff87e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,12 +45,56 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.100" @@ -126,6 +170,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.11.1" @@ -230,6 +280,20 @@ dependencies = [ "rand_core 0.10.0", ] +[[package]] +name = "chumsky" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14377e276b2c8300513dff55ba4cc4142b44e5d6de6d00eb5b2307d650bb4ec1" +dependencies = [ + "hashbrown 0.15.5", + "regex-automata 0.3.9", + "serde", + "stacker", + "unicode-ident", + "unicode-segmentation", +] + [[package]] name = "chumsky" version = "0.12.0" @@ -296,6 +360,42 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +[[package]] +name = "codespan" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583f52b0658b321b25fd6b209b6c76cf058f433071297de64e5980c3d9aad937" +dependencies = [ + "codespan-reporting", + "serde", +] + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "console" version = "0.15.11" @@ -308,6 +408,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cpufeatures" version = "0.3.0" @@ -466,6 +575,37 @@ dependencies = [ "rayon", ] +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + [[package]] name = "either" version = "1.15.0" @@ -478,6 +618,29 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -494,6 +657,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "2.4.1" @@ -621,6 +794,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + [[package]] name = "indexmap" version = "2.11.4" @@ -654,6 +833,12 @@ dependencies = [ "rustversion", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.13.0" @@ -678,6 +863,31 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" +dependencies = [ + "defmt", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "js-sys" version = "0.3.81" @@ -727,9 +937,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "memchr" @@ -834,6 +1044,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "oorandom" version = "11.1.5" @@ -903,6 +1119,15 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1090,12 +1315,12 @@ checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", - "bitflags", + "bitflags 2.11.1", "num-traits", "rand 0.9.4", "rand_chacha", "rand_xorshift", - "regex-syntax 0.8.6", + "regex-syntax 0.8.11", "rusty-fork", "tempfile", "unarray", @@ -1277,19 +1502,19 @@ version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags", + "bitflags 2.11.1", ] [[package]] name = "regex" -version = "1.11.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.11", - "regex-syntax 0.8.6", + "regex-automata 0.4.14", + "regex-syntax 0.8.11", ] [[package]] @@ -1305,13 +1530,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.11" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.6", + "regex-syntax 0.8.11", ] [[package]] @@ -1322,9 +1547,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rustix" @@ -1332,7 +1557,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", @@ -1463,7 +1688,7 @@ name = "stim-parser" version = "0.1.0" dependencies = [ "ariadne", - "chumsky", + "chumsky 0.12.0", "proptest", ] @@ -1509,6 +1734,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "2.0.18" @@ -1575,12 +1809,82 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vihaco" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "690c9db1827ed6e392340d3d9c65408213c7bb4c030b5e8991b03459e5c7bb17" +dependencies = [ + "byteorder", + "chumsky 0.10.1", + "codespan", + "colored", + "env_logger", + "eyre", + "log", + "smallvec", + "vihaco-derive", + "vihaco-parser", + "vihaco-parser-core", +] + +[[package]] +name = "vihaco-circuit-isa" +version = "0.1.0" +dependencies = [ + "chumsky 0.10.1", + "eyre", + "smallvec", + "vihaco", + "vihaco-parser", + "vihaco-parser-core", +] + +[[package]] +name = "vihaco-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f11ac1123518d4bec265847a1a12e406c1b54f4ef2478db87f4e6986e26b918" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "vihaco-parser" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b0c3b54b4caeaaeeda2953fa273155887e89ebdb0ce80b65d23c435b1e70a5" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "vihaco-parser-core", +] + +[[package]] +name = "vihaco-parser-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aea97e3bc67e4b5a97e21f2a4f64e4a87749038850968021ec903cc595353d20" +dependencies = [ + "chumsky 0.10.1", +] + [[package]] name = "virtue" version = "0.0.18" @@ -1720,7 +2024,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags", + "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap", "semver", @@ -1919,7 +2223,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.11.1", "indexmap", "log", "serde", diff --git a/Cargo.toml b/Cargo.toml index ecb991f2..84451c01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ members = [ "crates/ppvm-tableau", "crates/ppvm-stim", "crates/stim-parser", "crates/ppvm-tableau-sum", + "crates/vihaco-circuit-isa", # Runnable copies of the Rust code blocks in skills/ppvm-usage/SKILL.md. # Built by `cargo build --workspace --all-targets` in CI so the skill # can't silently drift away from the public API. diff --git a/crates/vihaco-circuit-isa/Cargo.toml b/crates/vihaco-circuit-isa/Cargo.toml new file mode 100644 index 00000000..6c5e0188 --- /dev/null +++ b/crates/vihaco-circuit-isa/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "vihaco-circuit-isa" +version = "0.1.0" +edition = "2024" + +[dependencies] +chumsky = "0.10" +eyre = "0.6.12" +smallvec = "1.15.1" +vihaco = "0.1.1" +vihaco-parser = "0.1.1" +vihaco-parser-core = "0.1.1" + +[package.metadata.cargo-machete] +ignored = ["eyre"] # transitive dependency in vihaco, somehow not handled correctly by machete diff --git a/crates/vihaco-circuit-isa/src/lib.rs b/crates/vihaco-circuit-isa/src/lib.rs new file mode 100644 index 00000000..4094b02e --- /dev/null +++ b/crates/vihaco-circuit-isa/src/lib.rs @@ -0,0 +1,346 @@ +// SPDX-FileCopyrightText: 2026 The PPVM Authors +// SPDX-License-Identifier: Apache-2.0 + +use smallvec::SmallVec; +use vihaco::Instruction; +use vihaco::Message; +use vihaco_parser::Parse; + +/// The parse trait wired up by `#[derive(Parse)]`, re-exported so downstream +/// crates can call [`CircuitInstruction::parser`] without depending on +/// `vihaco-parser-core` directly to bring the trait into scope. +pub use vihaco_parser_core::Parse as ParseInstruction; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Instruction, Parse)] +pub enum CircuitInstruction { + // NOTE: longer tokens need to go first + TwoQubitPauliError, // needs to go before T + Truncate, // needs to go before T + Trace, // needs to go before T + + // Single-Qubit Clifford gates + X, + Y, + Z, + H, + + #[token = "sqrt_x_adj"] + SqrtXAdj, + + #[token = "sqrt_x"] + SqrtX, + + #[token = "sqrt_y_adj"] + SqrtYAdj, + + #[token = "sqrt_y"] + SqrtY, + + #[token = "s_adj"] + SAdj, + S, + + // Controlled gates + CNOT, + CZ, + + // T gate + TAdj, + T, + + // Two-qubit rotations + RXX, + RYY, + RZZ, + + // Single-qubit rotations + RX, + RY, + RZ, + + // U3 + U3, + + // Measurement & Reset + Measure, + Reset, + + // RXY + R, + + // Loss + Loss, + CorrelatedLoss, + + // Noise + PauliError, + Depolarize2, + Depolarize, +} + +impl std::fmt::Display for CircuitInstruction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use CircuitInstruction::*; + match self { + TwoQubitPauliError => write!(f, "TwoQubitPauliError"), + Truncate => write!(f, "Truncate"), + Trace => write!(f, "Trace"), + + X => write!(f, "X"), + Y => write!(f, "Y"), + Z => write!(f, "Z"), + H => write!(f, "H"), + + SqrtXAdj => write!(f, "SqrtXAdj"), + + SqrtX => write!(f, "SqrtX"), + + SqrtYAdj => write!(f, "SqrtYAdj"), + + SqrtY => write!(f, "SqrtY"), + + SAdj => write!(f, "SAdj"), + S => write!(f, "S"), + + CNOT => write!(f, "CNOT"), + CZ => write!(f, "CZ"), + + TAdj => write!(f, "TAdj"), + T => write!(f, "T"), + + RXX => write!(f, "RXX"), + RYY => write!(f, "RYY"), + RZZ => write!(f, "RZZ"), + + RX => write!(f, "RX"), + RY => write!(f, "RY"), + RZ => write!(f, "RZ"), + + U3 => write!(f, "U3"), + + Measure => write!(f, "Measure"), + Reset => write!(f, "Reset"), + + R => write!(f, "R"), + + Loss => write!(f, "Loss"), + CorrelatedLoss => write!(f, "CorrelatedLoss"), + + PauliError => write!(f, "PauliError"), + Depolarize2 => write!(f, "Depolarize2"), + Depolarize => write!(f, "Depolarize"), + } + } +} + +#[derive(Debug, Clone, PartialEq, Message)] +pub enum CircuitMessage { + None, // Truncate (no operand) + Qubit(usize), // X, Y, Z, ... + QubitAndFloat(usize, f64), // RX, depolarize, ... + QubitAndTwoFloats(usize, f64, f64), // R + TwoQubit(usize, usize), // CX, CZ + TwoQubitAndFloat(usize, usize, f64), // RXX, ... + QubitU3(usize, f64, f64, f64), // U3 + QubitAndFloatArr3(usize, [f64; 3]), // PauliError + TwoQubitAndFloatArr3(usize, usize, [f64; 3]), // Correlated loss + TwoQubitAndFloatArr15(usize, usize, [f64; 15]), // TwoQubitPauliError + + PauliPatternStr(String), // Trace (resolved Pauli-pattern source) + + // batched instructions + QubitBatch(SmallVec<[usize; 8]>), // X, Y, Z, ... + QubitBatchAndFloat(SmallVec<[usize; 8]>, f64), // RX, depolarize, ... + TwoQubitBatch(SmallVec<[(usize, usize); 8]>), // CX, CZ + TwoQubitBatchAndFloat(SmallVec<[(usize, usize); 8]>, f64), // RXX, ... + QubitBatchU3(SmallVec<[usize; 8]>, f64, f64, f64), // U3 + QubitBatchAndFloatArr3(SmallVec<[usize; 8]>, [f64; 3]), // PauliError + TwoQubitBatchAndFloatArr3(SmallVec<[(usize, usize); 8]>, [f64; 3]), // Correlated loss + TwoQubitBatchAndFloatArr15(SmallVec<[(usize, usize); 8]>, [f64; 15]), // TwoQubitPauliError +} + +#[derive(Debug, Clone)] +pub struct CircuitEffect { + pub inst: CircuitInstruction, + pub msg: CircuitMessage, +} + +#[cfg(test)] +mod tests { + use super::CircuitInstruction::*; + use super::*; + + use chumsky::Parser as _; + use vihaco::instruction::{FromBytes, OpCode, WriteBytes}; + // `parser()` comes from the crate's re-exported `ParseInstruction` trait, + // pulled in by the `use super::*` glob above — no direct parser-core dep. + + /// Every variant, in declaration order. Anything iterating over the full + /// instruction set (round-trips, opcode uniqueness) goes through this so a + /// newly added variant is automatically covered. + const ALL: &[CircuitInstruction] = &[ + TwoQubitPauliError, + Truncate, + Trace, + X, + Y, + Z, + H, + SqrtXAdj, + SqrtX, + SqrtYAdj, + SqrtY, + SAdj, + S, + CNOT, + CZ, + TAdj, + T, + RXX, + RYY, + RZZ, + RX, + RY, + RZ, + U3, + Measure, + Reset, + R, + Loss, + CorrelatedLoss, + PauliError, + Depolarize2, + Depolarize, + ]; + + fn parse(src: &str) -> CircuitInstruction { + CircuitInstruction::parser() + .parse(src) + .into_result() + .unwrap_or_else(|e| panic!("parse of `{src}` failed: {e:?}")) + } + + // ─── Parse: tokens are the lowercased variant name ──────────────────── + + #[test] + fn parses_simple_lowercase_tokens() { + assert_eq!(parse("x"), X); + assert_eq!(parse("y"), Y); + assert_eq!(parse("z"), Z); + assert_eq!(parse("h"), H); + assert_eq!(parse("cnot"), CNOT); + assert_eq!(parse("cz"), CZ); + assert_eq!(parse("u3"), U3); + assert_eq!(parse("measure"), Measure); + assert_eq!(parse("reset"), Reset); + assert_eq!(parse("r"), R); + assert_eq!(parse("rx"), RX); + assert_eq!(parse("ry"), RY); + assert_eq!(parse("rz"), RZ); + assert_eq!(parse("rxx"), RXX); + assert_eq!(parse("depolarize2"), Depolarize2); + assert_eq!(parse("depolarize"), Depolarize); + } + + // ─── Parse: prefix-sensitive disambiguation ─────────────────────────── + // + // These pairs share a prefix, so the declaration order in the enum is + // load-bearing: the longer token must win. These tests pin that contract. + + #[test] + fn parses_t_family_without_prefix_collision() { + // `t` is a prefix of `tadj`, `trace`, `truncate`, and `twoqubitpaulierror`. + assert_eq!(parse("t"), T); + assert_eq!(parse("tadj"), TAdj); + assert_eq!(parse("trace"), Trace); + assert_eq!(parse("truncate"), Truncate); + assert_eq!(parse("twoqubitpaulierror"), TwoQubitPauliError); + } + + #[test] + fn parses_s_family_without_prefix_collision() { + // `s` is a prefix of `s_adj`, `sqrt_x`, `sqrt_y`, etc. + assert_eq!(parse("s"), S); + assert_eq!(parse("s_adj"), SAdj); + assert_eq!(parse("sqrt_x"), SqrtX); + assert_eq!(parse("sqrt_x_adj"), SqrtXAdj); + assert_eq!(parse("sqrt_y"), SqrtY); + assert_eq!(parse("sqrt_y_adj"), SqrtYAdj); + } + + #[test] + fn rejects_unknown_token() { + assert!(CircuitInstruction::parser().parse("nope").has_errors()); + } + + #[test] + fn rejects_pascal_case_token() { + // The parse token is lowercase; the Display form must not parse back. + assert!(CircuitInstruction::parser().parse("CNOT").has_errors()); + } + + // ─── Display: PascalCase variant names ──────────────────────────────── + + #[test] + fn display_uses_pascal_case_names() { + assert_eq!(H.to_string(), "H"); + assert_eq!(CNOT.to_string(), "CNOT"); + assert_eq!(TwoQubitPauliError.to_string(), "TwoQubitPauliError"); + assert_eq!(Trace.to_string(), "Trace"); + assert_eq!(Truncate.to_string(), "Truncate"); + // Custom-token variants display their Rust name, not the parse token. + assert_eq!(SqrtXAdj.to_string(), "SqrtXAdj"); + assert_eq!(SAdj.to_string(), "SAdj"); + } + + // ─── Instruction codec (derived OpCode / WriteBytes / FromBytes) ────── + + #[test] + fn opcodes_are_unit_width() { + // All variants are field-less, so each encodes to a single byte. + assert_eq!(CircuitInstruction::width(), 1); + } + + #[test] + fn opcodes_are_unique() { + let mut seen = std::collections::HashSet::new(); + for inst in ALL { + assert!( + seen.insert(inst.opcode()), + "duplicate opcode {} for {inst:?}", + inst.opcode() + ); + } + assert_eq!(seen.len(), ALL.len()); + } + + #[test] + fn opcodes_match_declaration_order() { + // Opcodes default to the variant index, which is the on-disk contract + // for bytecode. Reordering variants silently breaks old bytecode, so + // pin the assignment here. + for (index, inst) in ALL.iter().enumerate() { + assert_eq!(inst.opcode() as usize, index, "{inst:?}"); + } + } + + #[test] + fn write_then_read_round_trips_every_variant() { + for inst in ALL { + let mut buf = Vec::new(); + inst.write_bytes(&mut buf).unwrap(); + assert_eq!(buf, [inst.opcode()], "{inst:?} should encode to one byte"); + + let mut cursor = std::io::Cursor::new(buf); + let back = CircuitInstruction::from_bytes(&mut cursor).unwrap(); + assert_eq!(back, *inst); + } + } + + #[test] + fn from_bytes_rejects_unknown_opcode() { + let mut cursor = std::io::Cursor::new([0xFFu8]); + let err = CircuitInstruction::from_bytes(&mut cursor).unwrap_err(); + assert!(err.to_string().contains("invalid opcode"), "err: {err}"); + } +}