разбор химической формулы со смесями элементов

Я хотел бы использовать boost :: spirit для извлечения стехиометрии соединений, состоящих из нескольких элементов, из грубой формулы. В данном соединении мой парсер должен уметь различать три вида паттернов химических элементов:

  • природный элемент из смеси изотопов в естественном изобилии
  • чистый изотоп
  • смесь изотопов в неприродном изобилии

Эти шаблоны затем используются для анализа таких следующих соединений:

  • «C» -> природный углерод, изготовленный из C [12] и C [13] в естественном изобилии
  • «CH4» -> метан из природного углерода и водорода
  • «C2H {H [1] (0,8) H [2] (0,2)} 6» -> этан, изготовленный из природного C и неприродного H, изготовленный из 80% водорода и 20% дейтерия
  • «U [235]» -> чистый уран 235

Очевидно, что структуры химических элементов могут быть в любом порядке (например, CH [1] 4 и H [1] 4C …) и частоты.

Я написал свой синтаксический анализатор, который довольно близок к выполнению работы, но я все еще сталкиваюсь с одной проблемой.

Вот мой код:

template <typename Iterator>
struct ChemicalFormulaParser : qi::grammar<Iterator,isotopesMixture(),qi::locals<isotopesMixture,double>>
{
ChemicalFormulaParser(): ChemicalFormulaParser::base_type(_start)
{

namespace phx = boost::phoenix;

// Semantic action for handling the case of pure isotope
phx::function<PureIsotopeBuilder> const build_pure_isotope = PureIsotopeBuilder();
// Semantic action for handling the case of pure isotope mixture
phx::function<IsotopesMixtureBuilder> const build_isotopes_mixture = IsotopesMixtureBuilder();
// Semantic action for handling the case of natural element
phx::function<NaturalElementBuilder> const build_natural_element = NaturalElementBuilder();

phx::function<UpdateElement> const update_element = UpdateElement();

// XML database that store all the isotopes of the periodical table
ChemicalDatabaseManager<Isotope>* imgr=ChemicalDatabaseManager<Isotope>::Instance();
const auto& isotopeDatabase=imgr->getDatabase();
// Loop over the database to the spirit symbols for the isotopes names (e.g. H[1],C[14]) and the elements (e.g. H,C)
for (const auto& isotope : isotopeDatabase) {
_isotopeNames.add(isotope.second.getName(),isotope.second.getName());
_elementSymbols.add(isotope.second.getProperty<std::string>("symbol"),isotope.second.getProperty<std::string>("symbol"));
}

_mixtureToken = "{" >> +(_isotopeNames >> "(" >> qi::double_ >> ")") >> "}";
_isotopesMixtureToken = (_elementSymbols[qi::_a=qi::_1] >> _mixtureToken[qi::_b=qi::_1])[qi::_pass=build_isotopes_mixture(qi::_val,qi::_a,qi::_b)];

_pureIsotopeToken = (_isotopeNames[qi::_a=qi::_1])[qi::_pass=build_pure_isotope(qi::_val,qi::_a)];
_naturalElementToken = (_elementSymbols[qi::_a=qi::_1])[qi::_pass=build_natural_element(qi::_val,qi::_a)];

_start = +( ( (_isotopesMixtureToken | _pureIsotopeToken | _naturalElementToken)[qi::_a=qi::_1] >>
(qi::double_|qi::attr(1.0))[qi::_b=qi::_1])[qi::_pass=update_element(qi::_val,qi::_a,qi::_b)] );

}

//! Defines the rule for matching a prefix
qi::symbols<char,std::string> _isotopeNames;
qi::symbols<char,std::string> _elementSymbols;

qi::rule<Iterator,isotopesMixture()> _mixtureToken;
qi::rule<Iterator,isotopesMixture(),qi::locals<std::string,isotopesMixture>> _isotopesMixtureToken;

qi::rule<Iterator,isotopesMixture(),qi::locals<std::string>> _pureIsotopeToken;
qi::rule<Iterator,isotopesMixture(),qi::locals<std::string>> _naturalElementToken;

qi::rule<Iterator,isotopesMixture(),qi::locals<isotopesMixture,double>> _start;
};

По сути, каждый отдельный шаблон элемента может быть надлежащим образом проанализирован с их соответствующим семантическим действием, которое в результате выдает карту между изотопами, которые строят соединение, и их соответствующей стехиометрией. Проблема начинается при разборе следующего соединения:

