#ifndef MANTID_GEOMETRY_MATRIXVECTORPAIRPARSER_H_ #define MANTID_GEOMETRY_MATRIXVECTORPAIRPARSER_H_ #include "MantidGeometry/DllConfig.h" #include "MantidGeometry/Crystal/MatrixVectorPair.h" #include "MantidGeometry/Crystal/V3R.h" #include "MantidKernel/Exception.h" #include <functional> #include <boost/spirit/include/qi.hpp> namespace Mantid { namespace Geometry { using ParsedRationalNumber = boost::fusion::vector<int, boost::optional<int>>; class MatrixVectorPairBuilder { public: MatrixVectorPairBuilder() { // Fill direction map m_directions.emplace("x", V3R(1, 0, 0)); m_directions.emplace("y", V3R(0, 1, 0)); m_directions.emplace("z", V3R(0, 0, 1)); // Initialize all other members correctly and consistently reset(); } /// Construct and return the actual matrix/vector pair, the rational matrix /// components are castd to T. template <typename T> MatrixVectorPair<T, V3R> getMatrixVectorPair() const { if (m_currentRow < 2) { throw std::runtime_error( "Less than three rows were processed by MatrixVectorPairBuilder."); } std::vector<T> typedMatrixElements; for (auto rb : m_matrixRows) { typedMatrixElements.push_back(boost::rational_cast<T>((rb).x())); typedMatrixElements.push_back(boost::rational_cast<T>((rb).y())); typedMatrixElements.push_back(boost::rational_cast<T>((rb).z())); } Kernel::Matrix<T> mat(typedMatrixElements); return MatrixVectorPair<T, V3R>(mat, m_vector); } /// Set the current factor, which is a rational number. Depending on whether a /// direction definition follows, it's processed differently later on. void setCurrentFactor(const ParsedRationalNumber &rationalNumberComponents) { int numerator = boost::fusion::at_c<0>(rationalNumberComponents); boost::optional<int> denominator = boost::fusion::at_c<1>(rationalNumberComponents); if (denominator.is_initialized()) { if (denominator.get() == 0) { throw std::runtime_error( "Zero denominator is not allowed in MatrixVectorPair-strings."); } m_currentFactor = RationalNumber(numerator, denominator.get()); } else { m_currentFactor = RationalNumber(numerator); } } /// Set the direction vector to one of the stored values that correspond to /// x, y or z. void setCurrentDirection(const std::string &vector) { m_currentDirection = m_directions[vector]; } /// Make the current sign negative. void setCurrentSignNegative() { m_currentSign = -1; } /// Make the current sign positive. void setCurrentSignPositive() { m_currentSign = 1; } /** * Adds currently stored state to the parse result * * This function takes the current factor, sign and direction that were * assigned using the respective functions and adds to the stored * intermediate result. * * If the current direction is (0,0,0) then the currently processed * component is only a rational number that should go into the vector * component of the row that's currently being assembled. Otherwise * the direction vector should be multiplied by the rational number * and added to the matrix row. * * After that, the current state is reset so that the next part of the current * component can be processed. */ void addCurrentStateToResult() { if (m_currentRow > m_matrixRows.size()) { throw std::runtime_error( "MatrixVectorPair can not have more than 3 rows."); } m_currentFactor *= m_currentSign; if (m_currentDirection != V3R(0, 0, 0)) { m_matrixRows[m_currentRow] += (m_currentDirection * m_currentFactor); } else { m_vector[m_currentRow] += m_currentFactor; } resetState(); } /// Advance to the next row of the matrix/vector pair. void advanceRow() { ++m_currentRow; resetState(); } /// Completely reset the builder, including stored preliminary results. void reset() { resetState(); resetAccumulatedResults(); } /// Reset the current state, i.e. the state representing the current row. void resetState() { m_currentFactor = RationalNumber(1); m_currentDirection = V3R(0, 0, 0); m_currentSign = 1; } /// Reset all accumulated results. void resetAccumulatedResults() { m_matrixRows = std::vector<V3R>(3); m_vector = V3R(0, 0, 0); m_currentRow = 0; } private: std::map<std::string, V3R> m_directions; std::vector<V3R> m_matrixRows; V3R m_vector; RationalNumber m_currentFactor; V3R m_currentDirection; int m_currentSign; size_t m_currentRow; }; using boost::spirit::qi::grammar; using boost::spirit::qi::rule; /** MatrixVectorPairParser MatrixVectorPairParser can parse matrix/vector pairs in Jones faithful notation of the form: a/b-x, cy, -z The resulting matrix/vector pair can be retrieved through a templated method that lets the caller decide on the numeric type stored in the matrix. The vector is currently always V3R. To reuse the internally constructed boost::spirit-based parser, simply instantiate MatrixVectorPairParser once and use the parse-method. @author Michael Wedel, ESS @date 02/11/2015 Copyright © 2015 ISIS Rutherford Appleton Laboratory, NScD Oak Ridge National Laboratory & European Spallation Source This file is part of Mantid. Mantid is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Mantid is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. File change history is stored at: <https://github.com/mantidproject/mantid> Code Documentation is available at: <http://doxygen.mantidproject.org> */ template <typename Iterator> class MatrixVectorPairParser : public grammar<Iterator, boost::spirit::qi::space_type> { public: MatrixVectorPairParser(MatrixVectorPairBuilder &builder) : MatrixVectorPairParser::base_type(m_parser) { using boost::spirit::unused_type; namespace qi = boost::spirit::qi; using qi::int_; using qi::uint_; using qi::lit; /* MSVC currently has some problems using std::bind in connection * with boost::spirit in some circumstances, but they can be circumvented * using lambda-expressions as a sort of call proxy. For consistency, * all semantic parse actions are formulated like that. */ auto positiveSignAction = [&builder](unused_type, unused_type, unused_type) { builder.setCurrentSignPositive(); }; auto negativeSignAction = [&builder](unused_type, unused_type, unused_type) { builder.setCurrentSignNegative(); }; auto currentFactorAction = [&builder]( const ParsedRationalNumber &rationalNumberComponents, unused_type, unused_type) { builder.setCurrentFactor(rationalNumberComponents); }; auto currentDirectionAction = [&builder](const std::string &s, unused_type, unused_type) { builder.setCurrentDirection(s); }; auto addCurrentStateToResultAction = [&builder](unused_type, unused_type, unused_type) { builder.addCurrentStateToResult(); }; auto advanceRowAction = [&builder](unused_type, unused_type, unused_type) { builder.advanceRow(); }; // Switch sign in the builder m_sign = lit('+')[positiveSignAction] | lit('-')[negativeSignAction]; // This matches a rational number (also things like -1/-2 -> 1/2) m_rational = (int_ >> -('/' >> int_))[currentFactorAction]; // Matches x, y or z. m_direction = (qi::string("x") | qi::string("y") | qi::string("z"))[currentDirectionAction]; /* A "component", which is either a rational number possibly followed by a * vector definition. Examples: * 2/3, -2/3, -2/-3, -4, 2, -2x, 3y, 4/5z, ... * Or a vector possibly preceded by a sign: * z, -y, +x */ m_component = (m_rational >> -m_direction) | (-m_sign >> m_direction); // One row of the matrix/vector pair can have many such "components". m_componentSeries = m_component[addCurrentStateToResultAction] >> *(m_component[addCurrentStateToResultAction]); // The entire matrix/vector pair is defined by three comma separated of // those component strings. m_parser = (m_componentSeries >> lit(',')[advanceRowAction] >> m_componentSeries >> lit(',')[advanceRowAction] >> m_componentSeries); } rule<Iterator, boost::spirit::qi::space_type> m_sign, m_rational, m_direction, m_component, m_componentSeries, m_parser; }; /// Tries to parse the given string. Throws a ParseError-exception if there is /// unparsable string left at the end. template <typename T> MatrixVectorPair<T, V3R> parseMatrixVectorPair(const std::string &matrixVectorString) { namespace qi = boost::spirit::qi; auto strIterator = matrixVectorString.cbegin(); auto strEnd = matrixVectorString.cend(); MatrixVectorPairBuilder builder; MatrixVectorPairParser<std::string::const_iterator> parser(builder); try { qi::phrase_parse(strIterator, strEnd, parser, qi::space); if (std::distance(strIterator, strEnd) > 0) { throw std::runtime_error("Additional characters at end of string: '" + std::string(strIterator, strEnd) + "'."); } } catch (std::runtime_error &builderError) { throw Kernel::Exception::ParseError("Parse error: " + std::string(builderError.what()), matrixVectorString, 0); } return builder.getMatrixVectorPair<T>(); } } // namespace Geometry } // namespace Mantid #endif /* MANTID_GEOMETRY_MATRIXVECTORPAIRPARSER_H_ */