Format Semantic Error Report

I'm playing around with the boost.spirit library and I can't seem to report a simple error message from my semantic activity.

// 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");

      

Actions

is just a symbol table ( boost::spirit::qi::symbols

). The attribute parameters

is std::vector

of boost::variant

, which describes the types of the parameters. I would like to create a meaningful error message in the semantic action ValidateAction

while also specifying the position within the input, which is not the case. If I just assign _pass

false the parsing ends, but the error message is something like "pending", not that one, for example. The second parameter is of the wrong type (expected int instead of string).

I read somewhere that I can throw an exception out of my semantic action, but the problem is that I haven't found if I can access the iterators from the parsed values. For example, I wanted to use an exception expectation_failure

, so my error handler is automatically called, but I need to pass iterators into the exception, which seems to be impossible.

Is there a good way to report semantic errors with more details other than returning only false?

+2


source to share


1 answer


I would use filepos_iterator and just throw the exception, so you have complete control over the reporting.

Let me see what I can find in the remaining 15 minutes.

Ok, took a little longer, but I think this is 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) '

      

+3


source







All Articles