CH{H[1](0.9)H[2](0.4)}

В таком случае семантическое действие build_isotopes_mixture верните false, потому что 0,9 + 0,4 не имеет смысла для суммы отношения. Следовательно, я ожидал и хотел, чтобы мой синтаксический анализатор потерпел неудачу для этого соединения. Однако из-за _start Правило, которое использует альтернативный оператор для трех типов паттернов химических элементов, парсеру удается его проанализировать, 1) выбрасывая {H[1](0.9)H[2](0.4)} часть 2) сохраняя предыдущее H 3) разбор с помощью _naturalElementToken, Разве моя грамматика недостаточно ясна для того, чтобы быть выраженной как парсер? Как использовать альтернативный оператор таким образом, чтобы при обнаружении вхождения но дал false при запуске семантического действия парсер останавливается?

3

Решение

Как использовать альтернативный оператор таким образом, чтобы при обнаружении вхождения, но при выдаче ложного значения при выполнении семантического действия анализатор останавливался?

В общем, вы достигаете этого, добавляя точка ожидания для предотвращения возврата.

В этом случае вы фактически «объединяете» несколько задач:

  1. соответствующий вход
  2. интерпретация согласованного ввода
  3. проверка согласованного ввода

Дух превосходит при соответствующем вводе, имеет
отличные возможности, когда дело доходит до устного перевода (в основном в смысле создания AST). Тем не менее, все становится «противным» с проверкой на лету.

Совет, который я часто повторяю, — это рассмотреть разделяя проблемы когда возможно. Я бы подумал

  1. сначала построение прямого представления AST для ввода,
  2. преобразование / нормализация / расширение / канонизация в более удобное или значимое представление области
  3. сделать окончательные проверки на результат

Это дает вам наиболее выразительный код, в то же время сохраняя его легко обслуживаемым.

Поскольку я недостаточно хорошо разбираюсь в проблемной области, а пример кода не является достаточно полным, чтобы вызвать его, я не буду пытаться дать полный пример того, что я имею в виду. Вместо этого я постараюсь сделать набросок точка ожидания подход, о котором я упоминал в самом начале.

Образец макета для компиляции

Это заняло больше всего времени. (Подумайте над тем, чтобы помочь людям, которые собираются вам помочь)

Жить на Колиру

#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <map>

namespace qi = boost::spirit::qi;

struct DummyBuilder {
using result_type = bool;

template <typename... Ts>
bool operator()(Ts&&...) const { return true; }
};

struct PureIsotopeBuilder     : DummyBuilder {  };
struct IsotopesMixtureBuilder : DummyBuilder {  };
struct NaturalElementBuilder  : DummyBuilder {  };
struct UpdateElement          : DummyBuilder {  };

struct Isotope {
std::string getName() const { return _name; }

Isotope(std::string const& name = "unnamed", std::string const& symbol = "?") : _name(name), _symbol(symbol) { }

template <typename T> std::string getProperty(std::string const& name) const {
if (name == "symbol")
return _symbol;
throw std::domain_error("no such property (" + name + ")");
}

private:
std::string _name, _symbol;
};

using MixComponent    = std::pair<Isotope, double>;
using isotopesMixture = std::list<MixComponent>;

template <typename Isotope>
struct ChemicalDatabaseManager {
static ChemicalDatabaseManager* Instance() {
static ChemicalDatabaseManager s_instance;
return &s_instance;
}

auto& getDatabase() { return _db; }
private:
std::map<int, Isotope> _db {
{ 1, { "H[1]",   "H" } },
{ 2, { "H[2]",   "H" } },
{ 3, { "Carbon", "C" } },
{ 4, { "U[235]", "U" } },
};
};

