error_handling.qbk 11 KB

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