• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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