Search code examples
c++boost-spirit-qi

boost spirit reporting semantic error


I am playing with boost.spirit library and I cannot manage to report a simple error message from my semantic action.

// supported parameter types (int or quoted strings)
parameter = bsqi::int_ | bsqi::lexeme[L'"' > *(bsqi_coding::char_ - L'"') > L'"'];
parameter.name("parameter");

// comma separator list of parameters (or no parameters)
parameters = -(parameter % L',');
parameters.name("parameters");

// action with parameters
action = (Actions > L'(' > parameters > L')')[bsqi::_pass = boost::phoenix::bind(&ValidateAction, bsqi::_1, bsqi::_2)];
action.name("action");

The Actions is just a symbol table (boost::spirit::qi::symbols). The attribute of parameters is std::vector of boost::variant which describes the parameters types. I would like to produces a meaningful error message within semantic action ValidateAction with also indicating position within input what is wrong. If I just assign _pass to false, parsing ends but the error message is something like 'expecting ' and not that e.g. 2nd parameter has wrong type (expected int instead of string).

Somewhere I read that I can throw an exception from my semantic action, but the problem is that I didn't find whether and how I can access iterators from parsed values. For example I wanted to use expectation_failure exception so my error handler automatically is called, but I need to pass iterators to the exception which seems impossible.

Is there any nice way how to report semantic failures with more detailed information except returning just false?


