eea593da4e
Missed an edit in d4c0179f
248 lines
9.4 KiB
Plaintext
248 lines
9.4 KiB
Plaintext
[/==============================================================================
|
|
Copyright (C) 2001-2018 Joel de Guzman
|
|
|
|
Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
|
|
I would like to thank Rainbowverse, llc (https://primeorbial.com/)
|
|
for sponsoring this work and donating it to the community.
|
|
===============================================================================/]
|
|
|
|
[section:minimal X3 Program Structure]
|
|
|
|
As a prerequisite in understanding this tutorial, please review the previous
|
|
[tutorial_employee employee example]. This example builds on top of that
|
|
example.
|
|
|
|
So far, to keep things simple, all of the tutorial programs are self
|
|
contained in one cpp file. In reality, you will want to separate various
|
|
logical modules of the parser into separate cpp and header files, decoupling
|
|
the interface from the implememtation.
|
|
|
|
There are many ways to structure an X3 parser, but the "minimal" example in
|
|
this tutorial shows the preferred way. This example basically reuses the same
|
|
parser as the [tutorial_employee employee example] for the sake of
|
|
familiarity, but structured to allow separate compilation of the actual
|
|
parser in its own definition file and cpp file. The cpp files, including main
|
|
see only the header files --the interfaces. This is a good example on how X3
|
|
parsers are structured in a C++ application.
|
|
|
|
[heading Structure]
|
|
|
|
The program is structured in a directory with the following header and cpp
|
|
files:
|
|
|
|
[table
|
|
[[`File` ] [Description ]]
|
|
[[[@../../../example/x3/minimal/ast.hpp ast.hpp]] [The AST ]]
|
|
[[[@../../../example/x3/minimal/ast_adapted.hpp ast_adapted.hpp]] [Fusion adapters ]]
|
|
[[[@../../../example/x3/minimal/config.hpp config.hpp]] [Configuration ]]
|
|
[[[@../../../example/x3/minimal/employee.hpp employee.hpp]] [Main parser API ]]
|
|
[[[@../../../example/x3/minimal/employee_def.hpp employee_def.hpp]] [Parser definitions ]]
|
|
[[[@../../../example/x3/minimal/employee.cpp employee.cpp]] [Parser instantiation ]]
|
|
[[[@../../../example/x3/minimal/main.cpp main.cpp]] [Main program ]]
|
|
]
|
|
|
|
The contents of the files should already be familiar. It's essentially the
|
|
same [tutorial_employee employee example]. So I will skip the details on how
|
|
the parser works and focus only on the features needed for refactoring the
|
|
program into a modular structure suitable for real-world deployment.
|
|
|
|
[heading AST]
|
|
|
|
We place the AST declaration here:
|
|
|
|
namespace client { namespace ast
|
|
{
|
|
struct employee
|
|
{
|
|
int age;
|
|
std::string forename;
|
|
std::string surname;
|
|
double salary;
|
|
};
|
|
|
|
using boost::fusion::operator<<;
|
|
}}
|
|
|
|
[heading Fusion adapters]
|
|
|
|
Here, we adapt the AST for Fusion, making it a first-class fusion citizen:
|
|
|
|
BOOST_FUSION_ADAPT_STRUCT(client::ast::employee,
|
|
age, forename, surname, salary
|
|
)
|
|
|
|
[heading Main parser API]
|
|
|
|
This is the main header file that all other cpp files need to include.
|
|
|
|
[#__tutorial_spirit_declare__]
|
|
[heading BOOST_SPIRIT_DECLARE]
|
|
|
|
Remember [link __tutorial_spirit_define__ `BOOST_SPIRIT_DEFINE`]? If not,
|
|
then you probably want to go back and review that section to get a better
|
|
understanding of what's happening.
|
|
|
|
Here in the header file, instead of `BOOST_SPIRIT_DEFINE`, we use
|
|
`BOOST_SPIRIT_DECLARE` for the *top* rule. Behind the scenes, what's actually
|
|
happening is that we are declaring a `parse_rule` function in the client
|
|
namespace. For example, given a rule named `my_rule`,
|
|
`BOOST_SPIRIT_DECLARE(my_rule)` expands to this code:
|
|
|
|
template <typename Iterator, typename Context>
|
|
bool parse_rule(
|
|
decltype(my_rule)
|
|
, Iterator& first, Iterator const& last
|
|
, Context const& context, decltype(my_rule)::attribute_type& attr);
|
|
|
|
If you went back and reviewed [link __tutorial_spirit_define__
|
|
BOOST_SPIRIT_DEFINE], you'll see why it is exactly what we need to use for
|
|
header files. `BOOST_SPIRIT_DECLARE` generates function declarations that are
|
|
meant to be placed in hpp (header) files while `BOOST_SPIRIT_DEFINE`
|
|
generates function definitions that are meant to be placed in cpp files.
|
|
|
|
[note `BOOST_SPIRIT_DECLARE` is variadic and may be used for one or more rules.
|
|
Example: `BOOST_SPIRIT_DECLARE(r1, r2, r3);`]
|
|
|
|
In this example, the top rule is `employee`. We declare `employee` in this
|
|
header file:
|
|
|
|
namespace client
|
|
{
|
|
namespace parser
|
|
{
|
|
namespace x3 = boost::spirit::x3;
|
|
using employee_type = x3::rule<class employee, ast::employee>;
|
|
BOOST_SPIRIT_DECLARE(employee_type);
|
|
}
|
|
|
|
parser::employee_type employee();
|
|
}
|
|
|
|
We also provide a function that returns an `employee` object. This is the
|
|
parser that we will use anywhere it is needed. X3 parser objects are very
|
|
lightweight. They are basically simple tags with no data other than the name
|
|
of the rule (e.g. "employee"). Notice that we are passing this by value.
|
|
|
|
[heading Parser Definitions]
|
|
|
|
Here is where we place the actual rules that make up our grammar:
|
|
|
|
namespace parser
|
|
{
|
|
namespace x3 = boost::spirit::x3;
|
|
namespace ascii = boost::spirit::x3::ascii;
|
|
|
|
using x3::int_;
|
|
using x3::lit;
|
|
using x3::double_;
|
|
using x3::lexeme;
|
|
using ascii::char_;
|
|
|
|
x3::rule<class employee, ast::employee> const employee = "employee";
|
|
|
|
auto const quoted_string = lexeme['"' >> +(char_ - '"') >> '"'];
|
|
|
|
auto const employee_def =
|
|
lit("employee")
|
|
>> '{'
|
|
>> int_ >> ','
|
|
>> quoted_string >> ','
|
|
>> quoted_string >> ','
|
|
>> double_
|
|
>> '}'
|
|
;
|
|
|
|
BOOST_SPIRIT_DEFINE(employee);
|
|
}
|
|
|
|
parser::employee_type employee()
|
|
{
|
|
return parser::employee;
|
|
}
|
|
|
|
In the parser definition, we use [link __tutorial_spirit_define__
|
|
`BOOST_SPIRIT_DEFINE`] just like we did in the [tutorial_employee employee
|
|
example].
|
|
|
|
While this is another header file, it is not meant to be included by the
|
|
client. Its purpose is to be included by an instantiations cpp file (see
|
|
below). We place this in an `.hpp` file for flexibility, so we have the
|
|
freedom to instantiate the parser with different iterator types.
|
|
|
|
[#tutorial_configuration]
|
|
[heading Configuration]
|
|
|
|
Here, we declare some types for instatntaiting our X3 parser with. Rememeber
|
|
that Spirit parsers can work with any __fwditer__. We'll also need to provide
|
|
the initial context type. This is the context that X3 will use to initiate a
|
|
parse. For calling `phrase_parse`, you will need the `phrase_parse_context`
|
|
like we do below, passing in the skipper type.
|
|
|
|
using iterator_type = std::string::const_iterator;
|
|
using context_type = x3::phrase_parse_context<x3::ascii::space_type>::type;
|
|
|
|
For plain `parse`, we simply use `x3::unused_type`.
|
|
|
|
[heading Parser Instantiation]
|
|
|
|
Now we instantiate our parser here, for our specific configuration:
|
|
|
|
namespace client { namespace parser
|
|
{
|
|
BOOST_SPIRIT_INSTANTIATE(employee_type, iterator_type, context_type);
|
|
}}
|
|
|
|
For that, we use `BOOST_SPIRIT_INSTANTIATE`, passing in the parser type,
|
|
the iterator type, and the context type.
|
|
|
|
[heading BOOST_SPIRIT_INSTANTIATE]
|
|
|
|
Go back and review [link __tutorial_spirit_define__ `BOOST_SPIRIT_DEFINE`]
|
|
and [link __tutorial_spirit_declare__ `BOOST_SPIRIT_DECLARE`] to get a better
|
|
grasp of what's happening with `BOOST_SPIRIT_INSTANTIATE` and why it is
|
|
needed.
|
|
|
|
So what the heck is `BOOST_SPIRIT_INSTANTIATE`? What we want is to isolate
|
|
the instantiation of our parsers (rules and all that), into separate
|
|
translation units (or cpp files, if you will). In this example, we want to
|
|
place our x3 employee stuff in [@../../../example/x3/minimal/employee.cpp
|
|
employee.cpp]. That way, we have separate compilation. Every time we update
|
|
our employee parser source code, we only have to build the `employee.cpp`
|
|
file. All the rest will not be affected. By compiling only once in one
|
|
translation unit, we save on build times and avoid code bloat. There is no
|
|
code duplication, which can happen otherwise if you simply include the
|
|
employee parser ([@../../../example/x3/minimal/employee.hpp employee.hpp])
|
|
everywhere.
|
|
|
|
But how do you do that. Remember that our parser definitions are also placed
|
|
in its own header file for flexibility, so we have the freedom to instantiate
|
|
the parser with different iterator types.
|
|
|
|
What we need to do is explicitly instantiate the `parse_rule` function we
|
|
declared and defined via `BOOST_SPIRIT_DECLARE` and `BOOST_SPIRIT_DEFINE`
|
|
respectively, using `BOOST_SPIRIT_INSTANTIATE`. For our particular example,
|
|
`BOOST_SPIRIT_INSTANTIATE` expands to this code:
|
|
|
|
template bool parse_rule<iterator_type, context_type>(
|
|
employee_type rule_
|
|
, iterator_type& first, iterator_type const& last
|
|
, context_type const& context, employee_type::attribute_type& attr);
|
|
|
|
[heading Main Program]
|
|
|
|
Finally, we have our main program. The code is the same as single cpp file
|
|
[tutorial_employee employee example], but here, we simply include three
|
|
header files:
|
|
|
|
#include "ast.hpp"
|
|
#include "ast_adapted.hpp"
|
|
#include "employee.hpp"
|
|
|
|
# `ast.hpp` for the AST declaration
|
|
# `ast_adapted.hpp` if you need to traverse the AST using fusion
|
|
# `employee.hpp` the main parser API
|
|
|
|
[endsect]
|