1[/============================================================================== 2 Copyright (C) 2001-2018 Joel de Guzman 3 4 Distributed under the Boost Software License, Version 1.0. (See accompanying 5 file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 7 I would like to thank Rainbowverse, llc (https://primeorbial.com/) 8 for sponsoring this work and donating it to the community. 9===============================================================================/] 10 11[section Error Handling] 12 13This tutorial wouldn't be complete without touching on error handling. As a 14prerequisite in understanding this tutorial, please review the previous 15[tutorial_employee employee] and [tutorial_annotation annotations] examples. 16This example builds on top of these previous examples. 17 18The full cpp file for this example can be found here: 19[@../../../example/x3/error_handling.cpp error_handling.cpp] 20 21Please review the previous [tutorial_annotation annotations example]. The 22information there will be very helpful in understanding error handling. 23 24[heading The AST] 25 26Our AST is exactly the same as what we had before in the [tutorial_annotation 27annotations]: 28 29 namespace client { namespace ast 30 { 31 struct person : x3::position_tagged 32 { 33 person( 34 std::string const& first_name = "" 35 , std::string const& last_name = "" 36 ) 37 : first_name(first_name) 38 , last_name(last_name) 39 {} 40 41 std::string first_name, last_name; 42 }; 43 44 struct employee : x3::position_tagged 45 { 46 int age; 47 person who; 48 double salary; 49 }; 50 }} 51 52We have two structs, the `person` and the `employee`. Each inherits from 53`x3::position_tagged` which provides positional information that we can use 54to tell the AST's position in the input stream anytime. We will need these 55information for error handling and reporting. 56 57Like before, we need to tell __fusion__ about our structs to make them 58first-class fusion citizens that the grammar can utilize: 59 60 BOOST_FUSION_ADAPT_STRUCT(client::ast::person, 61 first_name, last_name 62 ) 63 64 BOOST_FUSION_ADAPT_STRUCT(client::ast::employee, 65 age, who, salary 66 ) 67 68[heading Expectations] 69 70There are occasions in which it is expected that the input must match a 71particular parser or the input is invalid. Such cases generally arise after 72matching a portion of a grammar, such that the context is fully known. In 73such a situation, failure to match should result in an exception. For 74example, when parsing an e-mail address, a name, an "@" and a domain name 75must be matched or the address is invalid. 76 77Two X3 mechanisms facilitate parser expectations: 78 79# The expectation operator (__x3_expect__) 80# The expect directive (__x3_expectd__`[p]`) 81 82The expectation operator (__x3_expect__) requires that the following parser 83(`b`) match the input or an __x3_expectation_failure__ is emitted. Using a 84client supplied `on_error` handler, the exception can be serviced by calling 85the handler with the source iterators and context at which the parsing failed 86can be reported. 87 88By contrast, the sequence operator (__x3_sequence__) does not require that 89the following parser match the input, which allows for backtracking or simply 90returning false from the parse function with no exceptions. 91 92The expect directive (__x3_expectd__`[p]`) requires that the argument parser 93matches the input or an exception is emitted. Using on_error(), that 94exception can be handled by calling a handler with the context at which the 95parsing failed can be reported. 96 97[heading on_error] 98 99`on_error` is the counterpart of `on_success`, as discussed in the 100[tutorial_annotation annotations example]. While `on_success` handlers are 101callback hooks to client code that are executed by the parser after a 102/successful/ parse, `on_error` handlers are callback hooks to client code 103that are executed by the parser when an __x3_expectation_failure__ is thrown 104via the expect operator or directive. `on_error` handlers have access to the 105iterators, the context and the exception that was thrown. 106 107[heading Error Handling] 108 109Before we proceed, let me introduce a helper class, the 110x3::__x3_error_handler__. It is utility class that provides __clang__ style 111error reporting which gives you nice reports such as the following: 112 113[pre 114In line 16: 115Error! Expecting: person here: 116 'I am not a person!' <--- this should be a person 117____^_ 118] 119 120We'll see later that this error message is exactly what this example emits. 121 122Here's our `on_error` handler: 123 124 struct error_handler 125 { 126 template <typename Iterator, typename Exception, typename Context> 127 x3::error_handler_result on_error( 128 Iterator& first, Iterator const& last 129 , Exception const& x, Context const& context) 130 { 131 auto& error_handler = x3::get<x3::error_handler_tag>(context).get(); 132 std::string message = "Error! Expecting: " + x.which() + " here:"; 133 error_handler(x.where(), message); 134 return x3::error_handler_result::fail; 135 } 136 }; 137 138`x3::error_handler_tag` is a special tag we will use to get a reference to 139the actual x3::__x3_error_handler__ that we will inject at very start, when 140we call parse. We get the x3::__x3_error_handler__ here: 141 142 auto& error_handler = x3::get<error_handler_tag>(context).get(); 143 144The x3::__x3_error_handler__ handles all the nitty gritty details such as 145determining the line number and actual column position, and formatting the 146error message printed. All we have to do is provide the actual error string 147which we extract from the __x3_expectation_failure__ exception: 148 149 std::string message = "Error! Expecting: " + x.which() + " here:"; 150 151Then, we return `x3::error_handler_result::fail` to tell X3 that we want to 152fail the parse when such an event is caught. You can return one of: 153 154[table 155 [[`Action`] [Description]] 156 [[fail] [Quit and fail. Return a no_match.]] 157 [[retry] [Attempt error recovery, possibly moving the iterator position.]] 158 [[accept] [Force success, moving the iterator position appropriately.]] 159 [[rethrow] [Rethrows the error.]] 160] 161 162[heading The Parser] 163 164Now we'll rewrite employee parser with error handling in mind. Like the 165[tutorial_annotation annotations] example, inputs will be of the form: 166 167 { age, "forename", "surname", salary } 168 169Here we go: 170 171 namespace parser 172 { 173 using x3::int_; 174 using x3::double_; 175 using x3::lexeme; 176 using ascii::char_; 177 178 struct quoted_string_class; 179 struct person_class; 180 struct employee_class; 181 182 x3::rule<quoted_string_class, std::string> const quoted_string = "quoted_string"; 183 x3::rule<person_class, ast::person> const person = "person"; 184 x3::rule<employee_class, ast::employee> const employee = "employee"; 185 186 auto const quoted_string_def = lexeme['"' >> +(char_ - '"') >> '"']; 187 auto const person_def = quoted_string > ',' > quoted_string; 188 189 auto const employee_def = 190 '{' 191 > int_ > ',' 192 > person > ',' 193 > double_ 194 > '}' 195 ; 196 197 auto const employees = employee >> *(',' >> employee); 198 199 BOOST_SPIRIT_DEFINE(quoted_string, person, employee); 200 201 struct quoted_string_class {}; 202 struct person_class : x3::annotate_on_success {}; 203 struct employee_class : error_handler, x3::annotate_on_success {}; 204 } 205 206Go back and review the [link __tutorial_annotated_employee_parser__ annotated 207employee parser]. What has changed? It is almost identical, except: 208 209Where appropriate, we're using the expectation operator (__x3_expect__) in 210place of the sequence operator (__x3_sequence__): 211 212 auto const person_def = quoted_string > ',' > quoted_string; 213 214 auto const employee_def = 215 '{' 216 > int_ > ',' 217 > person > ',' 218 > double_ 219 > '}' 220 ; 221 222You will have some "deterministic points" in the grammar. Those are the 223places where backtracking *cannot* occur. For our example above, when you get 224a `'{'`, you definitely must see an `int_` next. After that, you definitely 225must have a `','` next and then a `person` and so on until the final `'}'`. 226Otherwise, there is no point in proceeding and trying other branches, 227regardless where they are. The input is definitely erroneous. When this 228happens, an expectation_failure exception is thrown. Somewhere outward, the 229error handler will catch the exception. In our case, it is caught in our 230`on_error` handler. 231 232Notice too that we subclass the `employee_class` from our `error_handler`. By 233doing so, we tell X3 that we want to call our `error_handler` whenever an 234exception is thrown somewhere inside the `employee` rule and whatever else it 235calls (i.e. the `person` and `quoted_string` rules). 236 237[heading Let's Parse] 238 239Now we have the complete parse mechanism with error handling: 240 241 void parse(std::string const& input) 242 { 243 using boost::spirit::x3::ascii::space; 244 typedef std::string::const_iterator iterator_type; 245 246 std::vector<client::ast::employee> ast; 247 iterator_type iter = input.begin(); 248 iterator_type const end = input.end(); 249 250 using boost::spirit::x3::with; 251 using boost::spirit::x3::error_handler_tag; 252 using error_handler_type = boost::spirit::x3::error_handler<iterator_type>; 253 254 // Our error handler 255 error_handler_type error_handler(iter, end, std::cerr); 256 257 // Our parser 258 using client::parser::employees; 259 auto const parser = 260 // we pass our error handler to the parser so we can access 261 // it later in our on_error and on_sucess handlers 262 with<error_handler_tag>(std::ref(error_handler)) 263 [ 264 employees 265 ]; 266 267 bool r = phrase_parse(iter, end, parser, space, ast); 268 269 // ... Some final reports here 270 } 271 272Prior to calling `phrase_parse`, we first create an AST where parsed data will be 273stored: 274 275 std::vector<client::ast::employee> ast; 276 277We also create the actual error handler, sending message to `std::cerr`: 278 279 error_handler_type error_handler(iter, end, std::cerr); 280 281Then, we inject a reference to `error_handler`, using the `with` directive 282similar to what we did in the [link __tutorial_with_directive__ annotations 283example]: 284 285 auto const parser = 286 // we pass our error handler to the parser so we can access 287 // it later in our on_error and on_sucess handlers 288 with<error_handler_tag>(std::ref(error_handler)) 289 [ 290 employees 291 ]; 292 293Now, if we give the parser an erroneous input: 294 295 std::string bad_input = R"( 296 { 297 23, 298 "Amanda", 299 "Stefanski", 300 1000.99 301 }, 302 { 303 35, 304 "Angie", 305 "Chilcote", 306 2000.99 307 }, 308 { 309 43, 310 'I am not a person!' <--- this should be a person 311 3000.99 312 }, 313 { 314 22, 315 "Dorene", 316 "Dole", 317 2500.99 318 }, 319 { 320 38, 321 "Rossana", 322 "Rafferty", 323 5000.99 324 } 325 )"; 326 327The parser will complain as expected: 328 329[pre 330------------------------- 331Now we have some errors 332In line 16: 333Error! Expecting: person here: 334 'I am not a person!' <--- this should be a person 335____^_ 336------------------------- 337Parsing failed 338------------------------- 339] 340 341[endsect] 342