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