nx_chem_smiles.nx
buildroot/runtime/nx_chem_smiles.nx
about
nx_chem_smiles.nx -- C2.1 + C2.2 milestones: basic SMILES parser.
Parses the organic-subset SMILES grammar (Daylight 1988 / OpenSMILES
2016) into the MolGraph type from nx_chem_molecule.nx. Memory-safe
by construction: every read is bounded against the input length;
every error path sets is_valid=0 + structured err_code + err_pos.
Supported (C2.1 + C2.2):
- Organic-subset aliphatic atoms outside brackets: B, C, N, O, P, S, F, Cl, Br, I
- Aromatic lowercase atoms outside brackets: b, c, n, o, p, s
- Wildcard: *
- **Full 118-element bracket atoms** (C2.2): H..Og, all IUPAC element symbols
- Bracket attributes:
isotope -- leading digits, e.g. [13C]
chirality -- @ (CCW) or @@ (CW) on atom; stored in Atom.stereo (C2.2)
H count -- H[count], e.g. [CH4], [NH4+]
charge -- +, -, ++, --, +n, -n
atom map -- :n, stored in Atom.map_num (C2.2)
- Bond types: -, =, #, :, . (default = single between aliphatic, aromatic between two aromatics)
- **Bond stereo: /, \\** -- stored in Bond.stereo as NX_BSTEREO_UP/DOWN (C2.2)
- Branches: ( and )
- **Ring closures: 0..9 + %nn (two-digit, 00..99)** (C2.2)
Honest gaps (deferred to C2.3+):
- E/Z resolution from /, \\ (C2.1 stores raw UP/DOWN; CIP-rule resolution is C2.3)
- Atropisomer + enhanced-stereo (C2.4)
- Implicit-H valence inference (C2.3 -- h_count stays -1 for non-bracket atoms)
- 6-aromaticity-tuple model dispatch (C2.5 -- this milestone sets one Daylight bit; tuple slot present)
- Canonical SMILES output (C2.3)
- Fuzzer 10^9 harness (C2.9)
EXCEED axes hit (landscape doc references):
E2 -- memory-safe parser by construction (bounded buffer reads + structured error paths)
E4 -- per-atom + per-bond stereo slots populated from SMILES input (C2.2)
E7 -- first-class radicals + charges + isotopes + full 118-element bracket support
E11 -- no format lock-in (output is native MolGraph)
nx_safety_envelope:
intended_use: SMILES -> MolGraph parser for cheminformatics pipeline
sil_target: SIL1
dependencies 2 imports · 21 importers
diagram shows first 10 each side; +0 more imports, +11 more importers in the complete lists below.
imports: nx_kernel_v2.nxnx_chem_molecule.nx
imported by: nx_chem_adulterant_db.nxnx_chem_adulterant_db_test.nxnx_chem_descriptors_test.nxnx_chem_fingerprint_test.nxnx_chem_isotope_pattern_test.nxnx_chem_mass_test.nxnx_chem_morgan_test.nxnx_chem_peak_list_test.nxnx_chem_report_csv_test.nxnx_chem_report_json_test.nxnx_chem_sildenafil_probe_test.nxnx_chem_smiles_emit.nxnx_chem_smiles_emit_test.nxnx_chem_smiles_test.nxnx_chem_stereo_test.nxnx_chem_tadalafil_probe_test.nxnx_chem_valence_test.nxnx_supplement.nxnx_supplement_batch.nxnx_supplement_check.nxnx_supplement_csv.nx
structs
| none |
consts
| 50 | const NX_C_0: nx_int = 0x30 |
| 51 | const NX_C_9: nx_int = 0x39 |
| 52 | const NX_C_A_UP: nx_int = 0x41 |
| 53 | const NX_C_Z_UP: nx_int = 0x5A |
| 54 | const NX_C_A_LO: nx_int = 0x61 |
| 55 | const NX_C_Z_LO: nx_int = 0x7A |
| 56 | const NX_C_LBRACKET: nx_int = 0x5B |
| 57 | const NX_C_RBRACKET: nx_int = 0x5D |
| 58 | const NX_C_LPAREN: nx_int = 0x28 |
| 59 | const NX_C_RPAREN: nx_int = 0x29 |
| 60 | const NX_C_PLUS: nx_int = 0x2B |
| 61 | const NX_C_MINUS: nx_int = 0x2D |
| 62 | const NX_C_EQ: nx_int = 0x3D |
| 63 | const NX_C_HASH: nx_int = 0x23 |
| 64 | const NX_C_COLON: nx_int = 0x3A |
| 65 | const NX_C_DOT: nx_int = 0x2E |
| 66 | const NX_C_SLASH: nx_int = 0x2F |
| 67 | const NX_C_BSLASH: nx_int = 0x5C |
| 68 | const NX_C_STAR: nx_int = 0x2A |
| 69 | const NX_C_AT: nx_int = 0x40 |
| 70 | const NX_C_PCT: nx_int = 0x25 |
| 71 | const NX_C_H_UP: nx_int = 0x48 |
| 72 | const NX_C_B_UP: nx_int = 0x42 |
| 73 | const NX_C_C_UP: nx_int = 0x43 |
| 74 | const NX_C_N_UP: nx_int = 0x4E |
| 75 | const NX_C_O_UP: nx_int = 0x4F |
| 76 | const NX_C_F_UP: nx_int = 0x46 |
| 77 | const NX_C_P_UP: nx_int = 0x50 |
| 78 | const NX_C_S_UP: nx_int = 0x53 |
| 79 | const NX_C_I_UP: nx_int = 0x49 |
| 80 | const NX_C_K_UP: nx_int = 0x4B |
| 81 | const NX_C_B_LO: nx_int = 0x62 |
| 82 | const NX_C_C_LO: nx_int = 0x63 |
| 83 | const NX_C_N_LO: nx_int = 0x6E |
| 84 | const NX_C_O_LO: nx_int = 0x6F |
| 85 | const NX_C_P_LO: nx_int = 0x70 |
| 86 | const NX_C_S_LO: nx_int = 0x73 |
| 87 | const NX_C_L_LO: nx_int = 0x6C |
| 88 | const NX_C_R_LO: nx_int = 0x72 |
functions
| 93 | func smi_is_digit(c: nx_int) -> nx_int |
| 99 | func smi_is_upper(c: nx_int) -> nx_int |
| 105 | func smi_is_lower(c: nx_int) -> nx_int called by 1: smi_parse_bracket |
| 116 | func smi_bracket_symbol_two_char(c1: nx_int, c2: nx_int) -> nx_int called by 1: smi_parse_bracket |
| 290 | func smi_bracket_symbol_single_char(c1: nx_int) -> nx_int called by 1: smi_parse_bracket |
| 311 | func smi_aromatic_lower_to_z(c: nx_int) -> nx_int |
| 327 | func smi_organic_atom(c1: nx_int, c2: nx_int, consumed_out: *nx_int) -> nx_int called by 1: nx_chem_parse_smiles |
| 355 | func smi_parse_bracket( |
| 556 | func smi_connect(m: *MolGraph, prev: nx_int, new_idx: nx_int, pending: nx_int, pending_bstereo: nx_int) -> nx_int |
| 594 | func nx_chem_smiles_populate_stereo_neighbors(m: *MolGraph) -> nx_int called by 1: nx_chem_parse_smiles |
| 676 | func nx_chem_parse_smiles(src: *u8, n: nx_int) -> *MolGraph |
| 932 | func nx_chem_parse_smiles_cstr(src: *u8) -> *MolGraph calls 1: nx_chem_parse_smiles |