Here is the complete example of a simple calculator. The user types expressions, and the calculator prints the result. You can define and recall variables, too.
#include "boost/spirit.hpp"
#include "boost/spirit/phoenix/binders.hpp"
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/bind.hpp"
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <istream>
#include <map>
#include <ostream>
#include <string>
// Semantic actions can be functors. The operator() function is called
// with two iterators that specify the range of input that matches the production.
struct do_help {
template<typename Iter>
void operator()(Iter, Iter) const
{
std::cout << "help - to be implemented\n";
}
};
struct do_quit {
template<typename Iter>
void operator()(Iter, Iter) const
{
std::exit(EXIT_SUCCESS);
}
};
// Symbol table for storing variables.
typedef std::map<std::string, double> symtab_t;
struct calculator : boost::spirit::grammar<calculator>
{
// The parser object is copied a lot, so instead of keeping its own table
// of variables, it keeps track of a reference to a common table.
calculator(symtab_t& variables) : variables(variables) {}
// A production can have an associated closure, to store information
// for that production.
struct value_closure : boost::spirit::closure<value_closure, double>
{
member1 value;
};
struct assignment_closure :
boost::spirit::closure<assignment_closure, std::string, double>
{
member1 name;
member2 value;
};
struct string_closure : boost::spirit::closure<string_closure, std::string>
{
member1 name;
};
// Following is the grammar definition.
template <typename ScannerT>
struct definition
{
definition(calculator const& self)
{
using namespace boost::spirit;
using namespace phoenix;
// The commands are linked to functors or member functions,
// to demonstrate both styles. In real code, you should choose
// one style and use it uniformly.
command
= as_lower_d["help"][do_help()]
| as_lower_d["quit"][do_quit()]
| as_lower_d["dump"][bind(&calculator::dump)(self)]
;
// The lexeme_d directive tells the scanner to treat white space as
// significant. Thus, an identifier cannot have internal white space.
// The alpha_p and alnum_p parsers are built-in.
// Notice how the semantic action uses a Phoenix lambda function
// that constructs a std::string. The arg1 and arg2 placeholders are
// are bound at runtime to the iterator range that matches this rule.
identifier
= lexeme_d
[
( alpha_p | '_')
>> *( alnum_p | '_')
][identifier.name = construct_<std::string>(arg1, arg2)]
;
group
= '('
>> expression[group.value = arg1]
>> ')'
;
// An assignment statement must store the variable name and value.
// The name and the value are stored in the closure, then the define
// function is called to store the definition. Notice how a rule can
// have multiple semantic actions.
assignment
= identifier[assignment.name = arg1]
>> '='
>> expression[assignment.value = arg1]
[bind(&calculator::define)(self, assignment.name, assignment.value)]
;
// A statement can end at the end of the line, or with a semicolon.
statement
= ( command
| assignment
| expression[bind(&calculator::do_print)(self, arg1)]
)
>> (end_p | ';')
;
// The longest_d directive is built-in to tell the parser to make
// the longest match it can. Thus "1.23" matches real_p rather than
// int_p followed by ".23".
literal
= longest_d
[
int_p[literal.value = arg1]
| real_p[literal.value = arg1]
]
;
// A variable name must be looked up. This is a straightforward
// Phoenix binding.
factor
= literal[factor.value = arg1]
| group[factor.value = arg1]
| identifier[factor.value = bind(&calculator::lookup)(self, arg1)]
;
term
= factor[term.value = arg1]
>> *( ('*' >> factor[term.value *= arg1])
| ('/' >> factor[term.value /= arg1])
)
;
expression
= term[expression.value = arg1]
>> *( ('+' >> term[expression.value += arg1])
| ('-' >> term[expression.value -= arg1])
)
;
}
// The start symbol is returned from start().
boost::spirit::rule<ScannerT> const&
start() const { return statement; }
// Each rule must be declared, optionally with an associated closure.
boost::spirit::rule<ScannerT> command, statement;
boost::spirit::rule<ScannerT, assignment_closure::context_t> assignment;
boost::spirit::rule<ScannerT, string_closure::context_t> identifier;
boost::spirit::rule<ScannerT, value_closure::context_t> expression, factor,
group, literal, term;
};
// Member functions that are called in semantic actions.
void define(const std::string& name, double value) const
{
variables[name] = value;
}
double lookup(const std::string& name) const
{
symtab_t::iterator it = variables.find(name);
if (it == variables.end()) {
std::cerr << "undefined name: " << name << '\n';
return 0.0;
}
else
return (*it).second;
}
void do_print(double x) const
{
std::cout << x << '\n';
}
void dump() const
{
// Dump the entire symbol table. Notice how this function uses
// Boost lambda functions instead of Phoenix, just to show you that
// you can mix the two in a single file.
using namespace boost::lambda;
typedef std::pair<const std::string, double> symtab_pair;
for_each(variables.begin(), variables.end(),
std::cout << bind(&symtab_pair::first, _1) << '=' <<
bind(&symtab_pair::second, _1) << '\n');
}
private:
symtab_t& variables;
};
int main()
{
using namespace boost::spirit;
using namespace std;
symtab_t variables;
calculator calc(variables);
string line;
variables["pi"] = 3.141592653589792;
while (getline(cin, line))
{
// Read one line of text and parse it. If the parser does not consume
// the entire string, keep parsing the same string until an error occurs
// or the string is consumed. Then go back and read another string.
string::iterator first = line.begin();
parse_info<string::iterator> info;
do {
info = parse(first, line.end(), calc, space_p);
if (! info.hit)
// Display a caret that points to the position where the error
// was detected.
cerr << setw(info.stop - line.begin()) << " " << "^ error\n";
else if (! info.full)
// Keep track of where to start parsing the next statement.
first = info.stop;
} while(! info.full && info.hit);
}
}
Return to C++: Beyond the Standard Library.