template <typename Iterator>
struct ChemicalFormulaParser : qi::grammar<Iterator, isotopesMixture(), qi::locals<isotopesMixture, double> >
{
ChemicalFormulaParser(): ChemicalFormulaParser::base_type(_start)
{
using namespace qi;
namespace phx = boost::phoenix;

phx::function<PureIsotopeBuilder>     build_pure_isotope;     // Semantic action for handling the case of pure isotope
phx::function<IsotopesMixtureBuilder> build_isotopes_mixture; // Semantic action for handling the case of pure isotope mixture
phx::function<NaturalElementBuilder>  build_natural_element;  // Semantic action for handling the case of natural element
phx::function<UpdateElement>          update_element;

// XML database that store all the isotopes of the periodical table
ChemicalDatabaseManager<Isotope>* imgr = ChemicalDatabaseManager<Isotope>::Instance();
const auto& isotopeDatabase=imgr->getDatabase();

// Loop over the database to the spirit symbols for the isotopes names (e.g. H[1],C[14]) and the elements (e.g. H,C)
for (const auto& isotope : isotopeDatabase) {
_isotopeNames.add(isotope.second.getName(),isotope.second.getName());
_elementSymbols.add(isotope.second.template getProperty<std::string>("symbol"),isotope.second.template getProperty<std::string>("symbol"));
}

_mixtureToken         = "{" >> +(_isotopeNames >> "(" >> double_ >> ")") >> "}";
_isotopesMixtureToken = (_elementSymbols[_a=_1] >> _mixtureToken[_b=_1])[_pass=build_isotopes_mixture(_val,_a,_b)];

_pureIsotopeToken     = (_isotopeNames[_a=_1])[_pass=build_pure_isotope(_val,_a)];
_naturalElementToken  = (_elementSymbols[_a=_1])[_pass=build_natural_element(_val,_a)];

_start = +( ( (_isotopesMixtureToken | _pureIsotopeToken | _naturalElementToken)[_a=_1] >>
(double_|attr(1.0))[_b=_1]) [_pass=update_element(_val,_a,_b)] );
}

private:
//! Defines the rule for matching a prefix
qi::symbols<char, std::string> _isotopeNames;
qi::symbols<char, std::string> _elementSymbols;

qi::rule<Iterator, isotopesMixture()> _mixtureToken;
qi::rule<Iterator, isotopesMixture(), qi::locals<std::string, isotopesMixture> > _isotopesMixtureToken;

qi::rule<Iterator, isotopesMixture(), qi::locals<std::string> > _pureIsotopeToken;
qi::rule<Iterator, isotopesMixture(), qi::locals<std::string> > _naturalElementToken;

qi::rule<Iterator, isotopesMixture(), qi::locals<isotopesMixture, double> > _start;
};

int main() {
using It = std::string::const_iterator;
ChemicalFormulaParser<It> parser;
for (std::string const input : {
"C",                        // --> natural carbon made of C[12] and C[13] in natural abundance
"CH4",                      // --> methane made of natural carbon and hydrogen
"C2H{H[1](0.8)H[2](0.2)}6", // --> ethane made of natural C and non-natural H made of 80% of hydrogen and 20% of deuterium
"C2H{H[1](0.9)H[2](0.2)}6", // --> invalid mixture (total is 110%?)
"U[235]",                   // --> pure uranium 235
})
{
std::cout << " ============= '" << input << "' ===========\n";
It f = input.begin(), l = input.end();
isotopesMixture mixture;
bool ok = qi::parse(f, l, parser, mixture);

if (ok)
std::cout << "Parsed successfully\n";
else
std::cout << "Parse failure\n";

if (f != l)
std::cout << "Remaining input unparsed: '" << std::string(f, l) << "'\n";
}
}

Который, как дано, просто печатает

 ============= 'C' ===========
Parsed successfully
============= 'CH4' ===========
Parsed successfully
============= 'C2H{H[1](0.8)H[2](0.2)}6' ===========
Parsed successfully
============= 'C2H{H[1](0.9)H[2](0.2)}6' ===========
Parsed successfully
============= 'U[235]' ===========
Parsed successfully

