asciichem 0.31.1 → 0.31.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95da94c5e8161536c0c7c7010235e3a2f398ccfdf8687de847a53cfd76e51731
4
- data.tar.gz: 20d38e60baf576485eba8fe14d54c7335515a5e60b4ad7d96d4dcd1afa716276
3
+ metadata.gz: b221962f73e48a95f8d177ffd94fdf051b135f70d014b9b42aaa5d7fb1005840
4
+ data.tar.gz: b2b40147701ba90d0b8b9ed014202836fbc65cbb22f14fa98f3f1195cdd3b7bd
5
5
  SHA512:
6
- metadata.gz: 84d797e0d0c161552b5180243c5496503ff8b7b925d76fb2dd85e9ca3ed0571504eb8f1acc9856839d0c3391a2a43df56b295bbd09db2e946d74169f648bbfca
7
- data.tar.gz: e77507e79c23a183001523f41a425a78ad82cbd47ca412ccbf84747fb87cdca4b391382e7893c4d27abfa9f8c8248d0eaed67a28dd37fc0f9496f7a282c1ada8
6
+ metadata.gz: 75aa3b257cb79d94a5fb7308b89b4a9ce1fb827dfbe073e96723fdfbe8c8f8991ef1e37d0a0af44b4c8e52c318811f63baf064cb2db646995420f6733e3114da
7
+ data.tar.gz: a33d70d38648147044a6eedf5a3e0afe171331862a7f08ea561ee52e3438bb3d3750636b85a2cbfd1d4a06e16937bb57f60616ee2a10f415ab479764741ef066
data/CHANGELOG.md CHANGED
@@ -3,6 +3,27 @@
3
3
  All notable changes to AsciiChem are documented here.
