Skip to content

Molecule

A molecule is an ordered list of atoms, groups, and bonds, with an optional leading stoichiometric coefficient and an optional stereochemistry marker.

FieldTypeSource positionExample
nodesArray of Atom, Group, or Bondthe bodyH, (OH), =
coefficientStringleading digits2H_2O
stereoSymbolprefix(R)-

The stereo field is nil or one of: :R, :S, :E, :Z, :alpha, :beta.

┌────────────────────────────────────────┐
stereo ───►│ Molecule │
│ │
│ nodes[0] ──┐ │
│ nodes[1] ──┤── ordered list │
│ nodes[2] ──┤ (Atom | Group | │
│ ... │ Bond) │
│ │ │
│ coefficient ─── leading digits │
└────────────────┘───────────────────────┘
molecule := stereo_prefix? coefficient? units
units := (unit | bond)+
unit := prefixed_atom | group | plain_atom

The stereo_prefix is optional; if present it must be a closed-letter set (R/S/E/Z/a/b/alpha/beta/α/β). The grammar tries prefixed_atom (with isotope) before plain_atom and before group; this ordering disambiguates (OH)-C (group + bond) from (R)-C (stereo prefix).

FormatterCoefficientStereo
MathML<mn>2</mn> prefix<mtext>(R)-</mtext> prefix
Textliteral digits prefix(R)- prefix
HTMLplain text prefix(R)- prefix
LaTeXmhchem bare digits\ce{(R)-...}
SVGliteral digits prefix(R)- prefix
AsciiChem.parse("H_2O").to_text # => "H_2O"
AsciiChem.parse("2H_2O").to_text # => "2H_2O"
AsciiChem.parse("(R)-CH_3").to_text # => "(R)-CH_3"
AsciiChem.parse("(alpha)-CH_3").to_text # => "(α)-CH_3" (canonical)

Why stereo is on the molecule, not the atom

Section titled “Why stereo is on the molecule, not the atom”

The chemist reads (R)-CH(OH)COOH and knows which carbon is chiral from chemistry knowledge. The parser only sees the prefix — it can’t know which atom is the stereocentre without structural context.

So stereo is metadata on the molecule. A future 2D structural renderer (via elk-rb integration) will be able to attach the marker to the specific atom via graph analysis; for now, the molecule-level marker preserves the source intent.

class Molecule
def children
nodes
end
end

The linter walks into a molecule’s nodes via children. Adding a new node type (e.g. an Anomer class for sugar-specific stereochemistry) means defining children on the new class; the linter picks it up.