1

I am working on parsing .csv files with spirit x3. I want to use expectations to get a detailed information on the error. Further the input should be accepted if it ends with or without a new line.

Here is my code so far:

#include <iostream>
#include <iomanip>
#include <vector>
#include <optional>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/utility/error_reporting.hpp>
#include <boost/spirit/home/x3/support/utility/annotate_on_success.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>

struct Coord
{
    int x;
    int y;
    std::optional<int> z;
};
struct Coords : std::vector<Coord> {};
BOOST_FUSION_ADAPT_STRUCT(Coord, (auto, x)(auto, y)(auto, z))
namespace boost::fusion::detail {
    template<typename T>
    std::ostream &operator<<(std::ostream &os, const std::optional<T> &t) {
        if (t)
            return os << *t;
        else
            return os << "N.A.";
    }
}

namespace Parser {
    namespace x3 = boost::spirit::x3;
    namespace ascii = boost::spirit::x3::ascii;

    struct error_handler
    {
        template<typename Iterator, typename Exception, typename Context>
        x3::error_handler_result on_error(
                Iterator& first, Iterator const& last, Exception const& x, Context const& context)
        {
            auto& error_handler = x3::get<x3::error_handler_tag>(context).get();
            std::string message = "Error! Expecting: " + x.which() + " here:";
            error_handler(x.where(), message);
            return x3::error_handler_result::fail;
        }
    };

    struct CSVLine;
    struct CSVFile;

    static const auto delim = x3::rule<struct Delim>{} = x3::char_(',');
    static const auto lineend = &(x3::eoi | x3::eol);
    using delim_type = std::remove_cv_t<decltype(delim)>;
    using lineend_type = std::remove_cv_t<decltype(lineend)>;

    template<bool expectFirstElement>
    static inline auto line_parser()
    {
        if constexpr (expectFirstElement)
            return x3::rule<CSVLine, Coord>{"line"} = x3::eps > x3::int_ > delim > x3::int_ > delim > -x3::int_ > lineend;
        else
            return x3::rule<CSVLine, Coord>{"line"} = x3::int_ > delim > x3::int_ > delim > -x3::int_ > lineend;
    }

    template<bool expectFirstElement>
    static inline auto csv_parser()
    {
        const auto line = line_parser<expectFirstElement>();
        return x3::rule<CSVFile, Coords>{"file"} = (line % x3::eol) >> -x3::eol >> x3::eoi;
    }

    struct CSVLine : x3::annotate_on_success {};
    struct CSVFile : error_handler, x3::annotate_on_success {};
}

static int example_conter = 1;

template<bool expectFirstElement>
bool parse(std::string const& input)
{
    using iterator_type = std::string::const_iterator;

    iterator_type iter = input.begin();
    iterator_type const end = input.end();

    using boost::spirit::x3::with;
    using boost::spirit::x3::error_handler_tag;
    using error_handler_type = boost::spirit::x3::error_handler<iterator_type>;

    error_handler_type error_handler(iter, end, std::cout);
    const auto p = Parser::csv_parser<expectFirstElement>();
    auto const parser = with<error_handler_tag>(std::ref(error_handler))[p];

    Coords coords;

    if (parse(iter, end, parser, coords))
    {
        if (iter != end)
            std::cout << "Remaining unparsed: " << std::quoted(std::string(iter, end)) << std::endl;
        else
        {
            using boost::fusion::operator<<;

            std::cout << boost::fusion::tuple_open('[');
            std::cout << boost::fusion::tuple_close(']');
            std::cout << boost::fusion::tuple_delimiter(", ");

            std::cout << "Parsing succeeded\n";
            for (auto const& c : coords)
                std::cout << "Coord: " << c << '\n';
            std::cout.flush();
            return true;
        }
    }
    std::cout << "Parsing failed\n";
    return false;
}

void checkParse(const std::string& input)
{
    std::cout << "====== Example " << example_conter++ << " ======\nInput:\n+++++++++++++++++++++++\n";
    std::cout << input << "\n+++++++++++++++++++++++" << std::endl;
    std::cout << "------ Not expecting first element ------\n";
    const auto r1 = parse<false>(input);
    std::cout.flush();
    std::cout << "-------- Expecting first element --------\n";
    const auto r2 = parse<true>(input);
    std::cout.flush();

    if (r1 != r2)
        std::cout << "\nFailed if expectFirstElement was " << (r1 ? "enabled" : "disabled");
    std::cout  << '\n' << std::endl;
}
namespace boost::spirit::x3 {
    template <> struct get_info<int_type> {
        typedef std::string result_type;
        std::string operator()(int_type const&) const { return "integral number"; }
    };
    template <> struct get_info<Parser::delim_type> {
        typedef std::string result_type;
        std::string operator()(Parser::delim_type const&) const { return "delimiter ','"; }
    };
    template <> struct get_info<Parser::lineend_type> {
        typedef std::string result_type;
        std::string operator()(Parser::lineend_type const&) const { return "end of line or input"; }
    };
}

int main()
{
    // New line at the end. Fails if first element of line is expected is enabled
    checkParse("1,2,3\n3,4,\n5,6,\n");
    // no detailed error message if first element of line is not expected is diabled
    checkParse("1,2,3\nx,4,\n5,6,\n");
    // missing delimiter at the end
    checkParse("1,2,3\n4,5\n,6,7,");

    return 0;
}

So either I get detailed information on the error but it fails with a new line at the end or there is no detailed error information.

Further if the last delimiter is missing on one line, the error message displays it for the next line and it is misleading:

In line 3:
Error! Expecting: delimiter ',' here:
,6,7,
^_
Parsing failed

How can I fix the expectations to avoid these issues? And is it possible to continue on the next line if an expectation failure happened? In order to display all possible errors at once and not only the next one.

Max
  • 638
  • 1
  • 4
  • 19

0 Answers0