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
20namespace mpl = boost::mpl;
21namespace proto = boost::proto;
22namespace fusion = boost::fusion;
23using proto::_;
24
25// The argument placeholder type
26template<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.
30struct calc_grammar;
31struct divides_rule : proto::divides<calc_grammar, calc_grammar> {};
32
33// Use external transforms in calc_gramar
34struct 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
66template<typename E> struct calc_expr;
67struct calc_domain : proto::domain<proto::generator<calc_expr> > {};
68
69template<typename E>
70struct 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
76calc_expr<proto::terminal<placeholder<mpl::int_<0> > >::type> _1;
77calc_expr<proto::terminal<placeholder<mpl::int_<1> > >::type> _2;
78
79// Use proto::external_transforms to map from named grammar rules to
80// transforms.
81struct non_checked_division
82 : proto::external_transforms<
83 proto::when< divides_rule, proto::_default<calc_grammar> >
84 >
85{};
86
87struct division_by_zero : std::exception {};
88
89struct 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.
102struct 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
111BOOST_PROTO_DEFINE_ENV_VAR(mydata_tag, mydata);
112
113void 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
174using namespace boost::unit_test;
175///////////////////////////////////////////////////////////////////////////////
176// init_unit_test_suite
177//
178test_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

source code of boost/libs/proto/test/external_transforms.cpp