Skip to content

Rust — asciichem-rs

asciichem-rs is the Rust implementation. It follows the same single-contract rule as the other three, but takes the grammar a different way: it does not reimplement the syntax. It consumes the compiled PARG artifact of the AsciiChem grammar and runs it on parsanol’s portable engine.

Terminal window
cargo add asciichem # not yet published — build from source
cargo test # corpus-backed, see below

PARG is the compiled, checksummed form of the grammar language — one compiler (Ruby-side), N consumers. The crate vendors the artifact (artifacts/asciichem.json, the same one the Ruby :parg engine runs) and parsanol verifies the envelope checksum at load. A stale or tampered artifact fails loudly instead of parsing a different language.

asciichem::parse("2H_2 + O_2 -> 2H_2O")?; // Ok when fully captured
asciichem::parse("C1CCC")?; // Err — unclosed ring
asciichem::parse_to_text("2H_2 + O_2 -> 2H_2O")?; // canonical text, == input
asciichem::mathml::render("^14C")?; // byte-exact presentation MathML
asciichem::wire::to_model_json(&asciichem::parse_nodes("H_2O")?)?; // v1 wire JSON
asciichem::svg::render("C1-C-C-C-C-C1")?; // deterministic 2D skeletal SVG

Two behaviours worth knowing:

  • Short captures are errors. Parsing must consume the whole trimmed input; a grammar edit that leaves a trailing literal uncaptured is rejected, not silently ignored.
  • Deep nesting runs on a dedicated large-stack thread. The recursive PEG engine overflows a default thread stack on the fuzz corpus’s deeply nested inputs; parse reserves a stack that is committed only as used.

The Rust implementation claims the parse contract and the L1 text round-trip contract — every corpus case parses or rejects exactly as marked, and every round-trip fixture renders back through the canonical text form byte-identically, gated in CI:

LevelClaim
parse/reject100% — every corpus case parses or rejects exactly as marked
L0 (wire round-trip)100% — every round-trip fixture survives the v1 wire JSON field-for-field
L1 (text round-trip)100% — every round-trip fixture satisfies parse_to_text(s) == s
L2 (MathML goldens)100% — every corpus MathML golden renders byte-exactly
L2 (SVG)100% — structural + linear SVG byte-exact with the TypeScript renderers

The wire form mirrors the reference WireAdapter field for field — camelCase keys, kebab-case type tags, empty collections omitted — so canonical JSON is interchangeable across implementations, with mechanism steps crossing as typed reactions (cascade steps have no wire form and say so).

The MathML renderer mirrors the reference formatter rule for rule — isotopes as mmultiscripts left superscripts (the atom-binding fix, never a phantom base), msubsup for subscript + charge/oxidation, conditions as mover/munder/munderover — byte-exact with the L2 goldens.

The semantic model subset mirrors the reference implementation’s TransformRules: atoms with isotopes, charges, oxidation states, Lewis markers, ring closures and chirality; all bond kinds; groups across bracket kinds; molecules with coefficients, stereo and @-annotations; reactions with all four arrows and bracket conditions; cascades; electron configurations; mechanisms with electron-flow flows and spectators; quoted text; embedded math. Constructs outside the subset report not yet supported rather than guessing.

The site’s conformance matrix reports this honestly per implementation.

The Rust implementation now covers every conformance level the corpus gates. The 2D layout is the same deterministic placer the TypeScript implementation uses (rings as regular polygons, fused rings anchored on shared atoms, chains as 120-degree zigzags), gated by a committed byte-parity reference snapshot. Because the grammar artifact is shared, the port is the tree-to-model transform, not the parser.

Artifact provenance

artifacts/README.md records where the vendored grammar comes from and why a checksum mismatch is fatal.

CI

Every PR runs cargo fmt --check, cargo clippy -D warnings, and the corpus-backed tests against a cloned asciichem-tests.

Play