taocpp / PEGTL

Parsing Expression Grammar Template Library
Boost Software License 1.0
1.94k stars 229 forks source link

Can't get custom error messages to work. #353

Closed rowlesmr closed 1 year ago

rowlesmr commented 1 year ago

I am getting a static assert error: error C2338: static_assert failed: 'explicit error message required for Rule' (from line 61 of tao\pegtl\must_if.hpp). I don't understand what I need to do to satisfy the assert, or even when the error means.

If I run the error message example, it works, so I know my setup can allow it to happen.

.

As an example, I have an input which fails with the error message:

C:\_______\data\test_data\test.cif:6:23: parse error matching struct row::cif::rules::lst_end
_list2 [ val val2 val3 ]
                      ^

I would like to specialise the error message.

From the examples, I've put together:

// I'm in the namespace row::cif
template< typename > inline constexpr const char* error_message = nullptr;
template<> inline constexpr auto error_message< rules::lst_end > = "Incorrect list ending.";

struct error {
    template< typename Rule > static constexpr auto message = error_message< Rule >;
};

template< typename Rule >
using control = pegtl::must_if< error >::control< Rule >;

and then called the parse function as

// I'm in the row::cif namespace
pegtl::parse < rules::CIF2_file, pegtl::nothing, control> (in);

I can't see anything obviously wrong with what I've done, unless I've put an error message on something that shouldn't, or need to put a message on something else.

. I have to see if I can make up a mwe without breaking anything else.

d-frey commented 1 year ago

I can't see anything obviously wrong, either. Can you reduce your code into a small but complete example and post it?

rowlesmr commented 1 year ago

