Next: , Previous: , Up: A Complete C++ Example   [Contents][Index]


10.1.8.3 Calc++ Parser

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]