• 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:minimal X3 Program Structure]
12
13As a prerequisite in understanding this tutorial, please review the previous
14[tutorial_employee employee example]. This example builds on top of that
15example.
16
17So far, to keep things simple, all of the tutorial programs are self
18contained in one cpp file. In reality, you will want to separate various
19logical modules of the parser into separate cpp and header files, decoupling
20the interface from the implememtation.
21
22There are many ways to structure an X3 parser, but the "minimal" example in
23this tutorial shows the preferred way. This example basically reuses the same
24parser as the [tutorial_employee employee example] for the sake of
25familiarity, but structured to allow separate compilation of the actual
26parser in its own definition file and cpp file. The cpp files, including main
27see only the header files --the interfaces. This is a good example on how X3
28parsers are structured in a C++ application.
29
30[heading Structure]
31
32The program is structured in a directory with the following header and cpp
33files:
34
35[table
36    [[`File`            ]                                               [Description            ]]
37    [[[@../../../example/x3/minimal/ast.hpp ast.hpp]]                   [The AST                ]]
38    [[[@../../../example/x3/minimal/ast_adapted.hpp ast_adapted.hpp]]   [Fusion adapters        ]]
39    [[[@../../../example/x3/minimal/config.hpp config.hpp]]             [Configuration          ]]
40    [[[@../../../example/x3/minimal/employee.hpp employee.hpp]]         [Main parser API        ]]
41    [[[@../../../example/x3/minimal/employee_def.hpp employee_def.hpp]] [Parser definitions     ]]
42    [[[@../../../example/x3/minimal/employee.cpp employee.cpp]]         [Parser instantiation   ]]
43    [[[@../../../example/x3/minimal/main.cpp main.cpp]]                 [Main program           ]]
44]
45
46The contents of the files should already be familiar. It's essentially the
47same [tutorial_employee employee example]. So I will skip the details on how
48the parser works and focus only on the features needed for refactoring the
49program into a modular structure suitable for real-world deployment.
50
51[heading AST]
52
53We place the AST declaration here:
54
55    namespace client { namespace ast
56    {
57        struct employee
58        {
59            int age;
60            std::string forename;
61            std::string surname;
62            double salary;
63        };
64
65        using boost::fusion::operator<<;
66    }}
67
68[heading Fusion adapters]
69
70Here, we adapt the AST for Fusion, making it a first-class fusion citizen:
71
72   BOOST_FUSION_ADAPT_STRUCT(client::ast::employee,
73      age, forename, surname, salary
74   )
75
76[heading Main parser API]
77
78This is the main header file that all other cpp files need to include.
79
80[#__tutorial_spirit_declare__]
81[heading BOOST_SPIRIT_DECLARE]
82
83Remember [link __tutorial_spirit_define__ `BOOST_SPIRIT_DEFINE`]? If not,
84then you probably want to go back and review that section to get a better
85understanding of what's happening.
86
87Here in the header file, instead of `BOOST_SPIRIT_DEFINE`, we use
88`BOOST_SPIRIT_DECLARE` for the *top* rule. Behind the scenes, what's actually
89happening is that we are declaring a `parse_rule` function in the client
90namespace. For example, given a rule named `my_rule`,
91`BOOST_SPIRIT_DECLARE(my_rule)` expands to this code:
92
93    template <typename Iterator, typename Context>
94    bool parse_rule(
95        decltype(my_rule)
96      , Iterator& first, Iterator const& last
97      , Context const& context, decltype(my_rule)::attribute_type& attr);
98
99If you went back and reviewed [link __tutorial_spirit_define__
100BOOST_SPIRIT_DEFINE], you'll see why it is exactly what we need to use for
101header files. `BOOST_SPIRIT_DECLARE` generates function declarations that are
102meant to be placed in hpp (header) files while `BOOST_SPIRIT_DEFINE`
103generates function definitions that are meant to be placed in cpp files.
104
105[note `BOOST_SPIRIT_DECLARE` is variadic and may be used for one or more rules.
106Example: `BOOST_SPIRIT_DECLARE(r1, r2, r3);`]
107
108In this example, the top rule is `employee`. We declare `employee` in this
109header file:
110
111    namespace client
112    {
113        namespace parser
114        {
115            namespace x3 = boost::spirit::x3;
116            using employee_type = x3::rule<class employee, ast::employee>;
117            BOOST_SPIRIT_DECLARE(employee_type);
118        }
119
120        parser::employee_type employee();
121    }
122
123We also provide a function that returns an `employee` object. This is the
124parser that we will use anywhere it is needed. X3 parser objects are very
125lightweight. They are basically simple tags with no data other than the name
126of the rule (e.g. "employee"). Notice that we are passing this by value.
127
128[heading Parser Definitions]
129
130Here is where we place the actual rules that make up our grammar:
131
132    namespace parser
133    {
134        namespace x3 = boost::spirit::x3;
135        namespace ascii = boost::spirit::x3::ascii;
136
137        using x3::int_;
138        using x3::lit;
139        using x3::double_;
140        using x3::lexeme;
141        using ascii::char_;
142
143        x3::rule<class employee, ast::employee> const employee = "employee";
144
145        auto const quoted_string = lexeme['"' >> +(char_ - '"') >> '"'];
146
147        auto const employee_def =
148            lit("employee")
149            >> '{'
150            >>  int_ >> ','
151            >>  quoted_string >> ','
152            >>  quoted_string >> ','
153            >>  double_
154            >>  '}'
155            ;
156
157        BOOST_SPIRIT_DEFINE(employee);
158    }
159
160    parser::employee_type employee()
161    {
162        return parser::employee;
163    }
164
165In the parser definition, we use [link __tutorial_spirit_define__
166`BOOST_SPIRIT_DEFINE`] just like we did in the [tutorial_employee employee
167example].
168
169While this is another header file, it is not meant to be included by the
170client. Its purpose is to be included by an instantiations cpp file (see
171below). We place this in an `.hpp` file for flexibility, so we have the
172freedom to instantiate the parser with different iterator types.
173
174[#tutorial_configuration]
175[heading Configuration]
176
177Here, we declare some types for instatntaiting our X3 parser with. Rememeber
178that Spirit parsers can work with any __fwditer__. We'll also need to provide
179the initial context type. This is the context that X3 will use to initiate a
180parse. For calling `phrase_parse`, you will need the `phrase_parse_context`
181like we do below, passing in the skipper type.
182
183    using iterator_type = std::string::const_iterator;
184    using context_type = x3::phrase_parse_context<x3::ascii::space_type>::type;
185
186For plain `parse`, we simply use `x3::unused_type`.
187
188[heading Parser Instantiation]
189
190Now we instantiate our parser here, for our specific configuration:
191
192    namespace client { namespace parser
193    {
194        BOOST_SPIRIT_INSTANTIATE(employee_type, iterator_type, context_type);
195    }}
196
197For that, we use `BOOST_SPIRIT_INSTANTIATE`, passing in the parser type,
198the iterator type, and the context type.
199
200[heading BOOST_SPIRIT_INSTANTIATE]
201
202Go back and review [link __tutorial_spirit_define__ `BOOST_SPIRIT_DEFINE`]
203and [link __tutorial_spirit_declare__ `BOOST_SPIRIT_DECLARE`] to get a better
204grasp of what's happening with `BOOST_SPIRIT_INSTANTIATE` and why it is
205needed.
206
207So what the heck is `BOOST_SPIRIT_INSTANTIATE`? What we want is to isolate
208the instantiation of our parsers (rules and all that), into separate
209translation units (or cpp files, if you will). In this example, we want to
210place our x3 employee stuff in [@../../../example/x3/minimal/employee.cpp
211employee.cpp]. That way, we have separate compilation. Every time we update
212our employee parser source code, we only have to build the `employee.cpp`
213file. All the rest will not be affected. By compiling only once in one
214translation unit, we save on build times and avoid code bloat. There is no
215code duplication, which can happen otherwise if you simply include the
216employee parser ([@../../../example/x3/minimal/employee.hpp employee.hpp])
217everywhere.
218
219But how do you do that. Remember that our parser definitions are also placed
220in its own header file for flexibility, so we have the freedom to instantiate
221the parser with different iterator types.
222
223What we need to do is explicitly instantiate the `parse_rule` function we
224declared and defined via `BOOST_SPIRIT_DECLARE` and `BOOST_SPIRIT_DEFINE`
225respectively, using `BOOST_SPIRIT_INSTANTIATE`. For our particular example,
226`BOOST_SPIRIT_INSTANTIATE` expands to this code:
227
228    template bool parse_rule<iterator_type, context_type>(
229        employee_type rule_
230      , iterator_type& first, iterator_type const& last
231      , context_type const& context, employee_type::attribute_type& attr);
232
233[heading Main Program]
234
235Finally, we have our main program. The code is the same as single cpp file
236[tutorial_employee employee example], but here, we simply include three
237header files:
238
239    #include "ast.hpp"
240    #include "ast_adapted.hpp"
241    #include "employee.hpp"
242
243# `ast.hpp` for the AST declaration
244# `ast_adapted.hpp` if you need to traverse the AST using fusion
245# `employee.hpp` the main parser API
246
247[endsect]
248