How's this? I've collapsed the namespaces and deleted a bunch of rules that aren't necessary for the example to work. The only rules I've changed are value and unquoted_string in order to remove rules that I've removed. Everything is still in the same order as in the real file. I've maintained the same level of indirections through various rules due to the actions I need to take (I've removed those as well).

Compiling in MSVC, Visual Studio 2022 (v143), /O2 /Ot.

Using /std:c++17, /std:c++20, and /std:c++latest, the development version always fails to build with tao_dev\pegtl\must_if.hpp(61,34): error C2338: static_assert failed: 'explicit error message required for Rule'

Using /std:c++17, /std:c++20, and /std:c++latest, the 3.x branch always fails to build with tao_3\pegtl\must_if.hpp(50,64): error C2338: static_assert failed: 'Errors::template message< Rule > != nullptr' additionally, under /std:c++20, and /std:c++latest, there is also error C4996: '_Header_ciso646': warning STL4036: <ciso646> is removed in C++20. You can define _SILENCE_CXX20_CISO646_REMOVED_WARNING or _SILENCE_ALL_CXX20_DEPRECATION_WARNINGS to suppress this warning.

#include "tao_dev/pegtl.hpp"

namespace row
{

    namespace pegtl = tao::pegtl;
    namespace pegtl8 = tao::pegtl::utf8;

    //reserved words
    struct DATA : TAO_PEGTL_ISTRING("data_") {};

    struct non_blank_char : pegtl8::ranges<0x0021, 0x007E, 0x00A0, 0xD7FF, 0xE000, 0xFDCF, 0xFDF0, 0xFFFD, 0x10000, 0x1FFFD, 0x20000, 0x2FFFD, 0x30000, 0x3FFFD, 0x40000, 0x4FFFD, 0x50000, 0x5FFFD, 0x60000, 0x6FFFD, 0x70000, 0x7FFFD, 0x80000, 0x8FFFD, 0x90000, 0x9FFFD, 0xA0000, 0xAFFFD, 0xB0000, 0xBFFFD, 0xC0000, 0xCFFFD, 0xD0000, 0xDFFFD, 0xE0000, 0xEFFFD, 0xF0000, 0xFFFFD, 0x100000, 0x10FFFD> {};
    struct restrict_char : pegtl8::ranges<0x0021, 0x005A, 0x005C, 0x005C, 0x005E, 0x007A, 0x007C, 0x007C, 0x007E, 0x007E, 0x00A0, 0xD7FF, 0xE000, 0xFDCF, 0xFDF0, 0xFFFD, 0x10000, 0x1FFFD, 0x20000, 0x2FFFD, 0x30000, 0x3FFFD, 0x40000, 0x4FFFD, 0x50000, 0x5FFFD, 0x60000, 0x6FFFD, 0x70000, 0x7FFFD, 0x80000, 0x8FFFD, 0x90000, 0x9FFFD, 0xA0000, 0xAFFFD, 0xB0000, 0xBFFFD, 0xC0000, 0xCFFFD, 0xD0000, 0xDFFFD, 0xE0000, 0xEFFFD, 0xF0000, 0xFFFFD, 0x100000, 0x10FFFD> {};
    struct lead_char : pegtl8::ranges<0x0021, 0x0021, 0x0025, 0x0026, 0x0028, 0x005A, 0x005C, 0x005C, 0x005E, 0x005E, 0x0060, 0x007A, 0x007C, 0x007C, 0x007E, 0x007E, 0x00A0, 0xD7FF, 0xE000, 0xFDCF, 0xFDF0, 0xFFFD, 0x10000, 0x1FFFD, 0x20000, 0x2FFFD, 0x30000, 0x3FFFD, 0x40000, 0x4FFFD, 0x50000, 0x5FFFD, 0x60000, 0x6FFFD, 0x70000, 0x7FFFD, 0x80000, 0x8FFFD, 0x90000, 0x9FFFD, 0xA0000, 0xAFFFD, 0xB0000, 0xBFFFD, 0xC0000, 0xCFFFD, 0xD0000, 0xDFFFD, 0xE0000, 0xEFFFD, 0xF0000, 0xFFFFD, 0x100000, 0x10FFFD> {};

    //Whitespace, and comments
    struct line_term : pegtl::sor<pegtl::seq<pegtl::one<'\r'>, pegtl::opt<pegtl::one<'\n'>>>, pegtl::one<'\n'>> {};
    struct comment : pegtl::if_must<pegtl::one<'#'>, pegtl::until<pegtl::eolf>> {};
    struct ws : pegtl::blank {};
    struct wschar : pegtl::sor<ws, line_term> {};
    struct whitespace : pegtl::plus<pegtl::sor<wschar, comment>> {};
    struct ws_or_eof : pegtl::sor<whitespace, pegtl::eof> {};

    //unquoted string
    struct unquoted_string : pegtl::seq<lead_char, pegtl::star<restrict_char>> {};

    //other values
    struct lst;
    struct value : pegtl::sor<unquoted_string, lst> {};

    //List
    struct lst_begin : pegtl::one<'['> {};
    struct lst_end : pegtl::one<']'> {};
    struct lst_value : value {};
    struct lst_values : pegtl::opt<pegtl::opt<whitespace>, lst_value, pegtl::star<whitespace, lst_value>> {};
    struct lst : pegtl::if_must<lst_begin, lst_values, /*pegtl::opt<whitespace>,*/ lst_end> {}; //rule is broken to test error messages

    //Actions:
    template<typename Rule>
    struct action : pegtl::nothing<Rule> {};

    template<> struct action< lst > {
        template<typename Input> static void apply(const Input& in) {
            std::cout << "The list is done.\n";
        }
    };

    //Error messages:
    template< typename > 
    inline constexpr const char* error_message = nullptr;

    template<> 
    inline constexpr auto error_message< lst_end > = "Test123";

    struct error { 
        template< typename Rule > 
        static constexpr auto message = error_message< Rule >; 
    };

    template< typename Rule > 
    using control = pegtl::must_if< error >::control< Rule >;
}

int main()
{
    std::string bad_string{"[ q w e ]"};
    std::string good_string{"[ q w e]"};
    tao::pegtl::string_input<tao::pegtl::tracking_mode::eager, tao::pegtl::eol::lf_crlf> in(bad_string, "string");
    try
    {
        tao::pegtl::parse< row::lst, row::action, row::control >(in); //tao::pegtl::normal
    }
    catch (const tao::pegtl::parse_error& e)
    {
        const auto p = e.positions().front();
        std::cerr << e.what() << '\n'
            << in.line_at(p) << '\n'
            << std::setw(p.column) << '^' << std::endl;
    }
}
d-frey commented 1 year ago

I think the issue is that you have more rules to cover. Your grammar uses:

if_must< lst_begin, lst_values, lst_end >

meaning that you need to provide error message specializations for lst_end, but also for lst_values. Also, you grammar contains

if_must<one<'#'>, until<eolf>>

which means you also need to specialize for until<eolf>. Once I add the two missing specializations, it start to compile. https://godbolt.org/z/fojoh8159

With other compilers, the error messages from the compiler also contain lst_values and until<eolf> to hint at which specializations are missing. I haven't tested it, but I think MSVC should also do this. But well... it's still C++ error messages and the PEGTL is quite heavy on templates, so it's definitely not easy to spot it immediately. ;)

rowlesmr commented 1 year ago

Knowing the answer, I can now kind of parse the error messages. I can also see how I read the docs wrong. I'll have to see how I'd reword it.

rowlesmr commented 1 year ago

until<eolf>

this is why it failed when I went nuclear and added all of my rules; I didn't add that one.

d-frey commented 1 year ago

until<eolf>

this is why it failed when I went nuclear and added all of my rules; I didn't add that one.

Note that you probably don't want to specialize for until<eolf> directly, as you might have this rule in several places in the future within your grammar. You would want to add struct comment_end : until<eolf> {};, use that in your comment rule and specialize for comment_end.

rowlesmr commented 1 year ago

You would want to add struct comment_end : until<eolf> {};, use that in your comment rule and specialize for comment_end.

Wilco.

rowlesmr commented 1 year ago

Can confirm. Adding those missing rules enabled compilation.