10.1.6.3 Calc++ Parser
The parser definition file ‘calc++-parser.yy’ starts by asking for
the C++ LALR(1) skeleton, the creation of the parser header file, and
specifies the name of the parser class. Because the C++ skeleton
changed several times, it is safer to require the version you designed
the grammar for.
| %skeleton "lalr1.cc" /* -*- C++ -*- */
%require "2.4"
%defines
%define parser_class_name "calcxx_parser"
|
Then come the declarations/inclusions needed to define the
%union
. Because the parser uses the parsing driver and
reciprocally, both cannot include the header of the other. Because the
driver's header needs detailed knowledge about the parser class (in
particular its inner types), it is the parser's header which will simply
use a forward declaration of the driver.
See section %code.
| %code requires {
# include <string>
class calcxx_driver;
}
|
The driver is passed by reference to the parser and to the scanner.
This provides a simple but effective pure interface, not relying on
global variables.
| // The parsing context.
%parse-param { calcxx_driver& driver }
%lex-param { calcxx_driver& driver }
|
Then we request the location tracking feature, and initialize the
first location's file name. Afterwards new locations are computed
relatively to the previous locations: the file name will be
automatically propagated.
| %locations
%initial-action
{
// Initialize the initial location.
@$.begin.filename = @$.end.filename = &driver.file;
};
|
Use the two following directives to enable parser tracing and verbose
error messages.
Semantic values cannot use “real” objects, but only pointers to
them.
| // Symbols.
%union
{
int ival;
std::string *sval;
};
|
The code between ‘%code {’ and ‘}’ is output in the
‘*.cc’ file; it needs detailed knowledge about the driver.
| %code {
# include "calc++-driver.hh"
}
|
The token numbered as 0 corresponds to end of file; the following line
allows for nicer error messages referring to “end of file” instead
of “$end”. Similarly user friendly named are provided for each
symbol. Note that the tokens names are prefixed by TOKEN_
to
avoid name clashes.
| %token END 0 "end of file"
%token ASSIGN ":="
%token <sval> IDENTIFIER "identifier"
%token <ival> NUMBER "number"
%type <ival> exp
|
To enable memory deallocation during error recovery, use
%destructor
.
| %printer { debug_stream () << *$$; } "identifier"
%destructor { delete $$; } "identifier"
%printer { debug_stream () << $$; } <ival>
|
The grammar itself is straightforward.
| %%
%start unit;
unit: assignments exp { driver.result = $2; };
assignments: assignments assignment {}
| /* Nothing. */ {};
assignment:
"identifier" ":=" exp
{ driver.variables[*$1] = $3; delete $1; };
%left '+' '-';
%left '*' '/';
exp: exp '+' exp { $$ = $1 + $3; }
| exp '-' exp { $$ = $1 - $3; }
| exp '*' exp { $$ = $1 * $3; }
| exp '/' exp { $$ = $1 / $3; }
| "identifier" { $$ = driver.variables[*$1]; delete $1; }
| "number" { $$ = $1; };
%%
|
Finally the error
member function registers the errors to the
driver.
| void
yy::calcxx_parser::error (const yy::calcxx_parser::location_type& l,
const std::string& m)
{
driver.error (l, m);
}
|