Boost logo

Boost-Commit :

Subject: [Boost-commit] svn:boost r76055 - in trunk/libs/proto: doc example
From: eric_at_[hidden]
Date: 2011-12-18 19:22:36


Author: eric_niebler
Date: 2011-12-18 19:22:35 EST (Sun, 18 Dec 2011)
New Revision: 76055
URL: http://svn.boost.org/trac/boost/changeset/76055

Log:
user docs and an example for external transforms
Added:
   trunk/libs/proto/example/external_transforms.cpp (contents, props changed)
Text files modified:
   trunk/libs/proto/doc/back_end.qbk | 168 ++++++++++++++++++++++++++++++++++++++++
   trunk/libs/proto/doc/examples.qbk | 11 ++
   trunk/libs/proto/example/Jamfile.v2 | 5 +
   3 files changed, 184 insertions(+), 0 deletions(-)

Modified: trunk/libs/proto/doc/back_end.qbk
==============================================================================
--- trunk/libs/proto/doc/back_end.qbk (original)
+++ trunk/libs/proto/doc/back_end.qbk 2011-12-18 19:22:35 EST (Sun, 18 Dec 2011)
@@ -1158,6 +1158,174 @@
 
 [endsect]
 
+[/=============================================================]
+[section:external_transforms Separating Grammars And Transforms]
+[/=============================================================]
+
+[note This is an advanced topic that is only necessary for people defining large EDSLs. Feel free to skip this if you're just getting started with Proto.]
+
+So far, we've seen examples of grammars with embedded transforms. In practice, grammars can get pretty large, and you may want to use them to drive several different computations. For instance, you may have a grammar for a linear algebra domain, and you may want to use it to compute the shape of the result (vector or matrix?) and also to compute the result optimally. You don't want to have to copy and paste the whole shebang just to tweak one of the embedded transforms. What you want instead is to define the grammar once, and specify the transforms later when you're ready to evaluate an expression. For that, you use /external transforms/.
+
+Let's begin this exercise by revisiting our old friend the calculator EDSL. Here is a bare-bones front end that defines a domain, a grammar, an expression wrapper, and some placeholder terminals.
+
+ #include <boost/assert.hpp>
+ #include <boost/mpl/int.hpp>
+ #include <boost/fusion/container/vector.hpp>
+ #include <boost/fusion/container/generation/make_vector.hpp>
+ #include <boost/proto/proto.hpp>
+ namespace mpl = boost::mpl;
+ namespace proto = boost::proto;
+ namespace fusion = boost::fusion;
+
+ // The argument placeholder type
+ template<typename I> struct placeholder : I {};
+
+ // The grammar for valid calculator expressions
+ struct calc_grammar
+ : proto::or_<
+ proto::terminal<placeholder<proto::_> >
+ , proto::terminal<int>
+ , proto::plus<calc_grammar, calc_grammar>
+ , proto::minus<calc_grammar, calc_grammar>
+ , proto::multiplies<calc_grammar, calc_grammar>
+ , proto::divides<calc_grammar, calc_grammar>
+ >
+ {};
+
+ template<typename E> struct calc_expr;
+ struct calc_domain : proto::domain<proto::generator<calc_expr> > {};
+
+ template<typename E>
+ struct calc_expr
+ : proto::extends<E, calc_expr<E>, calc_domain>
+ {
+ calc_expr(E const &e = E()) : calc_expr::proto_extends(e) {}
+ };
+
+ calc_expr<proto::terminal<placeholder<mpl::int_<0> > >::type> _1;
+ calc_expr<proto::terminal<placeholder<mpl::int_<1> > >::type> _2;
+
+ int main()
+ {
+ // Build a calculator expression, and do nothing with it.
+ (_1 + _2);
+ }
+
+Now, let's embed transforms into `calc_grammar` so that we can use it to evaluate calculator expressions:
+
+ // The calculator grammar with embedded transforms for evaluating expression.
+ struct calc_grammar
+ : proto::or_<
+ proto::when<
+ proto::terminal<placeholder<proto::_> >
+ , proto::functional::at(proto::_state, proto::_value)
+ >
+ , proto::when<
+ proto::terminal<int>
+ , proto::_value
+ >
+ , proto::when<
+ proto::plus<calc_grammar, calc_grammar>
+ , proto::_default<calc_grammar>
+ >
+ , proto::when<
+ proto::minus<calc_grammar, calc_grammar>
+ , proto::_default<calc_grammar>
+ >
+ , proto::when<
+ proto::multiplies<calc_grammar, calc_grammar>
+ , proto::_default<calc_grammar>
+ >
+ , proto::when<
+ proto::divides<calc_grammar, calc_grammar>
+ , proto::_default<calc_grammar>
+ >
+ >
+ {};
+
+With this definition of `calc_grammar` we can evaluate expressions by passing along a Fusion vector containing the values to use for the `_1` and `_2` placeholders:
+
+ int result = calc_grammar()(_1 + _2, fusion::make_vector(3, 4));
+ BOOST_ASSERT(result == 7);
+
+Now, imagine we want an alternative evaluation strategy that checks for division by zero and throws an exception. Just how rediculous would it be to copy the entire `calc_grammar` just to change the one line that transforms division expressions?! There are a few ways to solve this problem. One would be to make `calc_grammar` a template parameterized on the division transform. Instead, let's use an external transform.
+
+First, we give the division rule in our grammar a "name"; that is, we make it a struct. We'll use this type later to dispatch to the right transforms.
+
+ struct calc_grammar;
+ struct divides_rule : proto::divides<calc_grammar, calc_grammar> {};
+
+Next, we change `calc_grammar` to make the handling of division expressions external.
+
+ // The calculator grammar with an external transform for evaluating
+ // division expressions.
+ struct calc_grammar
+ : proto::or_<
+ /* ... as before ... */
+ , proto::when<
+ divides_rule
+ , proto::external_transform
+ >
+ >
+ {};
+
+The use of `proto::external_transform` above makes the handling of division expressions externally parameterizeable.
+
+Next, we use `proto::external_transforms<>` (note the trailing 's') to capture our evaluation strategy in a bundle that we can pass along to the transform in the data parameter. Read on for the explanation.
+
+ // Evaluate division nodes as before
+ struct non_checked_division
+ : proto::external_transforms<
+ proto::when< divides_rule, proto::_default<calc_grammar> >
+ >
+ {};
+
+ /* ... */
+
+ non_checked_division non_checked;
+ int result2 = calc_grammar()(_1 / _2, fusion::make_vector(6, 2), non_checked);
+
+The struct `non_cecked_division` associates the transform `proto::_default<calc_grammar>` with the `divides_rule` grammar rule. An instance of that struct is passed along as the third parameter when invoking `calc_grammar`.
+
+Now, let's implement checked division. The rest should be unsurprising.
+
+ struct division_by_zero : std::exception {};
+
+ struct do_checked_divide : proto::callable
+ {
+ typedef int result_type;
+ int operator()(int left, int right) const
+ {
+ if (right == 0) throw division_by_zero();
+ return left / right;
+ }
+ };
+
+ struct checked_division
+ : proto::external_transforms<
+ proto::when<
+ divides_rule
+ , do_checked_divide(calc_grammar(proto::_left), calc_grammar(proto::_right))
+ >
+ >
+ {};
+
+ /* ... */
+
+ try
+ {
+ checked_division checked;
+ int result3 = calc_grammar_extern()(_1 / _2, fusion::make_vector(6, 0), checked);
+ }
+ catch(division_by_zero)
+ {
+ std::cout << "caught division by zero!\n";
+ }
+
+The above code demonstrates how a single grammar can be used with different transforms specified externally. This makes it possibly to reuse a grammar to drive several different computations.
+
+[endsect]
+
 [/====================================================]
 [section:canned_transforms Proto's Built-In Transforms]
 [/====================================================]

Modified: trunk/libs/proto/doc/examples.qbk
==============================================================================
--- trunk/libs/proto/doc/examples.qbk (original)
+++ trunk/libs/proto/doc/examples.qbk 2011-12-18 19:22:35 EST (Sun, 18 Dec 2011)
@@ -18,6 +18,7 @@
 [import ../example/map_assign.cpp]
 [import ../example/futures.cpp]
 [import ../example/mini_lambda.cpp]
+[import ../example/external_transforms.cpp]
 
 [/===============]
 [section Examples]
@@ -158,4 +159,14 @@
 
 [endsect]
 
+[/===============================================================================]
+[section:checked_calc Checked Calculator: A Simple Example of External Transforms]
+[/===============================================================================]
+
+This is an advanced example that shows how to externally parameterize a grammar's transforms. It defines a calculator EDSL with a grammar that can perform either checked or unchecked arithmetic.
+
+[CheckedCalc]
+
+[endsect]
+
 [endsect]

Modified: trunk/libs/proto/example/Jamfile.v2
==============================================================================
--- trunk/libs/proto/example/Jamfile.v2 (original)
+++ trunk/libs/proto/example/Jamfile.v2 2011-12-18 19:22:35 EST (Sun, 18 Dec 2011)
@@ -72,3 +72,8 @@
     :
         virtual_member.cpp
     ;
+
+exe external_transforms
+ :
+ external_transforms.cpp
+ ;

Added: trunk/libs/proto/example/external_transforms.cpp
==============================================================================
--- (empty file)
+++ trunk/libs/proto/example/external_transforms.cpp 2011-12-18 19:22:35 EST (Sun, 18 Dec 2011)
@@ -0,0 +1,128 @@
+//[ CheckedCalc
+// Copyright 2011 Eric Niebler. Distributed under the Boost
+// Software License, Version 1.0. (See accompanying file
+// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+// This is an example of how to specify a transform externally so
+// that a single grammar can be used to drive multiple differnt
+// calculations. In particular, it defines a calculator grammar
+// that computes the result of an expression with either checked
+// or non-checked division.
+
+#include <iostream>
+#include <boost/assert.hpp>
+#include <boost/mpl/int.hpp>
+#include <boost/mpl/next.hpp>
+#include <boost/mpl/min_max.hpp>
+#include <boost/fusion/container/vector.hpp>
+#include <boost/fusion/container/generation/make_vector.hpp>
+#include <boost/proto/proto.hpp>
+namespace mpl = boost::mpl;
+namespace proto = boost::proto;
+namespace fusion = boost::fusion;
+
+// The argument placeholder type
+template<typename I> struct placeholder : I {};
+
+// Give each rule in the grammar a "name". This is so that we
+// can easily dispatch on it later.
+struct calc_grammar;
+struct divides_rule : proto::divides<calc_grammar, calc_grammar> {};
+
+// Use external transforms in calc_gramar
+struct calc_grammar
+ : proto::or_<
+ proto::when<
+ proto::terminal<placeholder<proto::_> >
+ , proto::functional::at(proto::_state, proto::_value)
+ >
+ , proto::when<
+ proto::terminal<proto::convertible_to<double> >
+ , proto::_value
+ >
+ , proto::when<
+ proto::plus<calc_grammar, calc_grammar>
+ , proto::_default<calc_grammar>
+ >
+ , proto::when<
+ proto::minus<calc_grammar, calc_grammar>
+ , proto::_default<calc_grammar>
+ >
+ , proto::when<
+ proto::multiplies<calc_grammar, calc_grammar>
+ , proto::_default<calc_grammar>
+ >
+ // Note that we don't specify how division nodes are
+ // handled here. Proto::external_transform is a placeholder
+ // for an actual transform.
+ , proto::when<
+ divides_rule
+ , proto::external_transform
+ >
+ >
+{};
+
+template<typename E> struct calc_expr;
+struct calc_domain : proto::domain<proto::generator<calc_expr> > {};
+
+template<typename E>
+struct calc_expr
+ : proto::extends<E, calc_expr<E>, calc_domain>
+{
+ calc_expr(E const &e = E()) : calc_expr::proto_extends(e) {}
+};
+
+calc_expr<proto::terminal<placeholder<mpl::int_<0> > >::type> _1;
+calc_expr<proto::terminal<placeholder<mpl::int_<1> > >::type> _2;
+
+// Use proto::external_transforms to map from named grammar rules to
+// transforms.
+struct non_checked_division
+ : proto::external_transforms<
+ proto::when< divides_rule, proto::_default<calc_grammar> >
+ >
+{};
+
+struct division_by_zero : std::exception {};
+
+struct do_checked_divide
+ : proto::callable
+{
+ typedef int result_type;
+ int operator()(int left, int right) const
+ {
+ if (right == 0) throw division_by_zero();
+ return left / right;
+ }
+};
+
+// Use proto::external_transforms again, this time to map the divides_rule
+// to a transforms that performs checked division.
+struct checked_division
+ : proto::external_transforms<
+ proto::when<
+ divides_rule
+ , do_checked_divide(calc_grammar(proto::_left), calc_grammar(proto::_right))
+ >
+ >
+{};
+
+int main()
+{
+ non_checked_division non_checked;
+ int result2 = calc_grammar()(_1 / _2, fusion::make_vector(6, 2), non_checked);
+ BOOST_ASSERT(result2 == 3);
+
+ try
+ {
+ checked_division checked;
+ // This should throw
+ int result3 = calc_grammar()(_1 / _2, fusion::make_vector(6, 0), checked);
+ BOOST_ASSERT(false); // shouldn't get here!
+ }
+ catch(division_by_zero)
+ {
+ std::cout << "caught division by zero!\n";
+ }
+}
+//]


Boost-Commit list run by bdawes at acm.org, david.abrahams at rcn.com, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk