Artifact provenance
artifacts/README.md records where the vendored grammar comes
from and why a checksum mismatch is fatal.
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.
cargo add asciichem # not yet published — build from sourcecargo test # corpus-backed, see belowPARG 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 capturedasciichem::parse("C1CCC")?; // Err — unclosed ringasciichem::parse_to_text("2H_2 + O_2 -> 2H_2O")?; // canonical text, == inputasciichem::mathml::render("^14C")?; // byte-exact presentation MathMLasciichem::wire::to_model_json(&asciichem::parse_nodes("H_2O")?)?; // v1 wire JSONasciichem::svg::render("C1-C-C-C-C-C1")?; // deterministic 2D skeletal SVGTwo behaviours worth knowing:
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:
| Level | Claim |
|---|---|
| parse/reject | 100% — 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.