4
4
  This project follows [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [0.31.2] - 2026-10-20
7
+
8
+ ### Added
9
+ - `Reaction#stoichiometry` (TODO.v2/13 item 4, slice 2): the
10
+ coefficients are already model data; the reaction now exposes them
11
+ normalized — per side, coefficient-free canonical species text to
12
+ integer (absent coefficient = 1, duplicate species sum). Pure
13
+ model read; wire format unchanged.
14
+ - OPSIN source adapter (item 3): name-to-structure derivation via
15
+ the OPSIN service, feeding the resolver pipeline
16
+ (`AsciiChem::Resolver::Opsin`).
17
+ - Reaction role hints (item 4, slice A): `ReactionRoles.roles_for`
18
+ classifies conditions into catalyst/solvent/reagent hints per the
19
+ above/below-arrow convention.
20
+ - The perception pass (item 2): `AsciiChem::Perception` derives
21
+ implicit hydrogens and aromatic rings as pure model services; the
22
+ ValenceCheck linter consumes it.
23
+ - Differential conformance harness (item 5): 300 seeded generated
24
+ cases shared through asciichem-tests (`corpus/generated/models.json`),
25
+ exercised by the Ruby property suite and by TS/PY gates.
26
+
6
27
  ## [0.31.1] - 2026-10-20
7
28
 
8
29
  ### Changed
@@ -1 +1 @@
1
- {"bindings":null,"version":"0.3.1","grammar":"AsciiChemCore","shape":"parsanol-tree/v2","binding_version":1,"entries":{"formula":{"root":"formula","grammar":{"atoms":[{"Entity":{"atom":5}},{"Str":{"pattern":" "}},{"Re":{"pattern":"[\\x09]"}},{"Re":{"pattern":"[\\x0a]"}},{"Re":{"pattern":"[\\x0d]"}},{"Alternative":{"atoms":[1,2,3,4]}},{"Repetition":{"atom":0,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":6,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":640}},{"Entity":{"atom":426}},{"Entity":{"atom":412}},{"Entity":{"atom":409}},{"Entity":{"atom":408}},{"Entity":{"atom":30}},{"Str":{"pattern":"("}},{"Entity":{"atom":26}},{"Str":{"pattern":"alpha"}},{"Str":{"pattern":"beta"}},{"Str":{"pattern":"R"}},{"Str":{"pattern":"S"}},{"Str":{"pattern":"E"}},{"Str":{"pattern":"Z"}},{"Str":{"pattern":"α"}},{"Str":{"pattern":"β"}},{"Str":{"pattern":"a"}},{"Str":{"pattern":"b"}},{"Alternative":{"atoms":[16,17,18,19,20,21,22,23,24,25]}},{"Named":{"name":"stereo","atom":15}},{"Str":{"pattern":")"}},{"Str":{"pattern":"-"}},{"Sequence":{"atoms":[14,27,28,29]}},{"Repetition":{"atom":13,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":43}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":33,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"("}},{"Str":{"pattern":"["}},{"Str":{"pattern":"{"}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Alternative":{"atoms":[35,36,37,38]}},{"Lookahead":{"atom":39,"positive":true}},{"Sequence":{"atoms":[34,40]}},{"Named":{"name":"value","atom":41}},{"Named":{"name":"coefficient","atom":42}},{"Repetition":{"atom":32,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":403}},{"Entity":{"atom":209}},{"Entity":{"atom":50}},{"Str":{"pattern":":"}},{"Repetition":{"atom":48,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"lone_pairs","atom":49}},{"Repetition":{"atom":47,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"^"}},{"Str":{"pattern":"_"}},{"Alternative":{"atoms":[52,53]}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":55,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[54,56]}},{"Named":{"name":"isotope","atom":57}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Repetition":{"atom":60,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[59,61]}},{"Named":{"name":"element","atom":62}},{"Entity":{"atom":124}},{"Str":{"pattern":"_"}},{"Entity":{"atom":77}},{"Str":{"pattern":"{"}},{"Str":{"pattern":"}"}},{"Lookahead":{"atom":68,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[69,70]}},{"Repetition":{"atom":71,"min":0,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"}"}},{"Sequence":{"atoms":[67,72,73]}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":75,"min":1,"max":null,"tag":"Repetition"}},{"Alternative":{"atoms":[74,76]}},{"Sequence":{"atoms":[65,66]}},{"Named":{"name":"subscript","atom":78}},{"Repetition":{"atom":79,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"^"}},{"Entity":{"atom":120}},{"Str":{"pattern":"{"}},{"Str":{"pattern":"}"}},{"Lookahead":{"atom":84,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[85,86]}},{"Repetition":{"atom":87,"min":0,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"}"}},{"Sequence":{"atoms":[83,88,89]}},{"Entity":{"atom":102}},{"Str":{"pattern":"("}},{"Re":{"pattern":"[I]"}},{"Re":{"pattern":"[V]"}},{"Re":{"pattern":"[X]"}},{"Re":{"pattern":"[L]"}},{"Re":{"pattern":"[C]"}},{"Re":{"pattern":"[D]"}},{"Alternative":{"atoms":[93,94,95,96,97,98]}},{"Repetition":{"atom":99,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":")"}},{"Sequence":{"atoms":[92,100,101]}},{"Entity":{"atom":118}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":104,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"+"}},{"Str":{"pattern":"-"}},{"Alternative":{"atoms":[106,107]}},{"Repetition":{"atom":108,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[105,109]}},{"Str":{"pattern":"+"}},{"Str":{"pattern":"-"}},{"Alternative":{"atoms":[111,112]}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":114,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":115,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[113,116]}},{"Alternative":{"atoms":[110,117]}},{"Alternative":{"atoms":[91,103]}},{"Alternative":{"atoms":[90,119]}},{"Sequence":{"atoms":[81,82]}},{"Named":{"name":"superscript","atom":121}},{"Repetition":{"atom":122,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[80,123]}},{"Entity":{"atom":128}},{"Str":{"pattern":"."}},{"Repetition":{"atom":126,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"radical_electrons","atom":127}},{"Repetition":{"atom":125,"min":0,"max":1,"tag":"Maybe"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":130,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"ring_closures","atom":131}},{"Repetition":{"atom":132,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":207}},{"Entity":{"atom":159}},{"Str":{"pattern":"@("}},{"Entity":{"atom":147}},{"Str":{"pattern":"-"}},{"Repetition":{"atom":138,"min":0,"max":1,"tag":"Maybe"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":140,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"."}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":143,"min":0,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[142,144]}},{"Repetition":{"atom":145,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[139,141,146]}},{"Named":{"name":"x2","atom":137}},{"Str":{"pattern":","}},{"Entity":{"atom":147}},{"Named":{"name":"y2","atom":150}},{"Str":{"pattern":","}},{"Entity":{"atom":147}},{"Named":{"name":"z2","atom":153}},{"Sequence":{"atoms":[152,154]}},{"Repetition":{"atom":155,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":")"}},{"Named":{"name":"coord_close","atom":157}},{"Sequence":{"atoms":[136,148,149,151,156,158]}},{"Repetition":{"atom":135,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":167}},{"Str":{"pattern":"@"}},{"Str":{"pattern":"R"}},{"Str":{"pattern":"S"}},{"Alternative":{"atoms":[163,164]}},{"Named":{"name":"atom_parity","atom":165}},{"Sequence":{"atoms":[162,166]}},{"Repetition":{"atom":161,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":176}},{"Str":{"pattern":"@m("}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":171,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"spin_multiplicity","atom":172}},{"Str":{"pattern":")"}},{"Named":{"name":"spin_close","atom":174}},{"Sequence":{"atoms":[170,173,175]}},{"Repetition":{"atom":169,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":191}},{"Str":{"pattern":"@t("}},{"Str":{"pattern":"\""}},{"Str":{"pattern":"\""}},{"Lookahead":{"atom":181,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[182,183]}},{"Repetition":{"atom":184,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"atom_title","atom":185}},{"Str":{"pattern":"\""}},{"Named":{"name":"title_quote","atom":187}},{"Str":{"pattern":")"}},{"Named":{"name":"title_close","atom":189}},{"Sequence":{"atoms":[179,180,186,188,190]}},{"Repetition":{"atom":178,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":205}},{"Str":{"pattern":"@f("}},{"Entity":{"atom":147}},{"Named":{"name":"x_fract","atom":195}},{"Str":{"pattern":","}},{"Entity":{"atom":147}},{"Named":{"name":"y_fract","atom":198}},{"Str":{"pattern":","}},{"Entity":{"atom":147}},{"Named":{"name":"z_fract","atom":201}},{"Str":{"pattern":")"}},{"Named":{"name":"fract_close","atom":203}},{"Sequence":{"atoms":[194,196,197,199,200,202,204]}},{"Repetition":{"atom":193,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[160,168,177,192,206]}},{"Sequence":{"atoms":[51,58,63,64,129,133,134]}},{"Named":{"name":"atom","atom":208}},{"Entity":{"atom":359}},{"Entity":{"atom":216}},{"Str":{"pattern":"("}},{"Str":{"pattern":"["}},{"Str":{"pattern":"{"}},{"Alternative":{"atoms":[212,213,214]}},{"Named":{"name":"open_bracket","atom":215}},{"Entity":{"atom":355}},{"Entity":{"atom":292}},{"Named":{"name":"reactants","atom":11}},{"Entity":{"atom":249}},{"Entity":{"atom":5}},{"Repetition":{"atom":221,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":222,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":229}},{"Str":{"pattern":"<=>"}},{"Str":{"pattern":"<->"}},{"Str":{"pattern":"->"}},{"Str":{"pattern":"<-"}},{"Alternative":{"atoms":[225,226,227,228]}},{"Named":{"name":"kind","atom":224}},{"Entity":{"atom":242}},{"Str":{"pattern":"["}},{"Str":{"pattern":"]"}},{"Lookahead":{"atom":233,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[234,235]}},{"Repetition":{"atom":236,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"text","atom":237}},{"Str":{"pattern":"]"}},{"Named":{"name":"condition_close","atom":239}},{"Sequence":{"atoms":[232,238,240]}},{"Named":{"name":"condition","atom":241}},{"Named":{"name":"above","atom":231}},{"Repetition":{"atom":243,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":242}},{"Named":{"name":"below","atom":245}},{"Repetition":{"atom":246,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[223,230,244,247]}},{"Named":{"name":"arrow","atom":248}},{"Entity":{"atom":5}},{"Repetition":{"atom":250,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":251,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":289}},{"Entity":{"atom":287}},{"Entity":{"atom":5}},{"Repetition":{"atom":255,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":256,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"+"}},{"Entity":{"atom":5}},{"Repetition":{"atom":259,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":260,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":285}},{"Entity":{"atom":280}},{"Str":{"pattern":"##"}},{"Named":{"name":"quadruple","atom":264}},{"Str":{"pattern":">-"}},{"Named":{"name":"wedge","atom":266}},{"Str":{"pattern":"-<"}},{"Named":{"name":"hash","atom":268}},{"Str":{"pattern":"~>"}},{"Named":{"name":"dative","atom":270}},{"Str":{"pattern":"~~"}},{"Named":{"name":"wavy","atom":272}},{"Str":{"pattern":"#"}},{"Named":{"name":"triple","atom":274}},{"Str":{"pattern":"="}},{"Named":{"name":"double","atom":276}},{"Str":{"pattern":"-"}},{"Named":{"name":"single","atom":278}},{"Alternative":{"atoms":[265,267,269,271,273,275,277,279]}},{"Alternative":{"atoms":[45,263]}},{"Repetition":{"atom":281,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"units","atom":282}},{"Sequence":{"atoms":[31,44,283]}},{"Named":{"name":"molecule","atom":284}},{"Sequence":{"atoms":[257,258,261,262]}},{"Named":{"name":"plus_term","atom":286}},{"Repetition":{"atom":254,"min":0,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[12,288]}},{"Named":{"name":"products","atom":253}},{"Sequence":{"atoms":[219,220,252,290]}},{"Named":{"name":"reaction","atom":291}},{"Entity":{"atom":315}},{"Entity":{"atom":305}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":295,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"s"}},{"Str":{"pattern":"p"}},{"Str":{"pattern":"d"}},{"Str":{"pattern":"f"}},{"Str":{"pattern":"g"}},{"Str":{"pattern":"h"}},{"Alternative":{"atoms":[297,298,299,300,301,302]}},{"Sequence":{"atoms":[296,303]}},{"Named":{"name":"orbital","atom":304}},{"Str":{"pattern":"^"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":307,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"occupancy","atom":308}},{"Entity":{"atom":5}},{"Repetition":{"atom":310,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":311,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[294,306,309,312]}},{"Repetition":{"atom":313,"min":2,"max":null,"tag":"Repetition"}},{"Named":{"name":"electron_config","atom":314}},{"Entity":{"atom":285}},{"Entity":{"atom":333}},{"Repetition":{"atom":217,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"group_nodes","atom":318}},{"Entity":{"atom":325}},{"Str":{"pattern":")"}},{"Str":{"pattern":"]"}},{"Str":{"pattern":"}"}},{"Alternative":{"atoms":[321,322,323]}},{"Named":{"name":"close_bracket","atom":324}},{"Str":{"pattern":"_"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":327,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[326,328]}},{"Named":{"name":"multiplicity","atom":329}},{"Repetition":{"atom":330,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[211,319,320,331]}},{"Named":{"name":"group","atom":332}},{"Entity":{"atom":345}},{"Str":{"pattern":"`"}},{"Str":{"pattern":"`"}},{"Lookahead":{"atom":336,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[337,338]}},{"Repetition":{"atom":339,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"math_source","atom":340}},{"Str":{"pattern":"`"}},{"Named":{"name":"math_close","atom":342}},{"Sequence":{"atoms":[335,341,343]}},{"Named":{"name":"embedded_math","atom":344}},{"Str":{"pattern":"\""}},{"Str":{"pattern":"\""}},{"Lookahead":{"atom":347,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[348,349]}},{"Repetition":{"atom":350,"min":0,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"\""}},{"Sequence":{"atoms":[346,351,352]}},{"Named":{"name":"group_text_run","atom":353}},{"Alternative":{"atoms":[218,293,316,317,334,354]}},{"Repetition":{"atom":217,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"group_nodes","atom":356}},{"Sequence":{"atoms":[211,357,320,331]}},{"Named":{"name":"group","atom":358}},{"Entity":{"atom":384}},{"Entity":{"atom":50}},{"Repetition":{"atom":361,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"H"}},{"Named":{"name":"element","atom":363}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Lookahead":{"atom":365,"positive":false}},{"Str":{"pattern":"_"}},{"Entity":{"atom":77}},{"Sequence":{"atoms":[367,368]}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":370,"min":1,"max":null,"tag":"Repetition"}},{"Alternative":{"atoms":[369,371]}},{"Named":{"name":"subscript","atom":372}},{"Repetition":{"atom":373,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"^"}},{"Entity":{"atom":120}},{"Sequence":{"atoms":[375,376]}},{"Named":{"name":"superscript","atom":377}},{"Repetition":{"atom":378,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":128}},{"Repetition":{"atom":380,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":207}},{"Sequence":{"atoms":[362,364,366,374,379,381,382]}},{"Named":{"name":"atom","atom":383}},{"Entity":{"atom":402}},{"Entity":{"atom":50}},{"Repetition":{"atom":386,"min":0,"max":1,"tag":"Maybe"}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Repetition":{"atom":389,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[388,390]}},{"Named":{"name":"element","atom":391}},{"Entity":{"atom":124}},{"Entity":{"atom":128}},{"Repetition":{"atom":394,"min":0,"max":1,"tag":"Maybe"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":396,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"ring_closures","atom":397}},{"Repetition":{"atom":398,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":207}},{"Sequence":{"atoms":[387,392,393,395,399,400]}},{"Named":{"name":"atom","atom":401}},{"Alternative":{"atoms":[46,210,360,385]}},{"Alternative":{"atoms":[45,263]}},{"Repetition":{"atom":404,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"units","atom":405}},{"Sequence":{"atoms":[31,44,406]}},{"Named":{"name":"molecule","atom":407}},{"Sequence":{"atoms":[12,288]}},{"Named":{"name":"reactants","atom":11}},{"Sequence":{"atoms":[410,220,252,290]}},{"Named":{"name":"reaction","atom":411}},{"Named":{"name":"first","atom":10}},{"Entity":{"atom":422}},{"Entity":{"atom":249}},{"Entity":{"atom":5}},{"Repetition":{"atom":416,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":417,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":409}},{"Named":{"name":"products","atom":419}},{"Sequence":{"atoms":[415,418,420]}},{"Named":{"name":"segment","atom":421}},{"Repetition":{"atom":414,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"segments","atom":423}},{"Sequence":{"atoms":[413,424]}},{"Named":{"name":"cascade","atom":425}},{"Entity":{"atom":412}},{"Entity":{"atom":315}},{"Entity":{"atom":465}},{"Str":{"pattern":"crystal"}},{"Str":{"pattern":"["}},{"Str":{"pattern":"]"}},{"Lookahead":{"atom":432,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[433,434]}},{"Repetition":{"atom":435,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"crystal_name","atom":436}},{"Str":{"pattern":"]"}},{"Named":{"name":"crystal_name_close","atom":438}},{"Sequence":{"atoms":[431,437,439]}},{"Repetition":{"atom":440,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"("}},{"Str":{"pattern":")"}},{"Lookahead":{"atom":443,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[444,445]}},{"Repetition":{"atom":446,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"crystal_params","atom":447}},{"Str":{"pattern":")"}},{"Named":{"name":"crystal_params_close","atom":449}},{"Sequence":{"atoms":[442,448,450]}},{"Repetition":{"atom":451,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"{"}},{"Str":{"pattern":"}"}},{"Lookahead":{"atom":454,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[455,456]}},{"Repetition":{"atom":457,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"crystal_body","atom":458}},{"Str":{"pattern":"}"}},{"Named":{"name":"crystal_body_close","atom":460}},{"Sequence":{"atoms":[453,459,461]}},{"Repetition":{"atom":462,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[430,441,452,463]}},{"Named":{"name":"crystal_node","atom":464}},{"Entity":{"atom":502}},{"Str":{"pattern":"spectrum"}},{"Str":{"pattern":"["}},{"Str":{"pattern":"]"}},{"Lookahead":{"atom":469,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[470,471]}},{"Repetition":{"atom":472,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"spectrum_type","atom":473}},{"Str":{"pattern":"]"}},{"Named":{"name":"spectrum_type_close","atom":475}},{"Sequence":{"atoms":[468,474,476]}},{"Repetition":{"atom":477,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"("}},{"Str":{"pattern":")"}},{"Lookahead":{"atom":480,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[481,482]}},{"Repetition":{"atom":483,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"spectrum_params","atom":484}},{"Str":{"pattern":")"}},{"Named":{"name":"spectrum_params_close","atom":486}},{"Sequence":{"atoms":[479,485,487]}},{"Repetition":{"atom":488,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"{"}},{"Str":{"pattern":"}"}},{"Lookahead":{"atom":491,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[492,493]}},{"Repetition":{"atom":494,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"spectrum_body","atom":495}},{"Str":{"pattern":"}"}},{"Named":{"name":"spectrum_body_close","atom":497}},{"Sequence":{"atoms":[490,496,498]}},{"Repetition":{"atom":499,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[467,478,489,500]}},{"Named":{"name":"spectrum_node","atom":501}},{"Entity":{"atom":528}},{"Str":{"pattern":"calc"}},{"Str":{"pattern":"("}},{"Str":{"pattern":")"}},{"Lookahead":{"atom":506,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[507,508]}},{"Repetition":{"atom":509,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"calc_params","atom":510}},{"Str":{"pattern":")"}},{"Named":{"name":"calc_params_close","atom":512}},{"Sequence":{"atoms":[505,511,513]}},{"Repetition":{"atom":514,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"{"}},{"Str":{"pattern":"}"}},{"Lookahead":{"atom":517,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[518,519]}},{"Repetition":{"atom":520,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"calc_body","atom":521}},{"Str":{"pattern":"}"}},{"Named":{"name":"calc_body_close","atom":523}},{"Sequence":{"atoms":[516,522,524]}},{"Repetition":{"atom":525,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[504,515,526]}},{"Named":{"name":"calc_node","atom":527}},{"Entity":{"atom":543}},{"Str":{"pattern":"zmatrix"}},{"Str":{"pattern":"{"}},{"Str":{"pattern":"}"}},{"Lookahead":{"atom":532,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[533,534]}},{"Repetition":{"atom":535,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"zmatrix_body","atom":536}},{"Str":{"pattern":"}"}},{"Named":{"name":"zmatrix_body_close","atom":538}},{"Sequence":{"atoms":[531,537,539]}},{"Repetition":{"atom":540,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[530,541]}},{"Named":{"name":"zmatrix_node","atom":542}},{"Entity":{"atom":558}},{"Str":{"pattern":"mechanism"}},{"Str":{"pattern":"{"}},{"Str":{"pattern":"}"}},{"Lookahead":{"atom":547,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[548,549]}},{"Repetition":{"atom":550,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"mechanism_body","atom":551}},{"Str":{"pattern":"}"}},{"Named":{"name":"mechanism_body_close","atom":553}},{"Sequence":{"atoms":[546,552,554]}},{"Repetition":{"atom":555,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[545,556]}},{"Named":{"name":"mechanism_node","atom":557}},{"Entity":{"atom":628}},{"Entity":{"atom":408}},{"Named":{"name":"mol","atom":560}},{"Entity":{"atom":624}},{"Entity":{"atom":5}},{"Repetition":{"atom":563,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":564,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":588}},{"Str":{"pattern":"@meta("}},{"Str":{"pattern":"\""}},{"Str":{"pattern":"\""}},{"Lookahead":{"atom":569,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[570,571]}},{"Repetition":{"atom":572,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"meta_key","atom":573}},{"Str":{"pattern":"\""}},{"Str":{"pattern":","}},{"Str":{"pattern":"\""}},{"Str":{"pattern":"\""}},{"Lookahead":{"atom":578,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[579,580]}},{"Repetition":{"atom":581,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"meta_value","atom":582}},{"Str":{"pattern":"\""}},{"Named":{"name":"meta_value_close","atom":584}},{"Str":{"pattern":")"}},{"Named":{"name":"meta_close","atom":586}},{"Sequence":{"atoms":[567,568,574,575,576,577,583,585,587]}},{"Entity":{"atom":621}},{"Str":{"pattern":"@"}},{"Entity":{"atom":602}},{"Str":{"pattern":"name"}},{"Str":{"pattern":"title"}},{"Str":{"pattern":"formula"}},{"Str":{"pattern":"label"}},{"Str":{"pattern":"inchi"}},{"Str":{"pattern":"smiles"}},{"Str":{"pattern":"cas"}},{"Str":{"pattern":"iupac"}},{"Str":{"pattern":"cid"}},{"Str":{"pattern":"chebi"}},{"Alternative":{"atoms":[592,593,594,595,596,597,598,599,600,601]}},{"Entity":{"atom":605}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Repetition":{"atom":604,"min":1,"max":null,"tag":"Repetition"}},{"Alternative":{"atoms":[591,603]}},{"Named":{"name":"ann_type","atom":606}},{"Str":{"pattern":"("}},{"Str":{"pattern":"\""}},{"Str":{"pattern":"\""}},{"Lookahead":{"atom":610,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[611,612]}},{"Repetition":{"atom":613,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"ann_value","atom":614}},{"Str":{"pattern":"\""}},{"Named":{"name":"ann_value_close","atom":616}},{"Str":{"pattern":")"}},{"Named":{"name":"ann_close","atom":618}},{"Sequence":{"atoms":[607,608,609,615,617,619]}},{"Sequence":{"atoms":[590,620]}},{"Alternative":{"atoms":[566,589]}},{"Sequence":{"atoms":[565,622]}},{"Named":{"name":"annotation_item","atom":623}},{"Repetition":{"atom":562,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"annotations","atom":625}},{"Sequence":{"atoms":[561,626]}},{"Named":{"name":"annotated_molecule","atom":627}},{"Entity":{"atom":408}},{"Entity":{"atom":345}},{"Str":{"pattern":"\""}},{"Str":{"pattern":"\""}},{"Lookahead":{"atom":632,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[633,634]}},{"Repetition":{"atom":635,"min":0,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"\""}},{"Sequence":{"atoms":[631,636,637]}},{"Named":{"name":"text_run","atom":638}},{"Alternative":{"atoms":[9,427,428,429,466,503,529,544,559,629,630,639]}},{"Entity":{"atom":5}},{"Repetition":{"atom":641,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":642,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":640}},{"Sequence":{"atoms":[643,644]}},{"Repetition":{"atom":645,"min":0,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[8,646]}},{"Named":{"name":"formula","atom":647}},{"Entity":{"atom":5}},{"Repetition":{"atom":649,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":650,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[7,648,651]}}],"root":652},"bindings":[]}},"preprocess":{},"tables":{},"lint":{"order_warnings":["rule formula_node: branches 1 and 2 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 1 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 1 and 9 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 1 and 10 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 2 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 2 and 9 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 2 and 10 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 3 and 9 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 3 and 10 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 4 and 6 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 9 and 10 are order-dependent (shared first bytes; ordered choice is decisive)","rule arrow_kind: branches 1 and 2 are order-dependent (shared first bytes; ordered choice is decisive)","rule arrow_kind: branches 1 and 4 are order-dependent (shared first bytes; ordered choice is decisive)","rule arrow_kind: branches 2 and 4 are order-dependent (shared first bytes; ordered choice is decisive)","rule stereo_letter: branches 1 and 9 are order-dependent (shared first bytes; ordered choice is decisive)","rule stereo_letter: branches 2 and 10 are order-dependent (shared first bytes; ordered choice is decisive)","rule stereo_letter: branches 7 and 8 are order-dependent (shared first bytes; ordered choice is decisive)","rule unit: branches 1 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule unit: branches 1 and 4 are order-dependent (shared first bytes; ordered choice is decisive)","rule unit: branches 3 and 4 are order-dependent (shared first bytes; ordered choice is decisive)","rule group_unit: branches 1 and 2 are order-dependent (shared first bytes; ordered choice is decisive)","rule group_unit: branches 1 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule group_unit: branches 1 and 4 are order-dependent (shared first bytes; ordered choice is decisive)","rule group_unit: branches 2 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule group_unit: branches 3 and 4 are order-dependent (shared first bytes; ordered choice is decisive)","rule bond: branches 1 and 6 are order-dependent (shared first bytes; ordered choice is decisive)","rule bond: branches 3 and 8 are order-dependent (shared first bytes; ordered choice is decisive)","rule bond: branches 4 and 5 are order-dependent (shared first bytes; ordered choice is decisive)","rule known_annotation_type: branches 5 and 8 are order-dependent (shared first bytes; ordered choice is decisive)","rule known_annotation_type: branches 7 and 9 are order-dependent (shared first bytes; ordered choice is decisive)","rule known_annotation_type: branches 7 and 10 are order-dependent (shared first bytes; ordered choice is decisive)","rule known_annotation_type: branches 9 and 10 are order-dependent (shared first bytes; ordered choice is decisive)"]},"default_entry":"formula","render":{},"derive":{},"tests":[],"docs":{"formula":"spaces? >> nodes.as(:formula) >> spaces?","formula_node":"Top-level nodes, DSL ordered-choice order (cascade before plain\nreaction, annotations before bare molecule).","cascade":"Two or more chained reactions: the transform promotes the chain\nto a ReactionCascade (first leg + arrow/products segments).","reaction":"Reactants, arrow (with optional above/below conditions), products.","arrow":"arrow.as(:arrow) {kind, above?, below?} — leading spaces ride the\narrow, conditions attach with no gap.","molecule":"A molecule: stereochemistry prefix, optional coefficient, units.","stereo_prefix":"`(R)-` style prefixes; a/b are the alpha/beta shorthand.","coefficient":"Coefficient: digits only when an element or bracket follows (else\nthe digits are subscripts/rings). Captured as {value}.","unit":"Units: prefixed (isotope) atoms, bracket groups, hydrogen\n(bare-digit subscripts allowed), plain atoms.","prefixed_atom":"Prefix isotope: ^14C or _14C — the marker binds to the atom, the\ncore semantic fix over AsciiMath. Captured with the marker.","group":"Bracket group: ( ... ) [ ... ] { ... } with optional multiplicity.","group_unit":"Inside a group: reactions, molecules, electron configs, embedded\nmath and quoted text recurse (DSL group_node set).","hydrogen_atom":"Hydrogen: bare digits after H are subscripts (H cannot ring-\nclose), but H must not steal the H of He, Hf...","atom_suffix":"Suffix markers consume `_` / `^` but capture marker + value\n(AtomBuilder strips the marker).","charge":"Charges: 2+, +2, 2 (bare digits fall through to the same capture).","oxidation_state":"Oxidation state: (IV), (II)...","lewis_prefix":"Lewis markers: leading colons = lone pairs; trailing dots =\nradical electrons.","atom_annotation":"Atom annotations, DSL order: coordinates, parity, multiplicity,\ntitle, fractional coordinates.","bond":"Bonds capture under their kind name (transform contract), longest\nfirst so `##` beats `#`; `->` is reserved for reactions.","crystal":"crystal[Name](a=X,b=Y,sg=SG){atoms with @f(x,y,z)}","spectrum":"spectrum[type](params){peak data}","calculation":"calc(method/basis){key: value units}","zmatrix":"zmatrix{lines}","mechanism":"mechanism{steps}","electron_config":"`1s2 2s2 2p6` — at least two orbital terms (DSL repeat(2)).","embedded_math":"Backtick-delimited Plurimath/AsciiMath source (delimiters stripped\nby the transform).","annotated_molecule":"Free-form text uses \"...\" delimiters, matching AsciiMath; the\ncapture includes the quotes (TextNormaliser strips them).\n@name(\"aspirin\") @cas(\"50-78-2\") @meta(\"k\",\"v\") — the annotation\nvocabulary rides the molecule.","metadata_annotation":"Two quoted args, comma-separated; keys distinguish metadata from\nregular annotations.","simple_annotation":"Known types (name, inchi, ...) handled specially by the transform;\nany other lowercase word becomes a property."},"source":"grammar AsciiChemCore version \"0.3.1\" {\n ## The AsciiChem syntax — full grammar, DSL-shaped captures\n ## (P1b, asciichem-ruby#96). Capture names and nesting mirror\n ## GrammarRules exactly so the PARG tree adapts to the same shape\n ## TransformRules consumes: sequence captures merge into one hash\n ## (parens to attach `as`), repetitions stay arrays, unwrapped\n ## rules let their captures ride into the parent context. Absent\n ## optionals omit their key (the tree adapter reinstates the ones\n ## transform patterns require, e.g. group multiplicity). Structural\n ## rules (molecule/reaction/group/annotated_molecule) are captured\n ## and the adapter unwraps them: PARG merge semantics for captured\n ## sequences with embedded repetitions yield arrays, and uncaptured\n ## sequence rules drop captures non-deterministically in reps\n ## (parsanol findings).\n ##\n ## Language notes (P0 findings): entries reference rules; sequences\n ## take no trailing capture (wrap in parens); [ ] is the option\n ## syntax so literal brackets are strings; until is exclusive\n ## (restate the closer); strict PEG — no repetition backtracking.\n\n entry formula: formula\n\n ## spaces? >> nodes.as(:formula) >> spaces?\n formula = [ 1*( ws ) ] ( formula_node 0*( [ 1*( ws ) ] formula_node ) ) as formula [ 1*( ws ) ]\n\n ws = ( \" \" / %x09 / %x0A / %x0D )\n\n ## Top-level nodes, DSL ordered-choice order (cascade before plain\n ## reaction, annotations before bare molecule).\n formula_node = cascade / reaction / electron_config / crystal / spectrum / calculation / zmatrix / mechanism / annotated_molecule / molecule / embedded_math / ( \"\\\"\" until \"\\\"\" \"\\\"\" ) as text_run\n\n # -- reactions ---------------------------------------------------------\n\n ## Two or more chained reactions: the transform promotes the chain\n ## to a ReactionCascade (first leg + arrow/products segments).\n cascade = ( ( reaction ) as first ( 1*( cascade_segment ) ) as segments ) as cascade\n\n cascade_segment = ( arrow [ 1*( ws ) ] ( terms as products ) ) as segment\n\n ## Reactants, arrow (with optional above/below conditions), products.\n reaction = ( ( terms as reactants ) arrow [ 1*( ws ) ] ( terms as products ) ) as reaction\n\n terms = molecule 0*( plus_term )\n\n plus_term = ( [ 1*( ws ) ] \"+\" [ 1*( ws ) ] molecule ) as plus_term\n\n ## arrow.as(:arrow) {kind, above?, below?} — leading spaces ride the\n ## arrow, conditions attach with no gap.\n arrow = ( [ 1*( ws ) ] ( arrow_kind ) as kind [ condition as above ] [ condition as below ] ) as arrow\n\n arrow_kind = ( \"<=>\" / \"<->\" / \"->\" / \"<-\" )\n\n condition = ( \"[\" ( until \"]\" ) as text ( \"]\" ) as condition_close ) as condition\n\n ## A molecule: stereochemistry prefix, optional coefficient, units.\n molecule = ( [ stereo_prefix ] [ coefficient ] ( 1*( unit / bond ) ) as units ) as molecule\n\n ## `(R)-` style prefixes; a/b are the alpha/beta shorthand.\n stereo_prefix = \"(\" ( stereo_letter ) as stereo \")\" \"-\"\n\n stereo_letter = ( \"alpha\" / \"beta\" / \"R\" / \"S\" / \"E\" / \"Z\" / \"α\" / \"β\" / \"a\" / \"b\" )\n\n ## Coefficient: digits only when an element or bracket follows (else\n ## the digits are subscripts/rings). Captured as {value}.\n coefficient = ( ( 1*(%x30-39) &( \"(\" / \"[\" / \"{\" / %x41-5A ) ) as value ) as coefficient\n\n ## Units: prefixed (isotope) atoms, bracket groups, hydrogen\n ## (bare-digit subscripts allowed), plain atoms.\n unit = prefixed_atom / group / hydrogen_atom / plain_atom\n\n ## Prefix isotope: ^14C or _14C — the marker binds to the atom, the\n ## core semantic fix over AsciiMath. Captured with the marker.\n prefixed_atom = ( [ lewis_prefix ] ( ( \"^\" / \"_\" ) 1*(%x30-39) ) as isotope ( %x41-5A [ %x61-7A ] ) as element atom_suffix [ lewis_radicals ] [ ( 1*(%x30-39) ) as ring_closures ] atom_annotation ) as atom\n\n ## Bracket group: ( ... ) [ ... ] { ... } with optional multiplicity.\n group = ( group_open ( 1*( group_unit ) ) as group_nodes group_close [ ( \"_\" 1*(%x30-39) ) as multiplicity ] ) as group\n\n group_open = ( \"(\" / \"[\" / \"{\" ) as open_bracket\n\n group_close = ( \")\" / \"]\" / \"}\" ) as close_bracket\n\n ## Inside a group: reactions, molecules, electron configs, embedded\n ## math and quoted text recurse (DSL group_node set).\n group_unit = reaction / electron_config / molecule / group / embedded_math / ( \"\\\"\" until \"\\\"\" \"\\\"\" ) as group_text_run\n\n ## Hydrogen: bare digits after H are subscripts (H cannot ring-\n ## close), but H must not steal the H of He, Hf...\n hydrogen_atom = ( [ lewis_prefix ] ( \"H\" ) as element !( %x61-7A ) [ ( \"_\" subscript_value / 1*(%x30-39) ) as subscript ] [ ( \"^\" superscript_value ) as superscript ] [ lewis_radicals ] atom_annotation ) as atom\n\n plain_atom = ( [ lewis_prefix ] ( %x41-5A [ %x61-7A ] ) as element atom_suffix [ lewis_radicals ] [ ( 1*(%x30-39) ) as ring_closures ] atom_annotation ) as atom\n\n ## Suffix markers consume `_` / `^` but capture marker + value\n ## (AtomBuilder strips the marker).\n atom_suffix = [ ( \"_\" subscript_value ) as subscript ] [ ( \"^\" superscript_value ) as superscript ]\n\n subscript_value = ( \"{\" until \"}\" \"}\" ) / 1*(%x30-39)\n\n superscript_value = ( \"{\" until \"}\" \"}\" ) / ( oxidation_state / charge )\n\n ## Charges: 2+, +2, 2 (bare digits fall through to the same capture).\n charge = ( 1*(%x30-39) [ \"+\" / \"-\" ] ) / ( ( \"+\" / \"-\" ) [ 1*(%x30-39) ] )\n\n ## Oxidation state: (IV), (II)...\n oxidation_state = \"(\" 1*( %x49 / %x56 / %x58 / %x4C / %x43 / %x44 ) \")\"\n\n ## Lewis markers: leading colons = lone pairs; trailing dots =\n ## radical electrons.\n lewis_prefix = 1*( \":\" ) as lone_pairs\n\n lewis_radicals = 1*( \".\" ) as radical_electrons\n\n ## Atom annotations, DSL order: coordinates, parity, multiplicity,\n ## title, fractional coordinates.\n atom_annotation = [ coordinate_annotation ] [ parity_annotation ] [ multiplicity_annotation ] [ title_annotation ] [ fractional_annotation ]\n\n coordinate_annotation = \"@(\" ( float_number ) as x2 \",\" ( float_number ) as y2 [ \",\" ( float_number ) as z2 ] ( \")\" ) as coord_close\n\n parity_annotation = \"@\" ( \"R\" / \"S\" ) as atom_parity\n\n multiplicity_annotation = \"@m(\" ( 1*(%x30-39) ) as spin_multiplicity ( \")\" ) as spin_close\n\n title_annotation = \"@t(\" \"\\\"\" ( until \"\\\"\" ) as atom_title ( \"\\\"\" ) as title_quote ( \")\" ) as title_close\n\n fractional_annotation = \"@f(\" ( float_number ) as x_fract \",\" ( float_number ) as y_fract \",\" ( float_number ) as z_fract ( \")\" ) as fract_close\n\n float_number = [ \"-\" ] 1*(%x30-39) [ \".\" 0*(%x30-39) ]\n\n # -- bonds ---------------------------------------------------------------\n\n ## Bonds capture under their kind name (transform contract), longest\n ## first so `##` beats `#`; `->` is reserved for reactions.\n bond = ( \"##\" as quadruple ) / ( \">-\" as wedge ) / ( \"-<\" as hash ) / ( \"~>\" as dative ) / ( \"~~\" as wavy ) / ( \"#\" as triple ) / ( \"=\" as double ) / ( \"-\" as single )\n\n # -- beyond-formulas constructs -----------------------------------------\n\n ## crystal[Name](a=X,b=Y,sg=SG){atoms with @f(x,y,z)}\n crystal = ( \"crystal\" [ ( \"[\" ( until \"]\" ) as crystal_name ( \"]\" ) as crystal_name_close ) ] [ ( \"(\" ( until \")\" ) as crystal_params ( \")\" ) as crystal_params_close ) ] [ ( \"{\" ( until \"}\" ) as crystal_body ( \"}\" ) as crystal_body_close ) ] ) as crystal_node\n\n ## spectrum[type](params){peak data}\n spectrum = ( \"spectrum\" [ ( \"[\" ( until \"]\" ) as spectrum_type ( \"]\" ) as spectrum_type_close ) ] [ ( \"(\" ( until \")\" ) as spectrum_params ( \")\" ) as spectrum_params_close ) ] [ ( \"{\" ( until \"}\" ) as spectrum_body ( \"}\" ) as spectrum_body_close ) ] ) as spectrum_node\n\n ## calc(method/basis){key: value units}\n calculation = ( \"calc\" [ ( \"(\" ( until \")\" ) as calc_params ( \")\" ) as calc_params_close ) ] [ ( \"{\" ( until \"}\" ) as calc_body ( \"}\" ) as calc_body_close ) ] ) as calc_node\n\n ## zmatrix{lines}\n zmatrix = ( \"zmatrix\" [ ( \"{\" ( until \"}\" ) as zmatrix_body ( \"}\" ) as zmatrix_body_close ) ] ) as zmatrix_node\n\n ## mechanism{steps}\n mechanism = ( \"mechanism\" [ ( \"{\" ( until \"}\" ) as mechanism_body ( \"}\" ) as mechanism_body_close ) ] ) as mechanism_node\n\n # -- electron configuration ----------------------------------------------\n\n ## `1s2 2s2 2p6` — at least two orbital terms (DSL repeat(2)).\n electron_config = 2*( orbital \"^\" ( 1*(%x30-39) ) as occupancy [ 1*( ws ) ] ) as electron_config\n\n orbital = ( 1*(%x30-39) ( \"s\" / \"p\" / \"d\" / \"f\" / \"g\" / \"h\" ) ) as orbital\n\n # -- embedded math and text ------------------------------------------------\n\n ## Backtick-delimited Plurimath/AsciiMath source (delimiters stripped\n ## by the transform).\n embedded_math = ( \"`\" ( until \"`\" ) as math_source ( \"`\" ) as math_close ) as embedded_math\n\n ## Free-form text uses \"...\" delimiters, matching AsciiMath; the\n ## capture includes the quotes (TextNormaliser strips them).\n\n # -- molecule annotations ---------------------------------------------------\n\n ## @name(\"aspirin\") @cas(\"50-78-2\") @meta(\"k\",\"v\") — the annotation\n ## vocabulary rides the molecule.\n annotated_molecule = ( ( molecule ) as mol ( 1*( annotation_item ) ) as annotations ) as annotated_molecule\n\n annotation_item = ( [ 1*( ws ) ] ( metadata_annotation / simple_annotation ) ) as annotation_item\n\n ## Two quoted args, comma-separated; keys distinguish metadata from\n ## regular annotations.\n metadata_annotation = \"@meta(\" \"\\\"\" ( until \"\\\"\" ) as meta_key \"\\\"\" \",\" \"\\\"\" ( until \"\\\"\" ) as meta_value ( \"\\\"\" ) as meta_value_close ( \")\" ) as meta_close\n\n ## Known types (name, inchi, ...) handled specially by the transform;\n ## any other lowercase word becomes a property.\n simple_annotation = \"@\" ( ( known_annotation_type / property_name ) as ann_type \"(\" \"\\\"\" ( until \"\\\"\" ) as ann_value ( \"\\\"\" ) as ann_value_close ( \")\" ) as ann_close )\n\n known_annotation_type = ( \"name\" / \"title\" / \"formula\" / \"label\" / \"inchi\" / \"smiles\" / \"cas\" / \"iupac\" / \"cid\" / \"chebi\" )\n\n property_name = 1*( %x61-7A )\n\n test formula {\n accept \"H_2O\"\n accept \"H2O\"\n accept \"Ca^2+\"\n accept \"SO_4^2-\"\n accept \"C1-C-C-C-C-C1\"\n accept \"C12-C-C1-C2\"\n accept \"CH_3-CH_2-OH\"\n accept \"2H_2 + O_2 -> 2H_2O\"\n accept \"N_2 + 3H_2 <=>[Fe][400C] 2NH_3\"\n accept \"A ->[heat] B ->[cool] C\"\n accept \"A -> B -> C -> D\"\n accept \"Ca(OH)_2\"\n accept \"Fe(IV)\"\n accept \"_14C\"\n accept \"^14C\"\n accept \"CC(=O)OC1=CC=CC=C1C(=O)O\"\n accept \"Ca^2+ + CO_3^2- -> CaCO_3\"\n accept \"(R)-CH_3CH(OH)COOH\"\n accept \":O\"\n accept \"1s^2 2s^2 2p^6\"\n accept \"`x^2`\"\n accept \"\\\"nitrogen gas\\\"\"\n accept \"crystal[NaCl](a=5.64,sg=Fm-3m){Na@f(0,0,0)}\"\n accept \"spectrum[nmr](type=1H){1.2: 3H s \\\"CH3\\\"}\"\n accept \"calc(b3lyp){energy: -234.5}\"\n accept \"zmatrix{ C1 H2 C1 1.09 }\"\n accept \"mechanism{ step1: A -> B }\"\n accept \"H_2O @name(\\\"water\\\") @cas(\\\"7732-18-5\\\")\"\n accept \"H_2O @meta(\\\"source\\\",\\\"handbook\\\")\"\n accept \" H_2O \"\n accept \"C@(10,20)@R@m(2)@t(\\\"C1\\\")\"\n reject \"\"\n reject \"???\"\n reject \"H_\"\n reject \"C(=O\"\n reject \"+ H_2O\"\n reject \"crystal[NaCl\"\n reject \"zmatrix{ C1 \"\n reject \"@name(\\\"x\\\") H_2O\"\n }\n}\n","source_hash":"bb8a13d71e196f3294318305cc157f60781c14e212802d6afb27b91d20237d56","compiler_version":"1.3.94","checksum":"sha256:4b042741d854fdd52375ffbb1fc980159855716b3a88b4f4ec6431b546eff343"}
1
+ {"bindings":null,"version":"0.4.0","grammar":"AsciiChemCore","shape":"parsanol-tree/v2","binding_version":1,"entries":{"formula":{"root":"formula","grammar":{"atoms":[{"Entity":{"atom":5}},{"Str":{"pattern":" "}},{"Re":{"pattern":"[\\x09]"}},{"Re":{"pattern":"[\\x0a]"}},{"Re":{"pattern":"[\\x0d]"}},{"Alternative":{"atoms":[1,2,3,4]}},{"Repetition":{"atom":0,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":6,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":652}},{"Entity":{"atom":438}},{"Entity":{"atom":424}},{"Entity":{"atom":421}},{"Entity":{"atom":420}},{"Entity":{"atom":30}},{"Str":{"pattern":"("}},{"Entity":{"atom":26}},{"Str":{"pattern":"alpha"}},{"Str":{"pattern":"beta"}},{"Str":{"pattern":"R"}},{"Str":{"pattern":"S"}},{"Str":{"pattern":"E"}},{"Str":{"pattern":"Z"}},{"Str":{"pattern":"α"}},{"Str":{"pattern":"β"}},{"Str":{"pattern":"a"}},{"Str":{"pattern":"b"}},{"Alternative":{"atoms":[16,17,18,19,20,21,22,23,24,25]}},{"Named":{"name":"stereo","atom":15}},{"Str":{"pattern":")"}},{"Str":{"pattern":"-"}},{"Sequence":{"atoms":[14,27,28,29]}},{"Repetition":{"atom":13,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":43}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":33,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"("}},{"Str":{"pattern":"["}},{"Str":{"pattern":"{"}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Alternative":{"atoms":[35,36,37,38]}},{"Lookahead":{"atom":39,"positive":true}},{"Sequence":{"atoms":[34,40]}},{"Named":{"name":"value","atom":41}},{"Named":{"name":"coefficient","atom":42}},{"Repetition":{"atom":32,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":415}},{"Entity":{"atom":217}},{"Entity":{"atom":50}},{"Str":{"pattern":":"}},{"Repetition":{"atom":48,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"lone_pairs","atom":49}},{"Repetition":{"atom":47,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"^"}},{"Str":{"pattern":"_"}},{"Alternative":{"atoms":[52,53]}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":55,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[54,56]}},{"Named":{"name":"isotope","atom":57}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Repetition":{"atom":60,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[59,61]}},{"Named":{"name":"element","atom":62}},{"Entity":{"atom":124}},{"Str":{"pattern":"_"}},{"Entity":{"atom":77}},{"Str":{"pattern":"{"}},{"Str":{"pattern":"}"}},{"Lookahead":{"atom":68,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[69,70]}},{"Repetition":{"atom":71,"min":0,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"}"}},{"Sequence":{"atoms":[67,72,73]}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":75,"min":1,"max":null,"tag":"Repetition"}},{"Alternative":{"atoms":[74,76]}},{"Sequence":{"atoms":[65,66]}},{"Named":{"name":"subscript","atom":78}},{"Repetition":{"atom":79,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"^"}},{"Entity":{"atom":120}},{"Str":{"pattern":"{"}},{"Str":{"pattern":"}"}},{"Lookahead":{"atom":84,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[85,86]}},{"Repetition":{"atom":87,"min":0,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"}"}},{"Sequence":{"atoms":[83,88,89]}},{"Entity":{"atom":102}},{"Str":{"pattern":"("}},{"Re":{"pattern":"[I]"}},{"Re":{"pattern":"[V]"}},{"Re":{"pattern":"[X]"}},{"Re":{"pattern":"[L]"}},{"Re":{"pattern":"[C]"}},{"Re":{"pattern":"[D]"}},{"Alternative":{"atoms":[93,94,95,96,97,98]}},{"Repetition":{"atom":99,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":")"}},{"Sequence":{"atoms":[92,100,101]}},{"Entity":{"atom":118}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":104,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"+"}},{"Str":{"pattern":"-"}},{"Alternative":{"atoms":[106,107]}},{"Repetition":{"atom":108,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[105,109]}},{"Str":{"pattern":"+"}},{"Str":{"pattern":"-"}},{"Alternative":{"atoms":[111,112]}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":114,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":115,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[113,116]}},{"Alternative":{"atoms":[110,117]}},{"Alternative":{"atoms":[91,103]}},{"Alternative":{"atoms":[90,119]}},{"Sequence":{"atoms":[81,82]}},{"Named":{"name":"superscript","atom":121}},{"Repetition":{"atom":122,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[80,123]}},{"Entity":{"atom":128}},{"Str":{"pattern":"."}},{"Repetition":{"atom":126,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"radical_electrons","atom":127}},{"Repetition":{"atom":125,"min":0,"max":1,"tag":"Maybe"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":130,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"ring_closures","atom":131}},{"Repetition":{"atom":132,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":215}},{"Entity":{"atom":159}},{"Str":{"pattern":"@("}},{"Entity":{"atom":147}},{"Str":{"pattern":"-"}},{"Repetition":{"atom":138,"min":0,"max":1,"tag":"Maybe"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":140,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"."}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":143,"min":0,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[142,144]}},{"Repetition":{"atom":145,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[139,141,146]}},{"Named":{"name":"x2","atom":137}},{"Str":{"pattern":","}},{"Entity":{"atom":147}},{"Named":{"name":"y2","atom":150}},{"Str":{"pattern":","}},{"Entity":{"atom":147}},{"Named":{"name":"z2","atom":153}},{"Sequence":{"atoms":[152,154]}},{"Repetition":{"atom":155,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":")"}},{"Named":{"name":"coord_close","atom":157}},{"Sequence":{"atoms":[136,148,149,151,156,158]}},{"Repetition":{"atom":135,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":167}},{"Str":{"pattern":"@"}},{"Str":{"pattern":"R"}},{"Str":{"pattern":"S"}},{"Alternative":{"atoms":[163,164]}},{"Named":{"name":"atom_parity","atom":165}},{"Sequence":{"atoms":[162,166]}},{"Repetition":{"atom":161,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":175}},{"Str":{"pattern":"@"}},{"Str":{"pattern":"TH1"}},{"Str":{"pattern":"TH2"}},{"Alternative":{"atoms":[171,172]}},{"Named":{"name":"chirality","atom":173}},{"Sequence":{"atoms":[170,174]}},{"Repetition":{"atom":169,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":184}},{"Str":{"pattern":"@m("}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":179,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"spin_multiplicity","atom":180}},{"Str":{"pattern":")"}},{"Named":{"name":"spin_close","atom":182}},{"Sequence":{"atoms":[178,181,183]}},{"Repetition":{"atom":177,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":199}},{"Str":{"pattern":"@t("}},{"Str":{"pattern":"\""}},{"Str":{"pattern":"\""}},{"Lookahead":{"atom":189,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[190,191]}},{"Repetition":{"atom":192,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"atom_title","atom":193}},{"Str":{"pattern":"\""}},{"Named":{"name":"title_quote","atom":195}},{"Str":{"pattern":")"}},{"Named":{"name":"title_close","atom":197}},{"Sequence":{"atoms":[187,188,194,196,198]}},{"Repetition":{"atom":186,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":213}},{"Str":{"pattern":"@f("}},{"Entity":{"atom":147}},{"Named":{"name":"x_fract","atom":203}},{"Str":{"pattern":","}},{"Entity":{"atom":147}},{"Named":{"name":"y_fract","atom":206}},{"Str":{"pattern":","}},{"Entity":{"atom":147}},{"Named":{"name":"z_fract","atom":209}},{"Str":{"pattern":")"}},{"Named":{"name":"fract_close","atom":211}},{"Sequence":{"atoms":[202,204,205,207,208,210,212]}},{"Repetition":{"atom":201,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[160,168,176,185,200,214]}},{"Sequence":{"atoms":[51,58,63,64,129,133,134]}},{"Named":{"name":"atom","atom":216}},{"Entity":{"atom":371}},{"Entity":{"atom":224}},{"Str":{"pattern":"("}},{"Str":{"pattern":"["}},{"Str":{"pattern":"{"}},{"Alternative":{"atoms":[220,221,222]}},{"Named":{"name":"open_bracket","atom":223}},{"Entity":{"atom":367}},{"Entity":{"atom":304}},{"Named":{"name":"reactants","atom":11}},{"Entity":{"atom":257}},{"Entity":{"atom":5}},{"Repetition":{"atom":229,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":230,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":237}},{"Str":{"pattern":"<=>"}},{"Str":{"pattern":"<->"}},{"Str":{"pattern":"->"}},{"Str":{"pattern":"<-"}},{"Alternative":{"atoms":[233,234,235,236]}},{"Named":{"name":"kind","atom":232}},{"Entity":{"atom":250}},{"Str":{"pattern":"["}},{"Str":{"pattern":"]"}},{"Lookahead":{"atom":241,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[242,243]}},{"Repetition":{"atom":244,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"text","atom":245}},{"Str":{"pattern":"]"}},{"Named":{"name":"condition_close","atom":247}},{"Sequence":{"atoms":[240,246,248]}},{"Named":{"name":"condition","atom":249}},{"Named":{"name":"above","atom":239}},{"Repetition":{"atom":251,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":250}},{"Named":{"name":"below","atom":253}},{"Repetition":{"atom":254,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[231,238,252,255]}},{"Named":{"name":"arrow","atom":256}},{"Entity":{"atom":5}},{"Repetition":{"atom":258,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":259,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":301}},{"Entity":{"atom":299}},{"Entity":{"atom":5}},{"Repetition":{"atom":263,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":264,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"+"}},{"Entity":{"atom":5}},{"Repetition":{"atom":267,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":268,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":297}},{"Entity":{"atom":292}},{"Str":{"pattern":"/"}},{"Named":{"name":"direction_up","atom":272}},{"Str":{"pattern":"\\"}},{"Named":{"name":"direction_down","atom":274}},{"Str":{"pattern":"##"}},{"Named":{"name":"quadruple","atom":276}},{"Str":{"pattern":">-"}},{"Named":{"name":"wedge","atom":278}},{"Str":{"pattern":"-<"}},{"Named":{"name":"hash","atom":280}},{"Str":{"pattern":"~>"}},{"Named":{"name":"dative","atom":282}},{"Str":{"pattern":"~~"}},{"Named":{"name":"wavy","atom":284}},{"Str":{"pattern":"#"}},{"Named":{"name":"triple","atom":286}},{"Str":{"pattern":"="}},{"Named":{"name":"double","atom":288}},{"Str":{"pattern":"-"}},{"Named":{"name":"single","atom":290}},{"Alternative":{"atoms":[273,275,277,279,281,283,285,287,289,291]}},{"Alternative":{"atoms":[45,271]}},{"Repetition":{"atom":293,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"units","atom":294}},{"Sequence":{"atoms":[31,44,295]}},{"Named":{"name":"molecule","atom":296}},{"Sequence":{"atoms":[265,266,269,270]}},{"Named":{"name":"plus_term","atom":298}},{"Repetition":{"atom":262,"min":0,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[12,300]}},{"Named":{"name":"products","atom":261}},{"Sequence":{"atoms":[227,228,260,302]}},{"Named":{"name":"reaction","atom":303}},{"Entity":{"atom":327}},{"Entity":{"atom":317}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":307,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"s"}},{"Str":{"pattern":"p"}},{"Str":{"pattern":"d"}},{"Str":{"pattern":"f"}},{"Str":{"pattern":"g"}},{"Str":{"pattern":"h"}},{"Alternative":{"atoms":[309,310,311,312,313,314]}},{"Sequence":{"atoms":[308,315]}},{"Named":{"name":"orbital","atom":316}},{"Str":{"pattern":"^"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":319,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"occupancy","atom":320}},{"Entity":{"atom":5}},{"Repetition":{"atom":322,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":323,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[306,318,321,324]}},{"Repetition":{"atom":325,"min":2,"max":null,"tag":"Repetition"}},{"Named":{"name":"electron_config","atom":326}},{"Entity":{"atom":297}},{"Entity":{"atom":345}},{"Repetition":{"atom":225,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"group_nodes","atom":330}},{"Entity":{"atom":337}},{"Str":{"pattern":")"}},{"Str":{"pattern":"]"}},{"Str":{"pattern":"}"}},{"Alternative":{"atoms":[333,334,335]}},{"Named":{"name":"close_bracket","atom":336}},{"Str":{"pattern":"_"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":339,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[338,340]}},{"Named":{"name":"multiplicity","atom":341}},{"Repetition":{"atom":342,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[219,331,332,343]}},{"Named":{"name":"group","atom":344}},{"Entity":{"atom":357}},{"Str":{"pattern":"`"}},{"Str":{"pattern":"`"}},{"Lookahead":{"atom":348,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[349,350]}},{"Repetition":{"atom":351,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"math_source","atom":352}},{"Str":{"pattern":"`"}},{"Named":{"name":"math_close","atom":354}},{"Sequence":{"atoms":[347,353,355]}},{"Named":{"name":"embedded_math","atom":356}},{"Str":{"pattern":"\""}},{"Str":{"pattern":"\""}},{"Lookahead":{"atom":359,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[360,361]}},{"Repetition":{"atom":362,"min":0,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"\""}},{"Sequence":{"atoms":[358,363,364]}},{"Named":{"name":"group_text_run","atom":365}},{"Alternative":{"atoms":[226,305,328,329,346,366]}},{"Repetition":{"atom":225,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"group_nodes","atom":368}},{"Sequence":{"atoms":[219,369,332,343]}},{"Named":{"name":"group","atom":370}},{"Entity":{"atom":396}},{"Entity":{"atom":50}},{"Repetition":{"atom":373,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"H"}},{"Named":{"name":"element","atom":375}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Lookahead":{"atom":377,"positive":false}},{"Str":{"pattern":"_"}},{"Entity":{"atom":77}},{"Sequence":{"atoms":[379,380]}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":382,"min":1,"max":null,"tag":"Repetition"}},{"Alternative":{"atoms":[381,383]}},{"Named":{"name":"subscript","atom":384}},{"Repetition":{"atom":385,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"^"}},{"Entity":{"atom":120}},{"Sequence":{"atoms":[387,388]}},{"Named":{"name":"superscript","atom":389}},{"Repetition":{"atom":390,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":128}},{"Repetition":{"atom":392,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":215}},{"Sequence":{"atoms":[374,376,378,386,391,393,394]}},{"Named":{"name":"atom","atom":395}},{"Entity":{"atom":414}},{"Entity":{"atom":50}},{"Repetition":{"atom":398,"min":0,"max":1,"tag":"Maybe"}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Repetition":{"atom":401,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[400,402]}},{"Named":{"name":"element","atom":403}},{"Entity":{"atom":124}},{"Entity":{"atom":128}},{"Repetition":{"atom":406,"min":0,"max":1,"tag":"Maybe"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":408,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"ring_closures","atom":409}},{"Repetition":{"atom":410,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":215}},{"Sequence":{"atoms":[399,404,405,407,411,412]}},{"Named":{"name":"atom","atom":413}},{"Alternative":{"atoms":[46,218,372,397]}},{"Alternative":{"atoms":[45,271]}},{"Repetition":{"atom":416,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"units","atom":417}},{"Sequence":{"atoms":[31,44,418]}},{"Named":{"name":"molecule","atom":419}},{"Sequence":{"atoms":[12,300]}},{"Named":{"name":"reactants","atom":11}},{"Sequence":{"atoms":[422,228,260,302]}},{"Named":{"name":"reaction","atom":423}},{"Named":{"name":"first","atom":10}},{"Entity":{"atom":434}},{"Entity":{"atom":257}},{"Entity":{"atom":5}},{"Repetition":{"atom":428,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":429,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":421}},{"Named":{"name":"products","atom":431}},{"Sequence":{"atoms":[427,430,432]}},{"Named":{"name":"segment","atom":433}},{"Repetition":{"atom":426,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"segments","atom":435}},{"Sequence":{"atoms":[425,436]}},{"Named":{"name":"cascade","atom":437}},{"Entity":{"atom":424}},{"Entity":{"atom":327}},{"Entity":{"atom":477}},{"Str":{"pattern":"crystal"}},{"Str":{"pattern":"["}},{"Str":{"pattern":"]"}},{"Lookahead":{"atom":444,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[445,446]}},{"Repetition":{"atom":447,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"crystal_name","atom":448}},{"Str":{"pattern":"]"}},{"Named":{"name":"crystal_name_close","atom":450}},{"Sequence":{"atoms":[443,449,451]}},{"Repetition":{"atom":452,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"("}},{"Str":{"pattern":")"}},{"Lookahead":{"atom":455,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[456,457]}},{"Repetition":{"atom":458,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"crystal_params","atom":459}},{"Str":{"pattern":")"}},{"Named":{"name":"crystal_params_close","atom":461}},{"Sequence":{"atoms":[454,460,462]}},{"Repetition":{"atom":463,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"{"}},{"Str":{"pattern":"}"}},{"Lookahead":{"atom":466,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[467,468]}},{"Repetition":{"atom":469,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"crystal_body","atom":470}},{"Str":{"pattern":"}"}},{"Named":{"name":"crystal_body_close","atom":472}},{"Sequence":{"atoms":[465,471,473]}},{"Repetition":{"atom":474,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[442,453,464,475]}},{"Named":{"name":"crystal_node","atom":476}},{"Entity":{"atom":514}},{"Str":{"pattern":"spectrum"}},{"Str":{"pattern":"["}},{"Str":{"pattern":"]"}},{"Lookahead":{"atom":481,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[482,483]}},{"Repetition":{"atom":484,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"spectrum_type","atom":485}},{"Str":{"pattern":"]"}},{"Named":{"name":"spectrum_type_close","atom":487}},{"Sequence":{"atoms":[480,486,488]}},{"Repetition":{"atom":489,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"("}},{"Str":{"pattern":")"}},{"Lookahead":{"atom":492,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[493,494]}},{"Repetition":{"atom":495,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"spectrum_params","atom":496}},{"Str":{"pattern":")"}},{"Named":{"name":"spectrum_params_close","atom":498}},{"Sequence":{"atoms":[491,497,499]}},{"Repetition":{"atom":500,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"{"}},{"Str":{"pattern":"}"}},{"Lookahead":{"atom":503,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[504,505]}},{"Repetition":{"atom":506,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"spectrum_body","atom":507}},{"Str":{"pattern":"}"}},{"Named":{"name":"spectrum_body_close","atom":509}},{"Sequence":{"atoms":[502,508,510]}},{"Repetition":{"atom":511,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[479,490,501,512]}},{"Named":{"name":"spectrum_node","atom":513}},{"Entity":{"atom":540}},{"Str":{"pattern":"calc"}},{"Str":{"pattern":"("}},{"Str":{"pattern":")"}},{"Lookahead":{"atom":518,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[519,520]}},{"Repetition":{"atom":521,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"calc_params","atom":522}},{"Str":{"pattern":")"}},{"Named":{"name":"calc_params_close","atom":524}},{"Sequence":{"atoms":[517,523,525]}},{"Repetition":{"atom":526,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"{"}},{"Str":{"pattern":"}"}},{"Lookahead":{"atom":529,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[530,531]}},{"Repetition":{"atom":532,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"calc_body","atom":533}},{"Str":{"pattern":"}"}},{"Named":{"name":"calc_body_close","atom":535}},{"Sequence":{"atoms":[528,534,536]}},{"Repetition":{"atom":537,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[516,527,538]}},{"Named":{"name":"calc_node","atom":539}},{"Entity":{"atom":555}},{"Str":{"pattern":"zmatrix"}},{"Str":{"pattern":"{"}},{"Str":{"pattern":"}"}},{"Lookahead":{"atom":544,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[545,546]}},{"Repetition":{"atom":547,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"zmatrix_body","atom":548}},{"Str":{"pattern":"}"}},{"Named":{"name":"zmatrix_body_close","atom":550}},{"Sequence":{"atoms":[543,549,551]}},{"Repetition":{"atom":552,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[542,553]}},{"Named":{"name":"zmatrix_node","atom":554}},{"Entity":{"atom":570}},{"Str":{"pattern":"mechanism"}},{"Str":{"pattern":"{"}},{"Str":{"pattern":"}"}},{"Lookahead":{"atom":559,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[560,561]}},{"Repetition":{"atom":562,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"mechanism_body","atom":563}},{"Str":{"pattern":"}"}},{"Named":{"name":"mechanism_body_close","atom":565}},{"Sequence":{"atoms":[558,564,566]}},{"Repetition":{"atom":567,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[557,568]}},{"Named":{"name":"mechanism_node","atom":569}},{"Entity":{"atom":640}},{"Entity":{"atom":420}},{"Named":{"name":"mol","atom":572}},{"Entity":{"atom":636}},{"Entity":{"atom":5}},{"Repetition":{"atom":575,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":576,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":600}},{"Str":{"pattern":"@meta("}},{"Str":{"pattern":"\""}},{"Str":{"pattern":"\""}},{"Lookahead":{"atom":581,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[582,583]}},{"Repetition":{"atom":584,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"meta_key","atom":585}},{"Str":{"pattern":"\""}},{"Str":{"pattern":","}},{"Str":{"pattern":"\""}},{"Str":{"pattern":"\""}},{"Lookahead":{"atom":590,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[591,592]}},{"Repetition":{"atom":593,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"meta_value","atom":594}},{"Str":{"pattern":"\""}},{"Named":{"name":"meta_value_close","atom":596}},{"Str":{"pattern":")"}},{"Named":{"name":"meta_close","atom":598}},{"Sequence":{"atoms":[579,580,586,587,588,589,595,597,599]}},{"Entity":{"atom":633}},{"Str":{"pattern":"@"}},{"Entity":{"atom":614}},{"Str":{"pattern":"name"}},{"Str":{"pattern":"title"}},{"Str":{"pattern":"formula"}},{"Str":{"pattern":"label"}},{"Str":{"pattern":"inchi"}},{"Str":{"pattern":"smiles"}},{"Str":{"pattern":"cas"}},{"Str":{"pattern":"iupac"}},{"Str":{"pattern":"cid"}},{"Str":{"pattern":"chebi"}},{"Alternative":{"atoms":[604,605,606,607,608,609,610,611,612,613]}},{"Entity":{"atom":617}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Repetition":{"atom":616,"min":1,"max":null,"tag":"Repetition"}},{"Alternative":{"atoms":[603,615]}},{"Named":{"name":"ann_type","atom":618}},{"Str":{"pattern":"("}},{"Str":{"pattern":"\""}},{"Str":{"pattern":"\""}},{"Lookahead":{"atom":622,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[623,624]}},{"Repetition":{"atom":625,"min":0,"max":null,"tag":"Repetition"}},{"Named":{"name":"ann_value","atom":626}},{"Str":{"pattern":"\""}},{"Named":{"name":"ann_value_close","atom":628}},{"Str":{"pattern":")"}},{"Named":{"name":"ann_close","atom":630}},{"Sequence":{"atoms":[619,620,621,627,629,631]}},{"Sequence":{"atoms":[602,632]}},{"Alternative":{"atoms":[578,601]}},{"Sequence":{"atoms":[577,634]}},{"Named":{"name":"annotation_item","atom":635}},{"Repetition":{"atom":574,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"annotations","atom":637}},{"Sequence":{"atoms":[573,638]}},{"Named":{"name":"annotated_molecule","atom":639}},{"Entity":{"atom":420}},{"Entity":{"atom":357}},{"Str":{"pattern":"\""}},{"Str":{"pattern":"\""}},{"Lookahead":{"atom":644,"positive":false}},{"Re":{"pattern":"[\\x00-\\u{10ffff}]"}},{"Sequence":{"atoms":[645,646]}},{"Repetition":{"atom":647,"min":0,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"\""}},{"Sequence":{"atoms":[643,648,649]}},{"Named":{"name":"text_run","atom":650}},{"Alternative":{"atoms":[9,439,440,441,478,515,541,556,571,641,642,651]}},{"Entity":{"atom":5}},{"Repetition":{"atom":653,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":654,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":652}},{"Sequence":{"atoms":[655,656]}},{"Repetition":{"atom":657,"min":0,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[8,658]}},{"Named":{"name":"formula","atom":659}},{"Entity":{"atom":5}},{"Repetition":{"atom":661,"min":1,"max":null,"tag":"Repetition"}},{"Repetition":{"atom":662,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[7,660,663]}}],"root":664},"bindings":[]}},"preprocess":{},"tables":{},"lint":{"order_warnings":["rule formula_node: branches 1 and 2 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 1 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 1 and 9 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 1 and 10 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 2 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 2 and 9 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 2 and 10 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 3 and 9 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 3 and 10 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 4 and 6 are order-dependent (shared first bytes; ordered choice is decisive)","rule formula_node: branches 9 and 10 are order-dependent (shared first bytes; ordered choice is decisive)","rule arrow_kind: branches 1 and 2 are order-dependent (shared first bytes; ordered choice is decisive)","rule arrow_kind: branches 1 and 4 are order-dependent (shared first bytes; ordered choice is decisive)","rule arrow_kind: branches 2 and 4 are order-dependent (shared first bytes; ordered choice is decisive)","rule stereo_letter: branches 1 and 9 are order-dependent (shared first bytes; ordered choice is decisive)","rule stereo_letter: branches 2 and 10 are order-dependent (shared first bytes; ordered choice is decisive)","rule stereo_letter: branches 7 and 8 are order-dependent (shared first bytes; ordered choice is decisive)","rule unit: branches 1 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule unit: branches 1 and 4 are order-dependent (shared first bytes; ordered choice is decisive)","rule unit: branches 3 and 4 are order-dependent (shared first bytes; ordered choice is decisive)","rule group_unit: branches 1 and 2 are order-dependent (shared first bytes; ordered choice is decisive)","rule group_unit: branches 1 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule group_unit: branches 1 and 4 are order-dependent (shared first bytes; ordered choice is decisive)","rule group_unit: branches 2 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule group_unit: branches 3 and 4 are order-dependent (shared first bytes; ordered choice is decisive)","rule bond: branches 3 and 8 are order-dependent (shared first bytes; ordered choice is decisive)","rule bond: branches 5 and 10 are order-dependent (shared first bytes; ordered choice is decisive)","rule bond: branches 6 and 7 are order-dependent (shared first bytes; ordered choice is decisive)","rule known_annotation_type: branches 5 and 8 are order-dependent (shared first bytes; ordered choice is decisive)","rule known_annotation_type: branches 7 and 9 are order-dependent (shared first bytes; ordered choice is decisive)","rule known_annotation_type: branches 7 and 10 are order-dependent (shared first bytes; ordered choice is decisive)","rule known_annotation_type: branches 9 and 10 are order-dependent (shared first bytes; ordered choice is decisive)"]},"default_entry":"formula","render":{},"derive":{},"tests":[],"docs":{"formula":"spaces? >> nodes.as(:formula) >> spaces?","formula_node":"Top-level nodes, DSL ordered-choice order (cascade before plain\nreaction, annotations before bare molecule).","cascade":"Two or more chained reactions: the transform promotes the chain\nto a ReactionCascade (first leg + arrow/products segments).","reaction":"Reactants, arrow (with optional above/below conditions), products.","arrow":"arrow.as(:arrow) {kind, above?, below?} — leading spaces ride the\narrow, conditions attach with no gap.","molecule":"A molecule: stereochemistry prefix, optional coefficient, units.","stereo_prefix":"`(R)-` style prefixes; a/b are the alpha/beta shorthand.","coefficient":"Coefficient: digits only when an element or bracket follows (else\nthe digits are subscripts/rings). Captured as {value}.","unit":"Units: prefixed (isotope) atoms, bracket groups, hydrogen\n(bare-digit subscripts allowed), plain atoms.","prefixed_atom":"Prefix isotope: ^14C or _14C — the marker binds to the atom, the\ncore semantic fix over AsciiMath. Captured with the marker.","group":"Bracket group: ( ... ) [ ... ] { ... } with optional multiplicity.","group_unit":"Inside a group: reactions, molecules, electron configs, embedded\nmath and quoted text recurse (DSL group_node set).","hydrogen_atom":"Hydrogen: bare digits after H are subscripts (H cannot ring-\nclose), but H must not steal the H of He, Hf...","atom_suffix":"Suffix markers consume `_` / `^` but capture marker + value\n(AtomBuilder strips the marker).","charge":"Charges: 2+, +2, 2 (bare digits fall through to the same capture).","oxidation_state":"Oxidation state: (IV), (II)...","lewis_prefix":"Lewis markers: leading colons = lone pairs; trailing dots =\nradical electrons.","atom_annotation":"Atom annotations, DSL order: coordinates, parity, multiplicity,\ntitle, fractional coordinates.","chirality_annotation":"Tetrahedral parity classes (SMILES @/@@ semantics, spelled\nexplicitly beside the CIP @R/@S label).","bond":"Bonds capture under their kind name (transform contract), longest\nfirst so `##` beats `#`; `->` is reserved for reactions.","crystal":"crystal[Name](a=X,b=Y,sg=SG){atoms with @f(x,y,z)}","spectrum":"spectrum[type](params){peak data}","calculation":"calc(method/basis){key: value units}","zmatrix":"zmatrix{lines}","mechanism":"mechanism{steps}","electron_config":"`1s2 2s2 2p6` — at least two orbital terms (DSL repeat(2)).","embedded_math":"Backtick-delimited Plurimath/AsciiMath source (delimiters stripped\nby the transform).","annotated_molecule":"Free-form text uses \"...\" delimiters, matching AsciiMath; the\ncapture includes the quotes (TextNormaliser strips them).\n@name(\"aspirin\") @cas(\"50-78-2\") @meta(\"k\",\"v\") — the annotation\nvocabulary rides the molecule.","metadata_annotation":"Two quoted args, comma-separated; keys distinguish metadata from\nregular annotations.","simple_annotation":"Known types (name, inchi, ...) handled specially by the transform;\nany other lowercase word becomes a property."},"source":"grammar AsciiChemCore version \"0.4.0\" {\n ## The AsciiChem syntax — full grammar, DSL-shaped captures\n ## (P1b, asciichem-ruby#96). Capture names and nesting mirror\n ## GrammarRules exactly so the PARG tree adapts to the same shape\n ## TransformRules consumes: sequence captures merge into one hash\n ## (parens to attach `as`), repetitions stay arrays, unwrapped\n ## rules let their captures ride into the parent context. Absent\n ## optionals omit their key (the tree adapter reinstates the ones\n ## transform patterns require, e.g. group multiplicity). Structural\n ## rules (molecule/reaction/group/annotated_molecule) are captured\n ## and the adapter unwraps them: PARG merge semantics for captured\n ## sequences with embedded repetitions yield arrays, and uncaptured\n ## sequence rules drop captures non-deterministically in reps\n ## (parsanol findings).\n ##\n ## Language notes (P0 findings): entries reference rules; sequences\n ## take no trailing capture (wrap in parens); [ ] is the option\n ## syntax so literal brackets are strings; until is exclusive\n ## (restate the closer); strict PEG — no repetition backtracking.\n\n entry formula: formula\n\n ## spaces? >> nodes.as(:formula) >> spaces?\n formula = [ 1*( ws ) ] ( formula_node 0*( [ 1*( ws ) ] formula_node ) ) as formula [ 1*( ws ) ]\n\n ws = ( \" \" / %x09 / %x0A / %x0D )\n\n ## Top-level nodes, DSL ordered-choice order (cascade before plain\n ## reaction, annotations before bare molecule).\n formula_node = cascade / reaction / electron_config / crystal / spectrum / calculation / zmatrix / mechanism / annotated_molecule / molecule / embedded_math / ( \"\\\"\" until \"\\\"\" \"\\\"\" ) as text_run\n\n # -- reactions ---------------------------------------------------------\n\n ## Two or more chained reactions: the transform promotes the chain\n ## to a ReactionCascade (first leg + arrow/products segments).\n cascade = ( ( reaction ) as first ( 1*( cascade_segment ) ) as segments ) as cascade\n\n cascade_segment = ( arrow [ 1*( ws ) ] ( terms as products ) ) as segment\n\n ## Reactants, arrow (with optional above/below conditions), products.\n reaction = ( ( terms as reactants ) arrow [ 1*( ws ) ] ( terms as products ) ) as reaction\n\n terms = molecule 0*( plus_term )\n\n plus_term = ( [ 1*( ws ) ] \"+\" [ 1*( ws ) ] molecule ) as plus_term\n\n ## arrow.as(:arrow) {kind, above?, below?} — leading spaces ride the\n ## arrow, conditions attach with no gap.\n arrow = ( [ 1*( ws ) ] ( arrow_kind ) as kind [ condition as above ] [ condition as below ] ) as arrow\n\n arrow_kind = ( \"<=>\" / \"<->\" / \"->\" / \"<-\" )\n\n condition = ( \"[\" ( until \"]\" ) as text ( \"]\" ) as condition_close ) as condition\n\n ## A molecule: stereochemistry prefix, optional coefficient, units.\n molecule = ( [ stereo_prefix ] [ coefficient ] ( 1*( unit / bond ) ) as units ) as molecule\n\n ## `(R)-` style prefixes; a/b are the alpha/beta shorthand.\n stereo_prefix = \"(\" ( stereo_letter ) as stereo \")\" \"-\"\n\n stereo_letter = ( \"alpha\" / \"beta\" / \"R\" / \"S\" / \"E\" / \"Z\" / \"α\" / \"β\" / \"a\" / \"b\" )\n\n ## Coefficient: digits only when an element or bracket follows (else\n ## the digits are subscripts/rings). Captured as {value}.\n coefficient = ( ( 1*(%x30-39) &( \"(\" / \"[\" / \"{\" / %x41-5A ) ) as value ) as coefficient\n\n ## Units: prefixed (isotope) atoms, bracket groups, hydrogen\n ## (bare-digit subscripts allowed), plain atoms.\n unit = prefixed_atom / group / hydrogen_atom / plain_atom\n\n ## Prefix isotope: ^14C or _14C — the marker binds to the atom, the\n ## core semantic fix over AsciiMath. Captured with the marker.\n prefixed_atom = ( [ lewis_prefix ] ( ( \"^\" / \"_\" ) 1*(%x30-39) ) as isotope ( %x41-5A [ %x61-7A ] ) as element atom_suffix [ lewis_radicals ] [ ( 1*(%x30-39) ) as ring_closures ] atom_annotation ) as atom\n\n ## Bracket group: ( ... ) [ ... ] { ... } with optional multiplicity.\n group = ( group_open ( 1*( group_unit ) ) as group_nodes group_close [ ( \"_\" 1*(%x30-39) ) as multiplicity ] ) as group\n\n group_open = ( \"(\" / \"[\" / \"{\" ) as open_bracket\n\n group_close = ( \")\" / \"]\" / \"}\" ) as close_bracket\n\n ## Inside a group: reactions, molecules, electron configs, embedded\n ## math and quoted text recurse (DSL group_node set).\n group_unit = reaction / electron_config / molecule / group / embedded_math / ( \"\\\"\" until \"\\\"\" \"\\\"\" ) as group_text_run\n\n ## Hydrogen: bare digits after H are subscripts (H cannot ring-\n ## close), but H must not steal the H of He, Hf...\n hydrogen_atom = ( [ lewis_prefix ] ( \"H\" ) as element !( %x61-7A ) [ ( \"_\" subscript_value / 1*(%x30-39) ) as subscript ] [ ( \"^\" superscript_value ) as superscript ] [ lewis_radicals ] atom_annotation ) as atom\n\n plain_atom = ( [ lewis_prefix ] ( %x41-5A [ %x61-7A ] ) as element atom_suffix [ lewis_radicals ] [ ( 1*(%x30-39) ) as ring_closures ] atom_annotation ) as atom\n\n ## Suffix markers consume `_` / `^` but capture marker + value\n ## (AtomBuilder strips the marker).\n atom_suffix = [ ( \"_\" subscript_value ) as subscript ] [ ( \"^\" superscript_value ) as superscript ]\n\n subscript_value = ( \"{\" until \"}\" \"}\" ) / 1*(%x30-39)\n\n superscript_value = ( \"{\" until \"}\" \"}\" ) / ( oxidation_state / charge )\n\n ## Charges: 2+, +2, 2 (bare digits fall through to the same capture).\n charge = ( 1*(%x30-39) [ \"+\" / \"-\" ] ) / ( ( \"+\" / \"-\" ) [ 1*(%x30-39) ] )\n\n ## Oxidation state: (IV), (II)...\n oxidation_state = \"(\" 1*( %x49 / %x56 / %x58 / %x4C / %x43 / %x44 ) \")\"\n\n ## Lewis markers: leading colons = lone pairs; trailing dots =\n ## radical electrons.\n lewis_prefix = 1*( \":\" ) as lone_pairs\n\n lewis_radicals = 1*( \".\" ) as radical_electrons\n\n ## Atom annotations, DSL order: coordinates, parity, multiplicity,\n ## title, fractional coordinates.\n atom_annotation = [ coordinate_annotation ] [ parity_annotation ] [ chirality_annotation ] [ multiplicity_annotation ] [ title_annotation ] [ fractional_annotation ]\n\n coordinate_annotation = \"@(\" ( float_number ) as x2 \",\" ( float_number ) as y2 [ \",\" ( float_number ) as z2 ] ( \")\" ) as coord_close\n\n parity_annotation = \"@\" ( \"R\" / \"S\" ) as atom_parity\n\n ## Tetrahedral parity classes (SMILES @/@@ semantics, spelled\n ## explicitly beside the CIP @R/@S label).\n chirality_annotation = \"@\" ( \"TH1\" / \"TH2\" ) as chirality\n\n multiplicity_annotation = \"@m(\" ( 1*(%x30-39) ) as spin_multiplicity ( \")\" ) as spin_close\n\n title_annotation = \"@t(\" \"\\\"\" ( until \"\\\"\" ) as atom_title ( \"\\\"\" ) as title_quote ( \")\" ) as title_close\n\n fractional_annotation = \"@f(\" ( float_number ) as x_fract \",\" ( float_number ) as y_fract \",\" ( float_number ) as z_fract ( \")\" ) as fract_close\n\n float_number = [ \"-\" ] 1*(%x30-39) [ \".\" 0*(%x30-39) ]\n\n # -- bonds ---------------------------------------------------------------\n\n ## Bonds capture under their kind name (transform contract), longest\n ## first so `##` beats `#`; `->` is reserved for reactions.\n bond = ( \"/\" as direction_up ) / ( \"\\\\\" as direction_down ) / ( \"##\" as quadruple ) / ( \">-\" as wedge ) / ( \"-<\" as hash ) / ( \"~>\" as dative ) / ( \"~~\" as wavy ) / ( \"#\" as triple ) / ( \"=\" as double ) / ( \"-\" as single )\n\n # -- beyond-formulas constructs -----------------------------------------\n\n ## crystal[Name](a=X,b=Y,sg=SG){atoms with @f(x,y,z)}\n crystal = ( \"crystal\" [ ( \"[\" ( until \"]\" ) as crystal_name ( \"]\" ) as crystal_name_close ) ] [ ( \"(\" ( until \")\" ) as crystal_params ( \")\" ) as crystal_params_close ) ] [ ( \"{\" ( until \"}\" ) as crystal_body ( \"}\" ) as crystal_body_close ) ] ) as crystal_node\n\n ## spectrum[type](params){peak data}\n spectrum = ( \"spectrum\" [ ( \"[\" ( until \"]\" ) as spectrum_type ( \"]\" ) as spectrum_type_close ) ] [ ( \"(\" ( until \")\" ) as spectrum_params ( \")\" ) as spectrum_params_close ) ] [ ( \"{\" ( until \"}\" ) as spectrum_body ( \"}\" ) as spectrum_body_close ) ] ) as spectrum_node\n\n ## calc(method/basis){key: value units}\n calculation = ( \"calc\" [ ( \"(\" ( until \")\" ) as calc_params ( \")\" ) as calc_params_close ) ] [ ( \"{\" ( until \"}\" ) as calc_body ( \"}\" ) as calc_body_close ) ] ) as calc_node\n\n ## zmatrix{lines}\n zmatrix = ( \"zmatrix\" [ ( \"{\" ( until \"}\" ) as zmatrix_body ( \"}\" ) as zmatrix_body_close ) ] ) as zmatrix_node\n\n ## mechanism{steps}\n mechanism = ( \"mechanism\" [ ( \"{\" ( until \"}\" ) as mechanism_body ( \"}\" ) as mechanism_body_close ) ] ) as mechanism_node\n\n # -- electron configuration ----------------------------------------------\n\n ## `1s2 2s2 2p6` — at least two orbital terms (DSL repeat(2)).\n electron_config = 2*( orbital \"^\" ( 1*(%x30-39) ) as occupancy [ 1*( ws ) ] ) as electron_config\n\n orbital = ( 1*(%x30-39) ( \"s\" / \"p\" / \"d\" / \"f\" / \"g\" / \"h\" ) ) as orbital\n\n # -- embedded math and text ------------------------------------------------\n\n ## Backtick-delimited Plurimath/AsciiMath source (delimiters stripped\n ## by the transform).\n embedded_math = ( \"`\" ( until \"`\" ) as math_source ( \"`\" ) as math_close ) as embedded_math\n\n ## Free-form text uses \"...\" delimiters, matching AsciiMath; the\n ## capture includes the quotes (TextNormaliser strips them).\n\n # -- molecule annotations ---------------------------------------------------\n\n ## @name(\"aspirin\") @cas(\"50-78-2\") @meta(\"k\",\"v\") — the annotation\n ## vocabulary rides the molecule.\n annotated_molecule = ( ( molecule ) as mol ( 1*( annotation_item ) ) as annotations ) as annotated_molecule\n\n annotation_item = ( [ 1*( ws ) ] ( metadata_annotation / simple_annotation ) ) as annotation_item\n\n ## Two quoted args, comma-separated; keys distinguish metadata from\n ## regular annotations.\n metadata_annotation = \"@meta(\" \"\\\"\" ( until \"\\\"\" ) as meta_key \"\\\"\" \",\" \"\\\"\" ( until \"\\\"\" ) as meta_value ( \"\\\"\" ) as meta_value_close ( \")\" ) as meta_close\n\n ## Known types (name, inchi, ...) handled specially by the transform;\n ## any other lowercase word becomes a property.\n simple_annotation = \"@\" ( ( known_annotation_type / property_name ) as ann_type \"(\" \"\\\"\" ( until \"\\\"\" ) as ann_value ( \"\\\"\" ) as ann_value_close ( \")\" ) as ann_close )\n\n known_annotation_type = ( \"name\" / \"title\" / \"formula\" / \"label\" / \"inchi\" / \"smiles\" / \"cas\" / \"iupac\" / \"cid\" / \"chebi\" )\n\n property_name = 1*( %x61-7A )\n\n test formula {\n accept \"H_2O\"\n accept \"H2O\"\n accept \"Ca^2+\"\n accept \"SO_4^2-\"\n accept \"C1-C-C-C-C-C1\"\n accept \"C12-C-C1-C2\"\n accept \"CH_3-CH_2-OH\"\n accept \"2H_2 + O_2 -> 2H_2O\"\n accept \"N_2 + 3H_2 <=>[Fe][400C] 2NH_3\"\n accept \"A ->[heat] B ->[cool] C\"\n accept \"A -> B -> C -> D\"\n accept \"Ca(OH)_2\"\n accept \"Fe(IV)\"\n accept \"_14C\"\n accept \"^14C\"\n accept \"CC(=O)OC1=CC=CC=C1C(=O)O\"\n accept \"Ca^2+ + CO_3^2- -> CaCO_3\"\n accept \"(R)-CH_3CH(OH)COOH\"\n accept \":O\"\n accept \"1s^2 2s^2 2p^6\"\n accept \"`x^2`\"\n accept \"\\\"nitrogen gas\\\"\"\n accept \"crystal[NaCl](a=5.64,sg=Fm-3m){Na@f(0,0,0)}\"\n accept \"spectrum[nmr](type=1H){1.2: 3H s \\\"CH3\\\"}\"\n accept \"calc(b3lyp){energy: -234.5}\"\n accept \"zmatrix{ C1 H2 C1 1.09 }\"\n accept \"mechanism{ step1: A -> B }\"\n accept \"H_2O @name(\\\"water\\\") @cas(\\\"7732-18-5\\\")\"\n accept \"H_2O @meta(\\\"source\\\",\\\"handbook\\\")\"\n accept \" H_2O \"\n accept \"C@(10,20)@R@m(2)@t(\\\"C1\\\")\"\n reject \"\"\n reject \"???\"\n reject \"H_\"\n reject \"C(=O\"\n reject \"+ H_2O\"\n reject \"crystal[NaCl\"\n reject \"zmatrix{ C1 \"\n reject \"@name(\\\"x\\\") H_2O\"\n }\n}\n","source_hash":"a94cfb4f59936a381e9974e8c141c661e56a25babe46d64b0a288bfd0b4b3e3d","compiler_version":"1.3.94","checksum":"sha256:2891ce4d795e13bdbfe556f50174e2575fa5798f13e8300e402b2a05f68c05e5"}
@@ -32,7 +32,7 @@ module AsciiChem
32
32
  class PargEngine
33
33
  # Version the vendored artifact was built from; the load fails
34
34
  # loudly on drift so the engine never runs a stale grammar.
35
- PARG_GRAMMAR_VERSION = '0.3.1'
35
+ PARG_GRAMMAR_VERSION = '0.4.0'
36
36
 
37
37
  ARTIFACT_PATH = File.expand_path('parg/asciichem.json', __dir__)
38
38
 
@@ -64,6 +64,7 @@ module AsciiChem
64
64
  parts << "#{coord})"
65
65
  end
66
66
  parts << "@#{atom.atom_parity}" if atom.atom_parity
67
+ parts << "@#{atom.chirality}" if atom.chirality
67
68
  parts << "@m(#{atom.spin_multiplicity})" if atom.spin_multiplicity
68
69
  parts << %(@t("#{atom.atom_title}")) if atom.atom_title
69
70
  if atom.x_fract && atom.y_fract && atom.z_fract
@@ -83,6 +84,11 @@ module AsciiChem
83
84
  end
84
85
 
85
86
  def visit_bond(bond)
87
+ # Directional single bonds spell the direction instead of the
88
+ # plain dash (the round-trip contract: text re-parses to the
89
+ # same model).
90
+ return { up: '/', down: '\\' }.fetch(bond.direction) if bond.direction
91
+
86
92
  bond.ascii
87
93
  end
88
94
 
@@ -263,7 +263,9 @@ module AsciiChem
263
263
  # by the reaction arrow)
264
264
  # wavy `~~` (resonance / delocalised)
265
265
  rule(:bond) do
266
- str('##').as(:quadruple) |
266
+ str('/').as(:direction_up) |
267
+ str('\\').as(:direction_down) |
268
+ str('##').as(:quadruple) |
267
269
  str('>-').as(:wedge) |
268
270
  str('-<').as(:hash) |
269
271
  str('~>').as(:dative) |
@@ -306,6 +308,7 @@ module AsciiChem
306
308
  rule(:atom_annotation) do
307
309
  coordinate_annotation.maybe >>
308
310
  parity_annotation.maybe >>
311
+ chirality_annotation.maybe >>
309
312
  multiplicity_annotation.maybe >>
310
313
  atom_title_annotation.maybe >>
311
314
  fractional_annotation.maybe
@@ -315,6 +318,12 @@ module AsciiChem
315
318
  str('@') >> (str('R') | str('S')).as(:atom_parity)
316
319
  end
317
320
 
321
+ # Tetrahedral parity classes (SMILES @/@@ semantics, spelled
322
+ # explicitly to stay unambiguous beside the CIP @R/@S label).
323
+ rule(:chirality_annotation) do
324
+ str('@') >> (str('TH1') | str('TH2')).as(:chirality)
325
+ end
326
+
318
327
  rule(:coordinate_annotation) do
319
328
  str('@(') >>
320
329
  float_number.as(:x2) >> str(',') >>
@@ -11,9 +11,11 @@ module AsciiChem
11
11
  class ValenceCheck < Base
12
12
  register :valence
13
13
 
14
+ # Runs over the perception pass's graph view: every atom's bond
15
+ # order counts ALL its bonds (rings and branches included — the
16
+ # linear chain approximation missed both).
14
17
  def run(formula)
15
18
  diagnostics = []
16
- # Walk molecules so we know which atom is the bonding context.
17
19
  walk(formula) do |node|
18
20
  next unless node.is_a?(AsciiChem::Model::Molecule)
19
21
 
@@ -25,34 +27,17 @@ module AsciiChem
25
27
  private
26
28
 
27
29
  def check_molecule(molecule, diagnostics)
28
- # Walk the molecule's nodes; pair each atom with the bonds
29
- # immediately adjacent to it in the chain.
30
- bond_count = 0
31
- molecule.children.each do |node|
32
- case node
33
- when AsciiChem::Model::Bond
34
- bond_count += bond_order(node)
35
- when AsciiChem::Model::Atom
36
- check_atom(node, bond_count, diagnostics)
37
- bond_count = 0
38
- when AsciiChem::Model::Group
39
- # Group's internal bonds aren't visible here; reset.
40
- bond_count = 0
41
- end
30
+ atoms, edges = AsciiChem::Structure::Graph.build(molecule)
31
+ atoms.each_with_index do |atom, index|
32
+ check_atom(atom, bond_order_sum(index, edges), diagnostics)
42
33
  end
43
34
  end
44
35
 
45
- def bond_order(bond)
46
- case bond.kind
47
- when :single then 1
48
- when :double then 2
49
- when :triple then 3
50
- when :quadruple then 4
51
- else 1
52
- end
36
+ def bond_order_sum(index, edges)
37
+ AsciiChem::Perception.bond_order_sum(index, edges)
53
38
  end
54
39
 
55
- def check_atom(atom, incoming_bond_order, diagnostics)
40
+ def check_atom(atom, load, diagnostics)
56
41
  max = AsciiChem::PeriodicTable.max_valence(atom.element)
57
42
  if max.nil?
58
43
  diagnostics << info(
@@ -62,30 +47,13 @@ module AsciiChem
62
47
  return
63
48
  end
64
49
 
65
- # Approximate: an atom with subscript has multiplicity, not
66
- # extra bonds. We use incoming_bond_order + |charge| as the
67
- # rough load.
68
- charge = atom.charge.to_s
69
- charge_value = parse_charge_magnitude(charge)
70
- load = incoming_bond_order + charge_value
71
50
  return if load <= max
72
51
 
73
52
  diagnostics << error(
74
- "Atom #{atom.element} has load #{load} (bond order #{incoming_bond_order} + charge #{charge_value}); max valence is #{max}",
53
+ "Atom #{atom.element} has bond order #{load}; max valence is #{max}",
75
54
  node: atom
76
55
  )
77
56
  end
78
-
79
- def parse_charge_magnitude(charge)
80
- return 0 if charge.nil? || charge.empty?
81
-
82
- match = charge.match(/\A(?<n>\d*)(?<sign>[+-])\z/) ||
83
- charge.match(/\A(?<sign>[+-])(?<n>\d*)\z/)
84
- return 0 unless match
85
-
86
- n = match[:n].empty? ? 1 : match[:n].to_i
87
- n
88
- end
89
57
  end
90
58
  end
91
59
  end
@@ -35,7 +35,7 @@ module AsciiChem
35
35
  :charge, :oxidation_state,
36
36
  :lone_pairs, :radical_electrons,
37
37
  :ring_closures, :aromatic, :hydrogens,
38
- :x2, :y2, :z2, :atom_parity,
38
+ :x2, :y2, :z2, :atom_parity, :chirality,
39
39
  :spin_multiplicity, :atom_title,
40
40
  :x_fract, :y_fract, :z_fract
41
41
 
@@ -60,6 +60,7 @@ module AsciiChem
60
60
  lone_pairs: nil, radical_electrons: nil,
61
61
  ring_closures: nil, aromatic: nil, hydrogens: nil,
62
62
  x2: nil, y2: nil, z2: nil, atom_parity: nil,
63
+ chirality: nil,
63
64
  spin_multiplicity: nil, atom_title: nil,
64
65
  x_fract: nil, y_fract: nil, z_fract: nil)
65
66
  @element = element
@@ -77,6 +78,7 @@ module AsciiChem
77
78
  @y2 = y2
78
79
  @z2 = z2
79
80
  @atom_parity = atom_parity
81
+ @chirality = chirality
80
82
  @spin_multiplicity = spin_multiplicity
81
83
  @atom_title = atom_title
82
84
  @x_fract = x_fract
@@ -92,6 +94,7 @@ module AsciiChem
92
94
  ring_closures: ring_closures, aromatic: aromatic,
93
95
  hydrogens: hydrogens,
94
96
  x2: x2, y2: y2, z2: z2, atom_parity: atom_parity,
97
+ chirality: chirality,
95
98
  spin_multiplicity: spin_multiplicity, atom_title: atom_title,
96
99
  x_fract: x_fract, y_fract: y_fract, z_fract: z_fract }
97
100
  end
@@ -4,40 +4,48 @@ module AsciiChem
4
4
  module Model
5
5
  # A bond between two adjacent nodes in a structural chain.
6
6
  class Bond < Node
7
- attr_accessor :kind
7
+ attr_accessor :kind, :direction
8
8
 
9
9
  KINDS = {
10
- single: { ascii: "-", mathml_entity: "-" },
11
- double: { ascii: "=", mathml_entity: "=" },
12
- triple: { ascii: "#", mathml_entity: "≡" },
13
- quadruple: { ascii: "##", mathml_entity: "≣" },
14
- wedge: { ascii: ">-", mathml_entity: "↑" },
15
- hash: { ascii: "-<", mathml_entity: "↓" },
16
- dative: { ascii: "~>", mathml_entity: "→" },
17
- wavy: { ascii: "~~", mathml_entity: "∼" },
18
- aromatic: { ascii: ":", mathml_entity: ":" }
10
+ single: { ascii: '-', mathml_entity: '-' },
11
+ double: { ascii: '=', mathml_entity: '=' },
12
+ triple: { ascii: '#', mathml_entity: '≡' },
13
+ quadruple: { ascii: '##', mathml_entity: '≣' },
14
+ wedge: { ascii: '>-', mathml_entity: '↑' },
15
+ hash: { ascii: '-<', mathml_entity: '↓' },
16
+ dative: { ascii: '~>', mathml_entity: '→' },
17
+ wavy: { ascii: '~~', mathml_entity: '∼' },
18
+ aromatic: { ascii: ':', mathml_entity: ':' }
19
19
  }.freeze
20
20
 
21
21
  # CML wire order codes per bond kind. Single source of truth
22
22
  # for both adapter directions — to_canonical emits, from_canonical
23
23
  # parses. Adding a new bond kind = one entry here + one in KINDS.
24
24
  CML_ORDER_CODES = {
25
- single: "S", double: "D", triple: "T",
26
- quadruple: "Q", wedge: "W", hash: "H",
27
- dative: "A", wavy: "V"
25
+ single: 'S', double: 'D', triple: 'T',
26
+ quadruple: 'Q', wedge: 'W', hash: 'H',
27
+ dative: 'A', wavy: 'V'
28
28
  }.freeze
29
29
  KIND_BY_CML_ORDER = CML_ORDER_CODES.invert.freeze
30
30
 
31
31
  # Subset that carries stereo meaning (wedge = out of page,
32
32
  # hash = into page). Used for <bondStereo> elements.
33
- CML_STEREO_CODES = { wedge: "W", hash: "H" }.freeze
33
+ CML_STEREO_CODES = { wedge: 'W', hash: 'H' }.freeze
34
34
 
35
- def initialize(kind: :single)
35
+ def initialize(kind: :single, direction: nil)
36
36
  @kind = kind
37
+ @direction = direction
38
+ end
39
+
40
+ # SMILES directional-bond orientation (`/` up, `\` down) on the
41
+ # single bonds flanking a stereogenic double bond. Orthogonal to
42
+ # the bond kind; only :single carries it.
43
+ def direction
44
+ @direction if kind == :single
37
45
  end
38
46
 
39
47
  def value_attributes
40
- { kind: kind }
48
+ { kind: kind, direction: direction }
41
49
  end
42
50
 
43
51
  def ascii
@@ -49,7 +57,7 @@ module AsciiChem
49
57
  end
50
58
 
51
59
  def cml_order_code
52
- CML_ORDER_CODES.fetch(kind, "S")
60
+ CML_ORDER_CODES.fetch(kind, 'S')
53
61
  end
54
62
 
55
63
  def cml_stereo_code