Основные пометки:

  1. местные жители не нужны, просто используйте обычные заполнители:

    _mixtureToken         = "{" >> +(_isotopeNames >> "(" >> double_ >> ")") >> "}";
    _isotopesMixtureToken = (_elementSymbols >> _mixtureToken) [ _pass=build_isotopes_mixture(_val, _1, _2) ];
    
    _pureIsotopeToken     = _isotopeNames [ _pass=build_pure_isotope(_val, _1) ];
    _naturalElementToken  = _elementSymbols [ _pass=build_natural_element(_val, _1) ];
    
    _start = +(
    ( (_isotopesMixtureToken | _pureIsotopeToken | _naturalElementToken) >>
    (double_|attr(1.0)) ) [ _pass=update_element(_val, _1, _2) ]
    );
    
    // ....
    qi::rule<Iterator, isotopesMixture()> _mixtureToken;
    qi::rule<Iterator, isotopesMixture()> _isotopesMixtureToken;
    qi::rule<Iterator, isotopesMixture()> _pureIsotopeToken;
    qi::rule<Iterator, isotopesMixture()> _naturalElementToken;
    qi::rule<Iterator, isotopesMixture()> _start;
    
  2. вы захотите обработать конфликты между именами / символами (возможно, просто установив приоритеты одного или другого)

  3. соответствующие компиляторы потребуют template квалификатор (если я полностью не угадал вашу структуру данных, в этом случае я не знаю, что аргумент шаблона для ChemicalDatabaseManager должен был иметь в виду).

    Подсказка, MSVC не соответствует стандартам компилятора

Жить на Колиру

Эскиз точки ожидания

Предполагая, что «веса» нужно добавить до 100% внутри _mixtureToken Правило, мы можем сделать build_isotopes_micture «не пустышка» и добавьте проверку:

struct IsotopesMixtureBuilder {
bool operator()(isotopesMixture&/* output*/, std::string const&/* elementSymbol*/, isotopesMixture const& mixture) const {
using namespace boost::adaptors;

// validate weights total only
return std::abs(1.0 - boost::accumulate(mixture | map_values, 0.0)) < 0.00001;
}
};

Однако, как вы заметили, это может помешать возвращению. Вместо этого вы можете / утверждать / что любая полная смесь составляет до 100%:

_mixtureToken         = "{" >> +(_isotopeNames >> "(" >> double_ >> ")") >> "}" > eps(validate_weight_total(_val));

С чем-то вроде

struct ValidateWeightTotal {
bool operator()(isotopesMixture const& mixture) const {
using namespace boost::adaptors;

bool ok = std::abs(1.0 - boost::accumulate(mixture | map_values, 0.0)) < 0.00001;
return ok;
// or perhaps just :
return ok? ok : throw InconsistentsWeights {};
}

struct InconsistentsWeights : virtual std::runtime_error {
InconsistentsWeights() : std::runtime_error("InconsistentsWeights") {}
};
};

Жить на Колиру

#include <boost/fusion/adapted/std_pair.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/range/numeric.hpp>
#include <map>

namespace qi = boost::spirit::qi;

struct DummyBuilder {
using result_type = bool;

template <typename... Ts>
bool operator()(Ts&&...) const { return true; }
};

struct PureIsotopeBuilder     : DummyBuilder {  };
struct NaturalElementBuilder  : DummyBuilder {  };
struct UpdateElement          : DummyBuilder {  };

struct Isotope {
std::string getName() const { return _name; }

Isotope(std::string const& name = "unnamed", std::string const& symbol = "?") : _name(name), _symbol(symbol) { }

template <typename T> std::string getProperty(std::string const& name) const {
if (name == "symbol")
return _symbol;
throw std::domain_error("no such property (" + name + ")");
}

private:
std::string _name, _symbol;
};

using MixComponent    = std::pair<Isotope, double>;
using isotopesMixture = std::list<MixComponent>;

struct IsotopesMixtureBuilder {
bool operator()(isotopesMixture&/* output*/, std::string const&/* elementSymbol*/, isotopesMixture const& mixture) const {
using namespace boost::adaptors;

// validate weights total only
return std::abs(1.0 - boost::accumulate(mixture | map_values, 0.0)) < 0.00001;
}
};

struct ValidateWeightTotal {
bool operator()(isotopesMixture const& mixture) const {
using namespace boost::adaptors;

bool ok = std::abs(1.0 - boost::accumulate(mixture | map_values, 0.0)) < 0.00001;
return ok;
// or perhaps just :
return ok? ok : throw InconsistentsWeights {};
}

struct InconsistentsWeights : virtual std::runtime_error {
InconsistentsWeights() : std::runtime_error("InconsistentsWeights") {}
};
};

template <typename Isotope>
struct ChemicalDatabaseManager {
static ChemicalDatabaseManager* Instance() {
static ChemicalDatabaseManager s_instance;
return &s_instance;
}

auto& getDatabase() { return _db; }
private:
std::map<int, Isotope> _db {
{ 1, { "H[1]",   "H" } },
{ 2, { "H[2]",   "H" } },
{ 3, { "Carbon", "C" } },
{ 4, { "U[235]", "U" } },
};
};

