annotation.qbk 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  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:annotation Annotations - Decorating the ASTs]
  9. As a prerequisite in understanding this tutorial, please review the previous
  10. [tutorial_employee employee example]. This example builds on top of that
  11. example.
  12. Stop and think about it... We're actually generating ASTs (abstract syntax
  13. trees) in our previoius examples. We parsed a single structure and generated
  14. an in-memory representation of it in the form of a struct: the struct
  15. employee. If we changed the implementation to parse one or more employees,
  16. the result would be a std::vector<employee>. We can go on and add more
  17. hierarchy: teams, departments, corporations, etc. We can have an AST
  18. representation of it all.
  19. This example shows how to annotate the AST with the iterator positions for
  20. access to the source code when post processing using a client supplied
  21. `on_success` handler. The example will show how to get the position in input
  22. source stream that corresponds to a given element in the AST.
  23. In addition, This example also shows how to "inject" client data, using the
  24. "with" directive, that the `on_success` handler can access as it is called
  25. within the parse traversal through the parser's context.
  26. The full cpp file for this example can be found here:
  27. [@../../../example/x3/annotation.cpp annotation.cpp]
  28. [heading The AST]
  29. First, we'll update our previous employee struct, this time separating the
  30. person into its own struct. So now, we have two structs, the `person` and the
  31. `employee`. Take note too that we now inherit `person` and `employee` from
  32. `x3::position_tagged` which provides positional information that we can use
  33. to tell the AST's position in the input stream anytime.
  34. namespace client { namespace ast
  35. {
  36. struct person : x3::position_tagged
  37. {
  38. person(
  39. std::string const& first_name = ""
  40. , std::string const& last_name = ""
  41. )
  42. : first_name(first_name)
  43. , last_name(last_name)
  44. {}
  45. std::string first_name, last_name;
  46. };
  47. struct employee : x3::position_tagged
  48. {
  49. int age;
  50. person who;
  51. double salary;
  52. };
  53. }}
  54. Like before, we need to tell __fusion__ about our structs to make them
  55. first-class fusion citizens that the grammar can utilize:
  56. BOOST_FUSION_ADAPT_STRUCT(client::ast::person,
  57. first_name, last_name
  58. )
  59. BOOST_FUSION_ADAPT_STRUCT(client::ast::employee,
  60. age, who, salary
  61. )
  62. [heading x3::position_cache]
  63. Before we proceed, let me introduce a helper class called the
  64. `position_cache`. It is a simple class that collects iterator ranges that
  65. point to where each element in the AST are located in the input stream. Given
  66. an AST, you can query the position_cache about AST's position. For example:
  67. auto pos = positions.position_of(my_ast);
  68. Where `my_ast` is the AST, `positions` and is the `position_cache`,
  69. `position_of` returns an iterator range that points to the start and end
  70. (`pos.begin()` and `pos.end()`) positions where the AST was parsed from.
  71. `positions.begin()` and `positions.end()` points to the start and end of the
  72. entire input stream.
  73. [heading on_success]
  74. The `on_success` gives you everything you want from semantic actions without
  75. the visual clutter. Declarative code can and should be free from imperative
  76. code. `on_success` as a concept and mechanism is an important departure from
  77. how things are done in Spirit's previous version: Qi.
  78. As demonstrated in the previous [tutorial_employee employee example], the
  79. preferred way to extract data from an input source is by having the parser
  80. collect the data for us into C++ structs as it traverses the input stream.
  81. Ideally, Spirit X3 grammars are fully attributed and declared in such a way
  82. that you do not have to add any imperative code and there should be no need
  83. for semantic actions at all. The parser simply works as declared and you get
  84. your data back as a result.
  85. However, there are certain cases where there's no way to avoid introducing
  86. imperative code. But semantic actions mess up our clean declarative grammars.
  87. If we care to keep our code clean, `on_success` handlers are alternative
  88. callback hooks to client code that are executed by the parser after a
  89. successful parse without polluting the grammar. Like semantic actions,
  90. `on_success` handlers have access to the AST, the iterators, and context.
  91. But, unlike semantic actions, `on_success` handlers are cleanly separated
  92. from the actual grammar.
  93. [heading Annotation Handler]
  94. As discussed, we annotate the AST with its position in the input stream with
  95. our `on_success` handler:
  96. // tag used to get the position cache from the context
  97. struct position_cache_tag;
  98. struct annotate_position
  99. {
  100. template <typename T, typename Iterator, typename Context>
  101. inline void on_success(Iterator const& first, Iterator const& last
  102. , T& ast, Context const& context)
  103. {
  104. auto& position_cache = x3::get<position_cache_tag>(context).get();
  105. position_cache.annotate(ast, first, last);
  106. }
  107. };
  108. `position_cache_tag` is a special tag we will use to get a reference to the
  109. actual `position_cache`, client data that we will inject at very start, when
  110. we call parse. More on that later.
  111. Our `on_success` handler gets a reference to the actual `position_cache` and
  112. calls its `annotate` member function, passing in the AST and the iterators.
  113. `position_cache.annotate(ast, first, last)` annotates the AST with
  114. information required by `x3::position_tagged`.
  115. [heading The Parser]
  116. Now we'll write a parser for our employee. To simplify, inputs will be of the
  117. form:
  118. { age, "forename", "surname", salary }
  119. [#__tutorial_annotated_employee_parser__]
  120. Here we go:
  121. namespace parser
  122. {
  123. using x3::int_;
  124. using x3::double_;
  125. using x3::lexeme;
  126. using ascii::char_;
  127. struct quoted_string_class;
  128. struct person_class;
  129. struct employee_class;
  130. x3::rule<quoted_string_class, std::string> const quoted_string = "quoted_string";
  131. x3::rule<person_class, ast::person> const person = "person";
  132. x3::rule<employee_class, ast::employee> const employee = "employee";
  133. auto const quoted_string_def = lexeme['"' >> +(char_ - '"') >> '"'];
  134. auto const person_def = quoted_string >> ',' >> quoted_string;
  135. auto const employee_def =
  136. '{'
  137. >> int_ >> ','
  138. >> person >> ','
  139. >> double_
  140. >> '}'
  141. ;
  142. auto const employees = employee >> *(',' >> employee);
  143. BOOST_SPIRIT_DEFINE(quoted_string, person, employee);
  144. }
  145. [heading Rule Declarations]
  146. struct quoted_string_class;
  147. struct person_class;
  148. struct employee_class;
  149. x3::rule<quoted_string_class, std::string> const quoted_string = "quoted_string";
  150. x3::rule<person_class, ast::person> const person = "person";
  151. x3::rule<employee_class, ast::employee> const employee = "employee";
  152. Go back and review the original [link __tutorial_employee_parser__ employee parser].
  153. What has changed?
  154. * We split the single employee rule into three smaller rules: `quoted_string`,
  155. `person` and `employee`.
  156. * We're using forward declared rule classes: `quoted_string_class`, `person_class`,
  157. and `employee_class`.
  158. [heading Rule Classes]
  159. Like before, in this example, the rule classes, `quoted_string_class`,
  160. `person_class`, and `employee_class` provide statically known IDs for the
  161. rules required by X3 to perform its tasks. In addition to that, the rule
  162. class can also be extended to have some user-defined customization hooks that
  163. are called:
  164. * On success: After a rule sucessfully parses an input.
  165. * On Error: After a rule fails to parse.
  166. By subclassing the rule class from a client supplied handler such as our
  167. `annotate_position` handler above:
  168. struct person_class : annotate_position {};
  169. struct employee_class : annotate_position {};
  170. The code above tells X3 to check the rule class if it has an `on_success` or
  171. `on_error` member functions and appropriately calls them on such events.
  172. [#__tutorial_with_directive__]
  173. [heading The with Directive]
  174. For any parser `p`, one can inject supplementary data that semantic actions
  175. and handlers can access later on when they are called. The general syntax is:
  176. with<tag>(data)[p]
  177. For our particular example, we use to inject the `position_cache` into the
  178. parse for our `annotate_position` on_success handler to have access to:
  179. auto const parser =
  180. // we pass our position_cache to the parser so we can access
  181. // it later in our on_sucess handlers
  182. with<position_cache_tag>(std::ref(positions))
  183. [
  184. employees
  185. ];
  186. Typically this is done just before calling `x3::parse` or `x3::phrase_parse`.
  187. `with` is a very lightwight operation. It is possible to inject as much data
  188. as you want, even multiple `with` directives:
  189. with<tag1>(data1)
  190. [
  191. with<tag2>(data2)[p]
  192. ]
  193. Multiple `with` directives can (perhaps not obviously) be injected from
  194. outside the called function. Here's an outline:
  195. template <typename Parser>
  196. void bar(Parser const& p)
  197. {
  198. // Inject data2
  199. auto const parser = with<tag2>(data2)[p];
  200. x3::parse(first, last, parser);
  201. }
  202. void foo()
  203. {
  204. // Inject data1
  205. auto const parser = with<tag1>(data1)[my_parser];
  206. bar(p);
  207. }
  208. [heading Let's Parse]
  209. Now we have the complete parse mechanism with support for annotations:
  210. using iterator_type = std::string::const_iterator;
  211. using position_cache = boost::spirit::x3::position_cache<std::vector<iterator_type>>;
  212. std::vector<client::ast::employee>
  213. parse(std::string const& input, position_cache& positions)
  214. {
  215. using boost::spirit::x3::ascii::space;
  216. std::vector<client::ast::employee> ast;
  217. iterator_type iter = input.begin();
  218. iterator_type const end = input.end();
  219. using boost::spirit::x3::with;
  220. // Our parser
  221. using client::parser::employees;
  222. using client::parser::position_cache_tag;
  223. auto const parser =
  224. // we pass our position_cache to the parser so we can access
  225. // it later in our on_sucess handlers
  226. with<position_cache_tag>(std::ref(positions))
  227. [
  228. employees
  229. ];
  230. bool r = phrase_parse(iter, end, parser, space, ast);
  231. // ... Some error checking here
  232. return ast;
  233. }
  234. Let's walk through the code.
  235. First, we have some typedefs for 1) The iterator type we are using for the
  236. parser, `iterator_type` and 2) For the `position_cache` type. The latter is a
  237. template that accepts the type of container it will hold. In this case, a
  238. `std::vector<iterator_type>`.
  239. The main parse function accepts an input, a std::string and a reference to a
  240. position_cache, and retuns an AST: `std::vector<client::ast::employee>`.
  241. Inside the parse function, we first create an AST where parsed data will be
  242. stored:
  243. std::vector<client::ast::employee> ast;
  244. Then finally, we create a parser, injecting a reference to the `position_cache`,
  245. and call phrase_parse:
  246. using client::parser::employees;
  247. using client::parser::position_cache_tag;
  248. auto const parser =
  249. // we pass our position_cache to the parser so we can access
  250. // it later in our on_sucess handlers
  251. with<position_cache_tag>(std::ref(positions))
  252. [
  253. employees
  254. ];
  255. bool r = phrase_parse(iter, end, parser, space, ast);
  256. On successful parse, the AST, `ast`, will contain the actual parsed data.
  257. [heading Getting The Source Positions]
  258. Now that we have our main parse function, let's have an example sourcefile to
  259. parse and show how we can obtain the position of an AST element, returned
  260. after a successful parse.
  261. Given this input:
  262. std::string input = R"(
  263. {
  264. 23,
  265. "Amanda",
  266. "Stefanski",
  267. 1000.99
  268. },
  269. {
  270. 35,
  271. "Angie",
  272. "Chilcote",
  273. 2000.99
  274. },
  275. {
  276. 43,
  277. "Dannie",
  278. "Dillinger",
  279. 3000.99
  280. },
  281. {
  282. 22,
  283. "Dorene",
  284. "Dole",
  285. 2500.99
  286. },
  287. {
  288. 38,
  289. "Rossana",
  290. "Rafferty",
  291. 5000.99
  292. }
  293. )";
  294. We call our parse function after instantiating a `position_cache` object that
  295. will hold the source stream positions:
  296. position_cache positions{input.begin(), input.end()};
  297. auto ast = parse(input, positions);
  298. We now have an AST, `ast`, that contains the parsed results. Let us get the
  299. source positions of the 2nd employee:
  300. auto pos = positions.position_of(ast[1]); // zero based of course!
  301. `pos` is an iterator range that contians iterators to the start and end of
  302. `ast[1]` in the input stream.
  303. [heading Config]
  304. If you read the previous [tutorial_minimal Program Structure] tutorial where
  305. we separated various logical modules of the parser into separate cpp and
  306. header files, and you are wondering how to provide the context configuration
  307. information (see [link tutorial_configuration Config Section]), we need to
  308. supplement the context like this:
  309. using phrase_context_type = x3::phrase_parse_context<x3::ascii::space_type>::type;
  310. typedef x3::context<
  311. error_handler_tag
  312. , std::reference_wrapper<position_cache>
  313. , phrase_context_type>
  314. context_type;
  315. [endsect]