1 | // Copyright 2011 Eric Niebler. Distributed under the Boost |
2 | // Software License, Version 1.0. (See accompanying file |
3 | // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) |
4 | // |
5 | // This is an example of how to specify a transform externally so |
6 | // that a single grammar can be used to drive multiple differnt |
7 | // calculations. In particular, it defines a calculator grammar |
8 | // that computes the result of an expression with either checked |
9 | // or non-checked division. |
10 | |
11 | #include <iostream> |
12 | #include <boost/mpl/int.hpp> |
13 | #include <boost/mpl/next.hpp> |
14 | #include <boost/mpl/min_max.hpp> |
15 | #include <boost/fusion/container/vector.hpp> |
16 | #include <boost/fusion/container/generation/make_vector.hpp> |
17 | #include <boost/proto/proto.hpp> |
18 | #include <boost/test/unit_test.hpp> |
19 | |
20 | namespace mpl = boost::mpl; |
21 | namespace proto = boost::proto; |
22 | namespace fusion = boost::fusion; |
23 | using proto::_; |
24 | |
25 | // The argument placeholder type |
26 | template<typename I> struct placeholder : I {}; |
27 | |
28 | // Give each rule in the grammar a "name". This is so that we |
29 | // can easily dispatch on it later. |
30 | struct calc_grammar; |
31 | struct divides_rule : proto::divides<calc_grammar, calc_grammar> {}; |
32 | |
33 | // Use external transforms in calc_gramar |
34 | struct calc_grammar |
35 | : proto::or_< |
36 | proto::when< |
37 | proto::terminal<placeholder<_> > |
38 | , proto::functional::at(proto::_state, proto::_value) |
39 | > |
40 | , proto::when< |
41 | proto::terminal<proto::convertible_to<double> > |
42 | , proto::_value |
43 | > |
44 | , proto::when< |
45 | proto::plus<calc_grammar, calc_grammar> |
46 | , proto::_default<calc_grammar> |
47 | > |
48 | , proto::when< |
49 | proto::minus<calc_grammar, calc_grammar> |
50 | , proto::_default<calc_grammar> |
51 | > |
52 | , proto::when< |
53 | proto::multiplies<calc_grammar, calc_grammar> |
54 | , proto::_default<calc_grammar> |
55 | > |
56 | // Note that we don't specify how division nodes are |
57 | // handled here. Proto::external_transform is a placeholder |
58 | // for an actual transform. |
59 | , proto::when< |
60 | divides_rule |
61 | , proto::external_transform |
62 | > |
63 | > |
64 | {}; |
65 | |
66 | template<typename E> struct calc_expr; |
67 | struct calc_domain : proto::domain<proto::generator<calc_expr> > {}; |
68 | |
69 | template<typename E> |
70 | struct calc_expr |
71 | : proto::extends<E, calc_expr<E>, calc_domain> |
72 | { |
73 | calc_expr(E const &e = E()) : calc_expr::proto_extends(e) {} |
74 | }; |
75 | |
76 | calc_expr<proto::terminal<placeholder<mpl::int_<0> > >::type> _1; |
77 | calc_expr<proto::terminal<placeholder<mpl::int_<1> > >::type> _2; |
78 | |
79 | // Use proto::external_transforms to map from named grammar rules to |
80 | // transforms. |
81 | struct non_checked_division |
82 | : proto::external_transforms< |
83 | proto::when< divides_rule, proto::_default<calc_grammar> > |
84 | > |
85 | {}; |
86 | |
87 | struct division_by_zero : std::exception {}; |
88 | |
89 | struct do_checked_divide |
90 | : proto::callable |
91 | { |
92 | typedef int result_type; |
93 | int operator()(int left, int right) const |
94 | { |
95 | if (right == 0) throw division_by_zero(); |
96 | return left / right; |
97 | } |
98 | }; |
99 | |
100 | // Use proto::external_transforms again, this time to map the divides_rule |
101 | // to a transforms that performs checked division. |
102 | struct checked_division |
103 | : proto::external_transforms< |
104 | proto::when< |
105 | divides_rule |
106 | , do_checked_divide(calc_grammar(proto::_left), calc_grammar(proto::_right)) |
107 | > |
108 | > |
109 | {}; |
110 | |
111 | BOOST_PROTO_DEFINE_ENV_VAR(mydata_tag, mydata); |
112 | |
113 | void test_external_transforms() |
114 | { |
115 | non_checked_division non_checked; |
116 | int result1 = calc_grammar()(_1 / _2, fusion::make_vector(arg: 6, arg: 2), non_checked); |
117 | BOOST_CHECK_EQUAL(result1, 3); |
118 | |
119 | // check that additional data slots are ignored |
120 | int result2 = calc_grammar()(_1 / _2, fusion::make_vector(arg: 8, arg: 2), (non_checked, mydata = "foo" )); |
121 | BOOST_CHECK_EQUAL(result2, 4); |
122 | |
123 | // check that we can use the dedicated slot for this purpose |
124 | int result3 = calc_grammar()(_1 / _2, fusion::make_vector(arg: 8, arg: 2), (42, proto::transforms = non_checked, mydata = "foo" )); |
125 | BOOST_CHECK_EQUAL(result2, 4); |
126 | |
127 | checked_division checked; |
128 | try |
129 | { |
130 | // This should throw |
131 | int result3 = calc_grammar()(_1 / _2, fusion::make_vector(arg: 6, arg: 0), checked); |
132 | BOOST_CHECK(!"Didn't throw an exception" ); // shouldn't get here! |
133 | } |
134 | catch(division_by_zero) |
135 | { |
136 | ; // OK |
137 | } |
138 | catch(...) |
139 | { |
140 | BOOST_CHECK(!"Unexpected exception" ); // shouldn't get here! |
141 | } |
142 | |
143 | try |
144 | { |
145 | // This should throw |
146 | int result4 = calc_grammar()(_1 / _2, fusion::make_vector(arg: 6, arg: 0), (checked, mydata = test_external_transforms)); |
147 | BOOST_CHECK(!"Didn't throw an exception" ); // shouldn't get here! |
148 | } |
149 | catch(division_by_zero) |
150 | { |
151 | ; // OK |
152 | } |
153 | catch(...) |
154 | { |
155 | BOOST_CHECK(!"Unexpected exception" ); // shouldn't get here! |
156 | } |
157 | |
158 | try |
159 | { |
160 | // This should throw |
161 | int result5 = calc_grammar()(_1 / _2, fusion::make_vector(arg: 6, arg: 0), (42, proto::transforms = checked, mydata = test_external_transforms)); |
162 | BOOST_CHECK(!"Didn't throw an exception" ); // shouldn't get here! |
163 | } |
164 | catch(division_by_zero) |
165 | { |
166 | ; // OK |
167 | } |
168 | catch(...) |
169 | { |
170 | BOOST_CHECK(!"Unexpected exception" ); // shouldn't get here! |
171 | } |
172 | } |
173 | |
174 | using namespace boost::unit_test; |
175 | /////////////////////////////////////////////////////////////////////////////// |
176 | // init_unit_test_suite |
177 | // |
178 | test_suite* init_unit_test_suite( int argc, char* argv[] ) |
179 | { |
180 | test_suite *test = BOOST_TEST_SUITE("test for external transforms" ); |
181 | |
182 | test->add(BOOST_TEST_CASE(&test_external_transforms)); |
183 | |
184 | return test; |
185 | } |
186 | |