template <typename Iterator>
struct ChemicalFormulaParser : qi::grammar<Iterator, isotopesMixture()>
{
ChemicalFormulaParser(): ChemicalFormulaParser::base_type(_start)
{
using namespace qi;
namespace phx = boost::phoenix;

phx::function<PureIsotopeBuilder>     build_pure_isotope;     // Semantic action for handling the case of pure isotope
phx::function<IsotopesMixtureBuilder> build_isotopes_mixture; // Semantic action for handling the case of pure isotope mixture
phx::function<NaturalElementBuilder>  build_natural_element;  // Semantic action for handling the case of natural element
phx::function<UpdateElement>          update_element;
phx::function<ValidateWeightTotal>    validate_weight_total;

// XML database that store all the isotopes of the periodical table
ChemicalDatabaseManager<Isotope>* imgr = ChemicalDatabaseManager<Isotope>::Instance();
const auto& isotopeDatabase=imgr->getDatabase();

// Loop over the database to the spirit symbols for the isotopes names (e.g. H[1],C[14]) and the elements (e.g. H,C)
for (const auto& isotope : isotopeDatabase) {
_isotopeNames.add(isotope.second.getName(),isotope.second.getName());
_elementSymbols.add(isotope.second.template getProperty<std::string>("symbol"), isotope.second.template getProperty<std::string>("symbol"));
}

_mixtureToken         = "{" >> +(_isotopeNames >> "(" >> double_ >> ")") >> "}" > eps(validate_weight_total(_val));
_isotopesMixtureToken = (_elementSymbols >> _mixtureToken) [ _pass=build_isotopes_mixture(_val, _1, _2) ];

_pureIsotopeToken     = _isotopeNames [ _pass=build_pure_isotope(_val, _1) ];
_naturalElementToken  = _elementSymbols [ _pass=build_natural_element(_val, _1) ];

_start = +(
( (_isotopesMixtureToken | _pureIsotopeToken | _naturalElementToken) >>
(double_|attr(1.0)) ) [ _pass=update_element(_val, _1, _2) ]
);
}

private:
//! Defines the rule for matching a prefix
qi::symbols<char, std::string> _isotopeNames;
qi::symbols<char, std::string> _elementSymbols;

qi::rule<Iterator, isotopesMixture()> _mixtureToken;
qi::rule<Iterator, isotopesMixture()> _isotopesMixtureToken;
qi::rule<Iterator, isotopesMixture()> _pureIsotopeToken;
qi::rule<Iterator, isotopesMixture()> _naturalElementToken;
qi::rule<Iterator, isotopesMixture()> _start;
};

int main() {
using It = std::string::const_iterator;
ChemicalFormulaParser<It> parser;
for (std::string const input : {
"C",                        // --> natural carbon made of C[12] and C[13] in natural abundance
"CH4",                      // --> methane made of natural carbon and hydrogen
"C2H{H[1](0.8)H[2](0.2)}6", // --> ethane made of natural C and non-natural H made of 80% of hydrogen and 20% of deuterium
"C2H{H[1](0.9)H[2](0.2)}6", // --> invalid mixture (total is 110%?)
"U[235]",                   // --> pure uranium 235
}) try
{
std::cout << " ============= '" << input << "' ===========\n";
It f = input.begin(), l = input.end();
isotopesMixture mixture;
bool ok = qi::parse(f, l, parser, mixture);

if (ok)
std::cout << "Parsed successfully\n";
else
std::cout << "Parse failure\n";

if (f != l)
std::cout << "Remaining input unparsed: '" << std::string(f, l) << "'\n";
} catch(std::exception const& e) {
std::cout << "Caught exception '" << e.what() << "'\n";
}
}

Печать

 ============= 'C' ===========
Parsed successfully
============= 'CH4' ===========
Parsed successfully
============= 'C2H{H[1](0.8)H[2](0.2)}6' ===========
Parsed successfully
============= 'C2H{H[1](0.9)H[2](0.2)}6' ===========
Caught exception 'boost::spirit::qi::expectation_failure'
============= 'U[235]' ===========
Parsed successfully
3

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector