Next: Calc++ Scanner, Previous: Calc++ Parsing Driver, Up: A Complete C++ Example [Contents][Index]
The grammar file parser.yy starts by asking for the C++ deterministic parser skeleton, the creation of the parser header file. Because the C++ skeleton changed several times, it is safer to require the version you designed the grammar for.
%skeleton "lalr1.cc" // -*- C++ -*- %require "3.8.1" %header
Because our scanner returns only genuine tokens and never simple characters (i.e., it returns ‘PLUS’, not ‘'+'’), we can avoid conversions.
%define api.token.raw
This example uses genuine C++ objects as semantic values, therefore, we
require the variant-based storage of semantic values. To make sure we
properly use it, we enable assertions. To fully benefit from type-safety
and more natural definition of “symbol”, we enable
api.token.constructor
.
%define api.token.constructor %define api.value.type variant %define parse.assert
Then come the declarations/inclusions needed by the semantic values. Because the parser uses the parsing driver and reciprocally, both would like to include the header of the other, which is, of course, insane. This mutual dependency will be broken using forward declarations. 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 use a forward declaration of the driver. See %code Summary.
%code requires { # include <string> class 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. %param { driver& drv }
Then we request location tracking.
%locations
Use the following two directives to enable parser tracing and detailed error messages. However, detailed error messages can contain incorrect information if lookahead correction is not enabled (see LAC).
%define parse.trace %define parse.error detailed %define parse.lac full
The code between ‘%code {’ and ‘}’ is output in the *.cc file; it needs detailed knowledge about the driver.
%code { # include "driver.hh" }
User friendly names are provided for each symbol. To avoid name clashes in
the generated files (see Calc++ Scanner), prefix tokens with TOK_
(see %define Summary).
%define api.token.prefix {TOK_} %token ASSIGN ":=" MINUS "-" PLUS "+" STAR "*" SLASH "/" LPAREN "(" RPAREN ")" ;
Since we use variant-based semantic values, %union
is not used, and
%token
, %nterm
and %type
expect genuine types, not type
tags.
%token <std::string> IDENTIFIER "identifier" %token <int> NUMBER "number" %nterm <int> exp
No %destructor
is needed to enable memory deallocation during error
recovery; the memory, for strings for instance, will be reclaimed by the
regular destructors. All the values are printed using their
operator<<
(see Printing Semantic Values).
%printer { yyo << $$; } <*>;
The grammar itself is straightforward (see Location Tracking Calculator: ltcalc
).
%% %start unit; unit: assignments exp { drv.result = $2; }; assignments: %empty {} | assignments assignment {}; assignment: "identifier" ":=" exp { drv.variables[$1] = $3; }; %left "+" "-"; %left "*" "/"; exp: "number" | "identifier" { $$ = drv.variables[$1]; } | exp "+" exp { $$ = $1 + $3; } | exp "-" exp { $$ = $1 - $3; } | exp "*" exp { $$ = $1 * $3; } | exp "/" exp { $$ = $1 / $3; } | "(" exp ")" { $$ = $2; } %%
Finally the error
member function reports the errors.
void yy::parser::error (const location_type& l, const std::string& m) { std::cerr << l << ": " << m << '\n'; }
Next: Calc++ Scanner, Previous: Calc++ Parsing Driver, Up: A Complete C++ Example [Contents][Index]