1 /*=============================================================================
2 Copyright (c) 2002-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 ///////////////////////////////////////////////////////////////////////////////
8 //
9 // Based on the employee parser (see employee.cpp), this example shows how
10 // to implement error handling. This example also shows how to "inject" client
11 // data, using the "with" directive, that the handlers can access.
12 //
13 // [ JDG May 9, 2007 ]
14 // [ JDG May 13, 2015 ] spirit X3
15 // [ JDG Feb 19, 2018 ] Error handling for spirit X3
16 //
17 // I would like to thank Rainbowverse, llc (https://primeorbial.com/)
18 // for sponsoring this work and donating it to the community.
19 //
20 ///////////////////////////////////////////////////////////////////////////////
21
22 #include <boost/config/warning_disable.hpp>
23 #include <boost/spirit/home/x3.hpp>
24 #include <boost/spirit/home/x3/support/ast/position_tagged.hpp>
25 #include <boost/spirit/home/x3/support/utility/error_reporting.hpp>
26 #include <boost/spirit/home/x3/support/utility/annotate_on_success.hpp>
27 #include <boost/fusion/include/adapt_struct.hpp>
28 #include <boost/fusion/include/io.hpp>
29
30 #include <iostream>
31 #include <string>
32
33 namespace client { namespace ast
34 {
35 ///////////////////////////////////////////////////////////////////////////
36 // Our AST (employee and person structs)
37 ///////////////////////////////////////////////////////////////////////////
38 namespace x3 = boost::spirit::x3;
39
40 struct person : x3::position_tagged
41 {
personclient::ast::person42 person(
43 std::string const& first_name = ""
44 , std::string const& last_name = ""
45 )
46 : first_name(first_name)
47 , last_name(last_name)
48 {}
49
50 std::string first_name, last_name;
51 };
52
53 struct employee : x3::position_tagged
54 {
55 int age;
56 person who;
57 double salary;
58 };
59
60 using boost::fusion::operator<<;
61 }}
62
63 // We need to tell fusion about our employee struct
64 // to make it a first-class fusion citizen. This has to
65 // be in global scope.
66
67 BOOST_FUSION_ADAPT_STRUCT(client::ast::person,
68 first_name, last_name
69 )
70
71 BOOST_FUSION_ADAPT_STRUCT(client::ast::employee,
72 age, who, salary
73 )
74
75 namespace client
76 {
77 namespace parser
78 {
79 namespace x3 = boost::spirit::x3;
80 namespace ascii = boost::spirit::x3::ascii;
81
82 ///////////////////////////////////////////////////////////////////////
83 // Our error handler
84 ///////////////////////////////////////////////////////////////////////
85 struct error_handler
86 {
87 template <typename Iterator, typename Exception, typename Context>
on_errorclient::parser::error_handler88 x3::error_handler_result on_error(
89 Iterator& first, Iterator const& last
90 , Exception const& x, Context const& context)
91 {
92 auto& error_handler = x3::get<x3::error_handler_tag>(context).get();
93 std::string message = "Error! Expecting: " + x.which() + " here:";
94 error_handler(x.where(), message);
95 return x3::error_handler_result::fail;
96 }
97 };
98
99 ///////////////////////////////////////////////////////////////////////
100 // Our employee parser
101 ///////////////////////////////////////////////////////////////////////
102
103 using x3::int_;
104 using x3::double_;
105 using x3::lexeme;
106 using ascii::char_;
107
108 struct quoted_string_class;
109 struct person_class;
110 struct employee_class;
111
112 x3::rule<quoted_string_class, std::string> const quoted_string = "quoted_string";
113 x3::rule<person_class, ast::person> const person = "person";
114 x3::rule<employee_class, ast::employee> const employee = "employee";
115
116 auto const quoted_string_def = lexeme['"' >> +(char_ - '"') >> '"'];
117 auto const person_def = quoted_string > ',' > quoted_string;
118
119 auto const employee_def =
120 '{'
121 > int_ > ','
122 > person > ','
123 > double_
124 > '}'
125 ;
126
127 auto const employees = employee >> *(',' >> employee);
128
129 BOOST_SPIRIT_DEFINE(quoted_string, person, employee);
130
131 struct quoted_string_class {};
132 struct person_class : x3::annotate_on_success {};
133 struct employee_class : error_handler, x3::annotate_on_success {};
134 }
135 }
136
137 ///////////////////////////////////////////////////////////////////////////////
138 // Main program
139 ///////////////////////////////////////////////////////////////////////////////
140
141 ///////////////////////////////////////////////////////////////////////////////
142 // Our main parse entry point
143 ///////////////////////////////////////////////////////////////////////////////
144
parse(std::string const & input)145 void parse(std::string const& input)
146 {
147 using boost::spirit::x3::ascii::space;
148 typedef std::string::const_iterator iterator_type;
149
150 std::vector<client::ast::employee> ast;
151 iterator_type iter = input.begin();
152 iterator_type const end = input.end();
153
154 using boost::spirit::x3::with;
155 using boost::spirit::x3::error_handler_tag;
156 using error_handler_type = boost::spirit::x3::error_handler<iterator_type>;
157
158 // Our error handler
159 error_handler_type error_handler(iter, end, std::cerr);
160
161 // Our parser
162 using client::parser::employees;
163 auto const parser =
164 // we pass our error handler to the parser so we can access
165 // it later in our on_error and on_sucess handlers
166 with<error_handler_tag>(std::ref(error_handler))
167 [
168 employees
169 ];
170
171 bool r = phrase_parse(iter, end, parser, space, ast);
172
173 if (r && iter == end)
174 {
175 std::cout << boost::fusion::tuple_open('[');
176 std::cout << boost::fusion::tuple_close(']');
177 std::cout << boost::fusion::tuple_delimiter(", ");
178
179 std::cout << "-------------------------\n";
180 std::cout << "Parsing succeeded\n";
181
182 for (auto const& emp : ast)
183 {
184 std::cout << "got: " << emp << std::endl;
185 }
186 std::cout << "\n-------------------------\n";
187
188 }
189 else
190 {
191 std::cout << "-------------------------\n";
192 std::cout << "Parsing failed\n";
193 std::cout << "-------------------------\n";
194 ast.clear();
195 }
196 }
197
198 // Good sample:
199
200 std::string good_input = R"(
201 {
202 23,
203 "Amanda",
204 "Stefanski",
205 1000.99
206 },
207 {
208 35,
209 "Angie",
210 "Chilcote",
211 2000.99
212 },
213 {
214 43,
215 "Dannie",
216 "Dillinger",
217 3000.99
218 },
219 {
220 22,
221 "Dorene",
222 "Dole",
223 2500.99
224 },
225 {
226 38,
227 "Rossana",
228 "Rafferty",
229 5000.99
230 }
231 )";
232
233 // Input sample with error:
234
235 std::string bad_input = R"(
236 {
237 23,
238 "Amanda",
239 "Stefanski",
240 1000.99
241 },
242 {
243 35,
244 "Angie",
245 "Chilcote",
246 2000.99
247 },
248 {
249 43,
250 'I am not a person!' <--- this should be a person
251 3000.99
252 },
253 {
254 22,
255 "Dorene",
256 "Dole",
257 2500.99
258 },
259 {
260 38,
261 "Rossana",
262 "Rafferty",
263 5000.99
264 }
265 )";
266
267 int
main()268 main()
269 {
270 // Good input
271 parse(good_input);
272
273 // Bad input
274 std::cout << "Now we have some errors" << std::endl;
275 parse(bad_input);
276 return 0;
277 }
278