Solution

  • I'd use filepos_iterator and just throw an exception, so you have complete control over the reporting.

    Let me see what I can come up with in the remaining 15 minutes I have

    Ok, took a little bit more time but think it's an instructive demo:

    Live On Coliru

    #include <boost/fusion/adapted.hpp>
    #include <boost/fusion/include/io.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/spirit/include/support_line_pos_iterator.hpp>
    #include <boost/spirit/repository/include/qi_iter_pos.hpp>
    #include <boost/lexical_cast.hpp>
    
    namespace qi = boost::spirit::qi;
    namespace qr = boost::spirit::repository::qi;
    namespace px = boost::phoenix;
    namespace qi_coding = boost::spirit::ascii;
    using It = boost::spirit::line_pos_iterator<std::string::const_iterator>;
    
    namespace ast {
        enum actionid { f_unary, f_binary };
        enum param_type { int_param, string_param };
    
        static inline std::ostream& operator<<(std::ostream& os, actionid id) {
            switch(id) {
                case f_unary:      return os << "f_unary";
                case f_binary:     return os << "f_binary";
                default:           return os << "(unknown)";
            } }
        static inline std::ostream& operator<<(std::ostream& os, param_type t) {
            switch(t) {
                case int_param:    return os << "integer";
                case string_param: return os << "string";
                default:           return os << "(unknown)";
            } }
    
    
        using param_value = boost::variant<int, std::string>;
        struct parameter {
            It position;
            param_value value;
    
            friend std::ostream& operator<<(std::ostream& os, parameter const& p) { return os << p.value; }
        };
        using parameters = std::vector<parameter>;
    
        struct action {
            /*
             *action() = default;
             *template <typename Sequence> action(Sequence const& seq) { boost::fusion::copy(seq, *this); }
             */
            actionid id;
            parameters params;
        };
    }
    
    namespace std {
        static inline std::ostream& operator<<(std::ostream& os, ast::parameters const& v) {
            std::copy(v.begin(), v.end(), std::ostream_iterator<ast::parameter>(os, " "));
            return os;
        }
    }
    
    BOOST_FUSION_ADAPT_STRUCT(ast::action, id, params)
    BOOST_FUSION_ADAPT_STRUCT(ast::parameter, position, value)
    
    struct BadAction : std::exception {
        It          _where;
        std::string _what;
        BadAction(It it, std::string msg) : _where(it), _what(std::move(msg)) {}
        It where() const { return _where; }
        char const* what() const noexcept { return _what.c_str(); }
    };
    
    struct ValidateAction {
        std::map<ast::actionid, std::vector<ast::param_type> > const specs {
            { ast::f_unary,  { ast::int_param } },
            { ast::f_binary, { ast::int_param, ast::string_param } },
        };
    
        ast::action operator()(It source, ast::action parsed) const {
            auto check = [](ast::parameter const& p, ast::param_type expected_type) {
                if (p.value.which() != expected_type) {
                    auto name = boost::lexical_cast<std::string>(expected_type);
                    throw BadAction(p.position, "Type mismatch (expecting " + name + ")");
                }
            };
    
            int i;
            try {
                auto& formals = specs.at(parsed.id);
                auto& actuals = parsed.params;
                auto  arity   = formals.size();
    
                for (i=0; i<arity; ++i)
                    check(actuals.at(i), formals.at(i));
    
                if (actuals.size() > arity) 
                    throw BadAction(actuals.at(arity).position, "Excess parameters");
            } catch(std::out_of_range const&) { 
                throw BadAction(source, "Missing parameter #" + std::to_string(i+1)); 
            }
            return parsed;
        }
    };
    
    template <typename It, typename Skipper = qi::space_type>
    struct Parser : qi::grammar<It, ast::action(), Skipper> {
        Parser() : Parser::base_type(start) {
            using namespace qi;
            parameter  = qr::iter_pos >> (int_ | lexeme['"' >> *~qi_coding::char_('"') >> '"']);
            parameters = -(parameter % ',');
            action     = actions_ >> '(' >> parameters >> ')';
            start      = (qr::iter_pos >> action) [ _val = validate_(_1, _2) ];
    
            BOOST_SPIRIT_DEBUG_NODES((parameter)(parameters)(action))
        }
      private:
        qi::rule<It, ast::action(),     Skipper> start, action;
        qi::rule<It, ast::parameters(), Skipper> parameters;
        qi::rule<It, ast::parameter(),  Skipper> parameter;
        px::function<ValidateAction> validate_;
    
        struct Actions : qi::symbols<char, ast::actionid> {
            Actions() { this->add("f_unary", ast::f_unary)("f_binary", ast::f_binary); }
        } actions_;
    
    };
    
    int main() {
        for (std::string const input : {
                // good
                "f_unary( 0 )",
                "f_binary ( 47, \"hello\")",
                // errors
                "f_binary ( 47, \"hello\") bogus",
                "f_unary ( 47, \"hello\") ",
                "f_binary ( 47, \r\n      7) ",
            })
        {
            std::cout << "-----------------------\n";
            Parser<It> p;
            It f(input.begin()), l(input.end());
    
            auto printErrorContext = [f,l](std::ostream& os, It where) {
                auto line = get_current_line(f, where, l);
    
                os << " line:" << get_line(where) 
                   << ", col:" << get_column(line.begin(), where) << "\n";
                while (!line.empty() && std::strchr("\r\n", *line.begin()))
                    line.advance_begin(1);
                std::cerr << line << "\n";
                std::cerr << std::string(std::distance(line.begin(), where), ' ') << "^ --- here\n";
            };
    
            ast::action data;
            try {
                if (qi::phrase_parse(f, l, p > qi::eoi, qi::space, data)) {
                    std::cout << "Parsed: " << boost::fusion::as_vector(data) << "\n";
                }
            } catch(qi::expectation_failure<It> const& e) {
                printErrorContext(std::cerr << "Expectation failed: " << e.what_, e.first);
            } catch(BadAction const& ba) {
                printErrorContext(std::cerr << "BadAction: " << ba.what(), ba.where());
            }
    
            if (f!=l) {
                std::cout << "Remaining unparsed: '" << std::string(f,l) << "'\n";
            }
        }
    }
    

    Printing:

    -----------------------
    Parsed: (f_unary 0 )
    -----------------------
    Parsed: (f_binary 47 hello )
    -----------------------
    Expectation failed: <eoi> line:1, col:25
    f_binary ( 47, "hello") bogus
                            ^ --- here
    Remaining unparsed: 'f_binary ( 47, "hello") bogus'
    -----------------------
    BadAction: Excess parameters line:1, col:15
    f_unary ( 47, "hello") 
                  ^ --- here
    Remaining unparsed: 'f_unary ( 47, "hello") '
    -----------------------
    BadAction: Type mismatch (expecting string) line:2, col:8
          7) 
          ^ --- here
    Remaining unparsed: 'f_binary ( 47, 
          7) '