Search code examples
c++boost-spiritboost-spirit-qi

How to refer to value from symbol table?


I can't figure out how to pass value from symbol table into the function.

template <typename Iterator>
class single_attribute_grammar : public qi::grammar<Iterator, AttributeData(), qi::blank_type>
{
public:
    single_attribute_grammar(const word_symbols &words) : single_attribute_grammar::base_type(single_attribute_rule)
    {
        auto attr_word = phx::bind(&AttributeData::word, qi::_val);
        auto grammar_word = phx::bind(&WordGrammar::word, qi::_1);
        auto attr_value = phx::bind(&AttributeData::value, qi::_val);
        single_attribute_rule = qi::lexeme[words[attr_word = grammar_word] > 
            qi::int_[attr_value = qi::_1] > (qi::space|qi::eoi)] >>
            qi::eps(phx::bind(verify_range, qi::_r1, qi::_val)); // <-- HERE is the problem
        BOOST_SPIRIT_DEBUG_NODE(single_attribute_rule);
    }

private:
    qi::rule<Iterator, AttributeData(), qi::blank_type> single_attribute_rule;
};

I would think I can refer to the value of the found key using qi::_r1 but the code doesn't compile:

main.cpp:72:31:   required from ‘single_attribute_grammar<Iterator>::single_attribute_grammar(const word_symbols&) [with Iterator = boost::spirit::classic::position_iterator2<boost::spirit::multi_pass<std::istreambuf_iterator<char, std::char_traits<char> > > >; word_symbols = boost::spirit::qi::symbols<char, WordGrammar>]’
main.cpp:87:16:   required from ‘all_attributes_grammar<Iterator>::all_attributes_grammar(const word_symbols&) [with Iterator = boost::spirit::classic::position_iterator2<boost::spirit::multi_pass<std::istreambuf_iterator<char, std::char_traits<char> > > >; word_symbols = boost::spirit::qi::symbols<char, WordGrammar>]’
main.cpp:130:62:   required from here
/usr/include/boost/spirit/home/support/context.hpp:180:13: error: static assertion failed: index_is_out_of_bounds
             BOOST_SPIRIT_ASSERT_MSG(
             ^
In file included from /usr/include/boost/spirit/home/qi/domain.hpp:18:0,
                 from /usr/include/boost/spirit/home/qi/meta_compiler.hpp:15,
                 from /usr/include/boost/spirit/home/qi/action/action.hpp:14,
                 from /usr/include/boost/spirit/home/qi/action.hpp:14,
                 from /usr/include/boost/spirit/home/qi.hpp:14,
                 from /usr/include/boost/spirit/include/qi.hpp:16,
                 from main.cpp:11:
/usr/include/boost/spirit/home/support/context.hpp:186:13: error: no type named ‘type’ in ‘struct boost::fusion::result_of::at_c<boost::fusion::cons<AttributeData&, boost::fusion::nil_>, 1>’
             type;
             ^~~~

In case it's helpful here is the MVCE.

#include <iomanip>
#include <string>
#include <vector>

#include <boost/variant.hpp>
#include <boost/optional/optional.hpp>

#define BOOST_SPIRIT_DEBUG

#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/qi_symbols.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_object.hpp> // construct
#include <boost/spirit/include/support_multi_pass.hpp>
#include <boost/spirit/include/classic_position_iterator.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/phoenix/bind.hpp>

namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace classic = boost::spirit::classic;
namespace phx = boost::phoenix;
namespace fusion = boost::fusion;

struct AttributeData
{
    std::string word;
    int value;
};

using AttributeVariant = boost::variant<
    AttributeData
>;

struct WordGrammar
{
    std::string word;
    int range_from;
    int range_to;
};


BOOST_FUSION_ADAPT_STRUCT(
    AttributeData,
    (std::string, word)
    (int, value)
)

using word_symbols = qi::symbols<char, WordGrammar>;

bool verify_range(const WordGrammar &grammar, const AttributeData &data) 
{
    if(data.value < grammar.range_from || data.value > grammar.range_to)
    {
        return false;
    }

    return true;
}

template <typename Iterator>
class single_attribute_grammar : public qi::grammar<Iterator, AttributeData(), qi::blank_type>
{
public:
    single_attribute_grammar(const word_symbols &words) : single_attribute_grammar::base_type(single_attribute_rule)
    {
        auto attr_word = phx::bind(&AttributeData::word, qi::_val);
        auto grammar_word = phx::bind(&WordGrammar::word, qi::_1);
        auto attr_value = phx::bind(&AttributeData::value, qi::_val);
        single_attribute_rule = qi::lexeme[words[attr_word = grammar_word] > 
            qi::int_[attr_value = qi::_1] > (qi::space|qi::eoi)] >>
            qi::eps(phx::bind(verify_range, qi::_r1, qi::_val)); // <-- HERE is the problem
        BOOST_SPIRIT_DEBUG_NODE(single_attribute_rule);
    }

private:
    qi::rule<Iterator, AttributeData(), qi::blank_type> single_attribute_rule;
};

template <typename Iterator>
class all_attributes_grammar : public qi::grammar<Iterator, std::vector<AttributeVariant>(), qi::blank_type>
{
public:
    all_attributes_grammar(const word_symbols &words) : all_attributes_grammar::base_type(line_attribute_vec_rule)
    , sag(words)
    {
        line_attribute_rule = (
            sag
        );
        BOOST_SPIRIT_DEBUG_NODE(line_attribute_rule);
        line_attribute_vec_rule = (line_attribute_rule % *qi::blank) > qi::eoi;
        BOOST_SPIRIT_DEBUG_NODE(line_attribute_vec_rule);
    }

private:
    single_attribute_grammar<Iterator> sag;
    qi::rule<Iterator, AttributeVariant(), qi::blank_type> line_attribute_rule;
    qi::rule<Iterator, std::vector<AttributeVariant>(), qi::blank_type> line_attribute_vec_rule;
};

int main()
{
    std::vector<AttributeVariant> value;
    std::string data{"N100 X-100 AC5"};
    std::istringstream input(data);

    // iterate over stream input
    typedef std::istreambuf_iterator<char> base_iterator_type;
    base_iterator_type in_begin(input);

    // convert input iterator to forward iterator, usable by spirit parser
    typedef boost::spirit::multi_pass<base_iterator_type> forward_iterator_type;
    forward_iterator_type fwd_begin = boost::spirit::make_default_multi_pass(in_begin);
    forward_iterator_type fwd_end;

    // wrap forward iterator with position iterator, to record the position
    typedef classic::position_iterator2<forward_iterator_type> pos_iterator_type;
    pos_iterator_type position_begin(fwd_begin, fwd_end);
    pos_iterator_type position_end;

    word_symbols sym;
    sym.add
    ("N", {"N", 1, 9999})
    ("X", {"X", -999, 999})
    ("AC", {"AC", -99, 999})
    ;

    all_attributes_grammar<pos_iterator_type> all_attr_gr(sym);

    try
    {
        qi::phrase_parse(position_begin, position_end, all_attr_gr, qi::blank, value);
    }
    catch (const qi::expectation_failure<pos_iterator_type>& e)
    {
        const classic::file_position_base<std::string>& pos = e.first.get_position();
        std::cout <<
            "Parse error at line " << pos.line << " column " << pos.column << ":" << std::endl <<
            "'" << e.first.get_currentline() << "'" << std::endl <<
            std::setw(pos.column) << " " << "^- here" << std::endl;
    }

    return 0;
}

Any ideas?


Solution

  • Lots of things to be simplified.

    • why is AttributeData adapted, when you use semantic actions instead? (see Boost Spirit: "Semantic actions are evil"?)
    • skipping whitespace is much more elegant when using a skipper. It's also logically contradictory to skip whitespace inside a lexeme (see Boost spirit skipper issues)
    • In particular, the skipping of whitespace here:

      line_attribute_vec_rule = (line_attribute_rule % *qi::blank) > qi::eoi;
      

      is completely impotent (because blanks are already skipped, the *qi::blank will never match any characters). The whole thing reduces to +line_attribute_rule.

      line_attribute_vec_rule = +line_attribute_rule > qi::eoi;
      

    Re: your answer

    Indeed, you can use more state in the rule. Instead, I'd simplify the AST to support your case. Say:

    struct AttrDef {
        std::string word;
        std::pair<int,int> range;
    };
    
    struct AttributeData {
        AttrDef def;
        int value;
    
        bool is_valid() const {
            return std::minmax({value, def.range.first, def.range.second}) == def.range;
        }
    };
    
    BOOST_FUSION_ADAPT_STRUCT(AttributeData, def, value)
    

    Now, single_attribute_grammar would not care about skipping, and be like:

    using attr_defs = qi::symbols<char, AttrDef>;
    
    template <typename Iterator>
    struct single_attribute_grammar : public qi::grammar<Iterator, AttributeData()> {
        single_attribute_grammar(const attr_defs &defs)
            : single_attribute_grammar::base_type(start) 
        {
            using namespace qi;
            attribute_data = defs >> int_;
            start         %= attribute_data [ _pass = is_valid_(_1) ];
    
            BOOST_SPIRIT_DEBUG_NODES((attribute_data));
        }
    
    private:
        phx::function<std::function<bool(AttributeData const&)> > is_valid_ {&AttributeData::is_valid};
        qi::rule<Iterator, AttributeData()> attribute_data;
        qi::rule<Iterator, AttributeData()> start;
    };
    

    As you can see, there's no state because I didn't use eps. That's right, I flew right in the face of my own guideline ("avoid semantic actions") for the simple reason that it avoids explicit state (instead using the exsting qi::_pass).

    Doing a lot of simplifications in main (specifically, using boost::spirit::istream_iterator rather than roll-your-own multi-pass adapting), I would arrive at this:

    Live On Coliru

    //#define BOOST_SPIRIT_DEBUG
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/spirit/include/classic_position_iterator.hpp>
    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/fusion/include/io.hpp>
    #include <iomanip>
    
    namespace qi      = boost::spirit::qi;
    namespace phx     = boost::phoenix;
    
    struct AttrDef {
        std::string word;
        std::pair<int,int> range;
    };
    
    struct AttributeData {
        AttrDef def;
        int value;
    
        bool is_valid() const {
            return std::minmax({value, def.range.first, def.range.second}) == def.range;
        }
    };
    
    BOOST_FUSION_ADAPT_STRUCT(AttributeData, def, value)
    
    static inline std::ostream& operator<<(std::ostream& os, const AttrDef& def) {
        return os << "AttrDef(" << def.word << ", " << def.range.first << ", " << def.range.second << ")";
    }
    
    using attr_defs = qi::symbols<char, AttrDef>;
    
    template <typename Iterator>
    struct single_attribute_grammar : public qi::grammar<Iterator, AttributeData()> {
        single_attribute_grammar(const attr_defs &defs)
            : single_attribute_grammar::base_type(start) 
        {
            using namespace qi;
            attribute_data = defs >> int_;
            start         %= attribute_data [ _pass = is_valid_(_1) ];
    
            BOOST_SPIRIT_DEBUG_NODES((attribute_data));
        }
    
    private:
        phx::function<std::function<bool(AttributeData const&)> > is_valid_ {&AttributeData::is_valid};
        qi::rule<Iterator, AttributeData()> attribute_data;
        qi::rule<Iterator, AttributeData()> start;
    };
    
    using AttributeVariant = boost::variant<AttributeData>;
    
    template <typename Iterator>
    class all_attributes_grammar : public qi::grammar<Iterator, std::vector<AttributeVariant>(), qi::blank_type>
    {
    public:
        all_attributes_grammar(const attr_defs &defs)
            : all_attributes_grammar::base_type(line_attribute_vec_rule),
              sag(defs)
        {
            line_attribute_rule = (
                sag
            );
            line_attribute_vec_rule = +line_attribute_rule > qi::eoi;
    
            BOOST_SPIRIT_DEBUG_NODES((line_attribute_rule)(line_attribute_vec_rule));
        }
    
    private:
        single_attribute_grammar<Iterator> sag;
        qi::rule<Iterator, AttributeVariant()>                              line_attribute_rule;
        qi::rule<Iterator, std::vector<AttributeVariant>(), qi::blank_type> line_attribute_vec_rule;
    };
    
    int main() {
        constexpr bool fail = false, succeed = true;
        struct _ { bool expect; std::string data; } const tests[] = {
             { succeed, "N100 X-100 AC5" },
             { fail,    ""               },
             { fail,    "  "             },
             { succeed, "N1"             },
             { succeed, " N1"            },
             { succeed, "N1 "            },
             { succeed, " N1 "           },
             { fail,    "N 1"            },
             { fail,    "N0"             },
             { succeed, "N9999"          },
             { fail,    "N10000"         },
        };
    
        for (auto test : tests) {
            std::istringstream input(test.data);
    
            typedef boost::spirit::classic::position_iterator2<boost::spirit::istream_iterator> pos_iterator_type;
            pos_iterator_type position_begin(boost::spirit::istream_iterator{input >> std::noskipws}, {}), position_end;
    
            attr_defs sym;
            sym.add
                ("N",  {"N", {1, 9999}})
                ("X",  {"X", {-999, 999}})
                ("AC", {"AC", {-99, 999}})
                ;
    
            all_attributes_grammar<pos_iterator_type> all_attr_gr(sym);
    
            try {
                std::vector<AttributeVariant> value;
                std::cout << " --------- '" << test.data << "'\n";
    
                bool actual = qi::phrase_parse(position_begin, position_end, all_attr_gr, qi::blank, value);
    
                std::cout << ((test.expect == actual)?"PASS":"FAIL");
    
                if (actual) {
                    std::cout << "\t";
                    for (auto& attr : value)
                        std::cout << boost::fusion::as_vector(boost::get<AttributeData>(attr)) << " ";
                    std::cout << "\n";
                } else {
                    std::cout << "\t(no valid parse)\n";
                }
            }
            catch (const qi::expectation_failure<pos_iterator_type>& e) {
                auto& pos = e.first.get_position();
                std::cout <<
                    "Parse error at line " << pos.line << " column " << pos.column << ":" << std::endl <<
                    "'" << e.first.get_currentline() << "'" << std::endl <<
                    std::setw(pos.column) << " " << "^- here" << std::endl;
            }
    
            if (position_begin != position_end)
                std::cout << " -> Remaining '" << std::string(position_begin, position_end) << "'\n";
        }
    }
    

    Prints:

     --------- 'N100 X-100 AC5'
    PASS    (AttrDef(N, 1, 9999) 100) (AttrDef(X, -999, 999) -100) (AttrDef(AC, -99, 999) 5) 
     --------- ''
    PASS    (no valid parse)
     --------- '  '
    PASS    (no valid parse)
     -> Remaining '  '
     --------- 'N1'
    PASS    (AttrDef(N, 1, 9999) 1) 
     --------- ' N1'
    PASS    (AttrDef(N, 1, 9999) 1) 
     --------- 'N1 '
    PASS    (AttrDef(N, 1, 9999) 1) 
     --------- ' N1 '
    PASS    (AttrDef(N, 1, 9999) 1) 
     --------- 'N 1'
    PASS    (no valid parse)
     -> Remaining 'N 1'
     --------- 'N0'
    PASS    (no valid parse)
     -> Remaining 'N0'
     --------- 'N9999'
    PASS    (AttrDef(N, 1, 9999) 9999) 
     --------- 'N10000'
    PASS    (no valid parse)
     -> Remaining 'N10000'