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 {
calc_exprcalc_expr73 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;
operator ()do_checked_divide93 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
test_external_transforms()113 void test_external_transforms()
114 {
115 non_checked_division non_checked;
116 int result1 = calc_grammar()(_1 / _2, fusion::make_vector(6, 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(8, 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(8, 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(6, 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(6, 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(6, 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 //
init_unit_test_suite(int argc,char * argv[])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