.mw-parser-output .documentation,.mw-parser-output .documentation-metadata{border:1px solid var(--border-color-base,#a2a9b1);background-color:#ecfcf4;color:inhe
-- Module:SMILES2IUPAC/Parser
-- Parses a SMILES string into a plain graph:
-- { atoms = {...}, bonds = {...} }
--
-- PHASE 1 SUPPORT:
-- * organic-subset atoms without brackets:
-- B C N O P S F Cl Br I
-- * single/double/triple bonds:
-- - = #
-- * branches:
-- ( )
--
-- NOT YET SUPPORTED:
-- * bracket atoms [ ... ]
-- * ring closures / %nn
-- * aromatic atoms
-- * / \ : bond symbols
--
-- The parser is responsible only for converting SMILES into
-- a molecular graph. Naming rules belong in separate modules.
local Data = require('Module:SMILES2IUPAC/Data')
local p = {}
-- Bond orders used throughout the graph.
local BOND_ORDER = {
single = 1,
double = 2,
triple = 3,
['-'] = 1,
['='] = 2,
['#'] = 3
}
-- Try to read one atom symbol starting at position i.
-- Returns symbol, newIndex or nil.
local function readAtomSymbol(str, i)
for _, sym in ipairs(Data.twoLetterSymbols) do
if str:sub(i, i + #sym - 1) == sym then
return sym, i + #sym
end
end
local one = str:sub(i, i)
if Data.organicSubset[one] then
return one, i + 1
end
return nil, i
end
--- Parses a SMILES string.
-- @return graph table on success, or nil, errorMessage on failure.
function p.parse(smiles)
if type(smiles) ~= 'string' or smiles == '' then
return nil, 'empty SMILES string'
end
-- Strip surrounding whitespace.
smiles = smiles:match('^%s*(.-)%s*$')
if smiles == '' then
return nil, 'empty SMILES string'
end
local atoms = {}
local bonds = {}
local pos = 1
local len = #smiles
-- Atom index to return to after a branch closes.
local branchStack = {}
local currentAtom = nil
-- Bond order waiting to be applied to the next atom.
local pendingBondOrder = nil
-- Whether the previous token was an atom.
-- This lets us validate where bond symbols and branches occur.
local previousWasAtom = false
while pos <= len do
local c = smiles:sub(pos, pos)
-- Branch opening.
if c == '(' then
if not currentAtom then
return nil, ("branch '(' at position %d has no preceding atom"):format(pos)
end
if pendingBondOrder then
return nil, ("branch '(' at position %d follows a dangling bond symbol"):format(pos)
end
table.insert(branchStack, currentAtom)
previousWasAtom = false
pos = pos + 1
-- Branch closing.
elseif c == ')' then
if #branchStack == 0 then
return nil, ("unmatched ')' at position %d"):format(pos)
end
if pendingBondOrder then
return nil, ("branch ends after a dangling bond symbol at position %d"):format(pos)
end
currentAtom = table.remove(branchStack)
previousWasAtom = true
pos = pos + 1
-- Explicit bond.
elseif BOND_ORDER[c] then
if not currentAtom then
return nil, ("bond symbol '%s' at position %d has no preceding atom"):format(c, pos)
end
if pendingBondOrder then
return nil, ("two bond symbols in a row at position %d"):format(pos)
end
pendingBondOrder = BOND_ORDER[c]
previousWasAtom = false
pos = pos + 1
-- Unsupported stereochemical/aromatic bond symbols.
elseif c:match('[/\\:]') then
return nil, ("bond symbol '%s' (stereo/aromatic bond) not supported yet"):format(c)
-- Unsupported bracket atoms.
elseif c == '[' then
return nil, 'bracket atoms ([...]) are not supported yet'
-- Unsupported ring closures.
elseif c:match('%d') or c == '%' then
return nil, 'ring closures are not supported yet'
-- Unsupported aromatic atoms.
elseif c:match('[a-z]') then
return nil, ("aromatic atom '%s' not supported yet"):format(c)
-- Atom.
else
local sym, nextPos = readAtomSymbol(smiles, pos)
if not sym then
return nil, ("unrecognised character '%s' at position %d"):format(c, pos)
end
-- A new atom may follow another atom directly,
-- in which case the bond is implicitly single.
local newIdx = #atoms + 1
table.insert(atoms, {
element = sym
})
if currentAtom then
local order = pendingBondOrder or BOND_ORDER.single
table.insert(bonds, {
a = currentAtom,
b = newIdx,
order = order
})
elseif pendingBondOrder then
-- This should normally be caught by the bond-symbol
-- validation above, but keep this guard here.
return nil, 'bond has no starting atom'
end
pendingBondOrder = nil
currentAtom = newIdx
previousWasAtom = true
pos = nextPos
end
end
-- A branch must always be closed.
if #branchStack > 0 then
return nil, "unmatched '(' — branch never closed"
end
-- A bond symbol must always be followed by an atom.
if pendingBondOrder then
return nil, 'SMILES ends with a dangling bond symbol'
end
if #atoms == 0 then
return nil, 'no atoms found'
end
return {
atoms = atoms,
bonds = bonds
}
end
return p
Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.