Nov 4

Boost Spirit III (Adding Error Handling)

Category: C++, Programming

This article expands the example from the previous two, here and here, to add more specific error information.

As always the full code from the example is available here here.

The Problem

In our previous example an error simply resulted in a generic error message and a printout of the location at which the error occurred.

Ideally we should provide some indication of what we were expecting to get.

The Error Enumeration

The first step is to define an enumeration containing the error types that may be encountered. In this case I have wrapped the enumeration in a struct so that:

  1. The enumeration values are not stuck into the global namespace
  2. I can add a static method for converting a value to an error message

:

struct error{
  enum _error{
    NONE,
    UNTERMINATED_QUOTE,
    MISSING_OPEN_BRACKET,
    MISSING_CLOSE_BRACKET,
    MISSING_RECORD_NAME,
    MISSING_EQUAL,
    MISSING_PROPERTY_VALUE
  };
 
  static const char *message(_rec_parse_error e){
    ...
  }
};
 
typedef error::_error error_t;

Defining Assertions

To define where these errors can occur in the grammar you will need to define an assertion for each error. You can define them anywhere, but since they require a constructor argument I did not make them member variables like a rules, instead I defined them on the stack in the definition struct constructor:

struct record_grammar:public grammar<record_grammar>{
  ...
  template<class ScannerT>
  struct definition{
    ...
    typedef assertion<error_t> my_assertion_t;
    definition(const record_grammar &self){
      my_assertion_t expect_quote(error::UNTERMINATED_QUOTE);
      my_assertion_t expect_open_bracket(error::MISSING_OPEN_BRACKET);
      my_assertion_t expect_close_bracket(error::MISSING_CLOSE_BRACKET);
      my_assertion_t expect_name(error::MISSING_RECORD_NAME);
      my_assertion_t expect_equal(error::MISSING_EQUAL);
      my_assertion_t expect_property_value(error::MISSING_PROPERTY_VALUE);
      ...
    }
    ...
  };
  ...
};

Attaching Assertions to the Grammar

Once you’ve defined your assertions you use them to wrap portions of the grammar that, if absent, will cause the error to occur. For instance, in order to specify that a quoted string must be terminated by a closing quote:

string_literal = lexeme_d[
  ch_p('"') >>
  *(
      (anychar_p - ch_p('"') )  |
      str_p("\\\"")
  ) >>
  expect_quote( ch_p('"') )
];

Processing Errors Using Guard

In order to actually catch and handle an error, you use the guard class. In theory you can have more than one, but in this case a top-level catch all seems appropriate:

definition(const record_grammar &self){
  ...
  guard<error_t> my_guard;
   ...
 
  record = my_guard(
        ...
    )[handle_error(self.error)];
};

The Guard Callback

An annoying part of the error mechanism is that the error callback must take ScannerT as an argument. Since this is a template argument that means the callback must be templatized which in turn means it cannot be a function2 object.

So instead I defined the following functor which will translate the error into a callback taking
void (error_t, const char *)
:

typedef function2<void, error_t, const char *> error_cb;
...
template<class ScannerT>
struct definition{
  ...
  struct handle_error{
    handle_error(error_cb &cb):cb(cb){
    };
 
    error_status<> operator()(ScannerT const &scan, 
        parser_error<error_t, const char*> error) const{
      cb(error.descriptor, error.where);
      return error_status<>();
    }
    error_cb &cb;
  };
  ...
}

Now we can use this functor as follows:

struct record_grammar:public grammar<record_grammar>{
  ...
  template<class ScannerT>
  struct definition{
    definition(const record_grammar &self){
      ...
      record = my_guard(
        ...
      )[handle_error(self.error)];
      ...
    }
    ...
  };
  ...
};

Closing Notes


One thing I wanted to get into in this post was recovering from parse errors. Most grammar parsers allow you to continue after a parse error and spirit appears to support at least the concept.

Unfortunately, although the error_status<> struct has the option to specify a retry, the documentation doesn’t provide any information on how to specify where the parser should pick back up or in what state.

Anyway, If I figure it out I’ll update the article.

The Author

Michael Smit is a software engineer in Seattle, Washington who works for amazon

1 comment

1 Comment so far

  1. [...] information on improving the error messages for your grammar take a look at the next article. The AuthorMichael Smit is a software engineer in Maryland who currently develops software for [...]