Next: , Previous: Calc++ Parsing Driver, Up: A Complete C++ Example


10.1.6.3 Calc++ Parser

The grammar file calc++-parser.yy starts by asking for the C++ deterministic parser 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 "3.0.2"
     %defines
     %define parser_class_name {calcxx_parser}

This example will use genuine C++ objects as semantic values, therefore, we require the variant-based interface. 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 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.
     %param { calcxx_driver& driver }

Then we request location tracking, and initialize the first location's file name. Afterward new locations are computed relatively to the previous locations: the file name will be propagated.

     %locations
     %initial-action
     {
       // Initialize the initial location.
       @$.begin.filename = @$.end.filename = &driver.file;
     };

Use the following two directives to enable parser tracing and verbose error messages. However, verbose error messages can contain incorrect information (see LAC).

     %define parse.trace
     %define parse.error verbose

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 names are provided for each symbol. To avoid name clashes in the generated files (see Calc++ Scanner), prefix tokens with TOK_ (see api.token.prefix).

     %define api.token.prefix {TOK_}
     %token
       END  0  "end of file"
       ASSIGN  ":="
       MINUS   "-"
       PLUS    "+"
       STAR    "*"
       SLASH   "/"
       LPAREN  "("
       RPAREN  ")"
     ;

Since we use variant-based semantic values, %union is not used, and both %type and %token expect genuine types, as opposed to type tags.

     %token <std::string> IDENTIFIER "identifier"
     %token <int> NUMBER "number"
     %type  <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 { yyoutput << $$; } <*>;

The grammar itself is straightforward (see Location Tracking Calculator: ltcalc).

     %%
     %start unit;
     unit: assignments exp  { driver.result = $2; };
     
     assignments:
       %empty                 {}
     | assignments assignment {};
     
     assignment:
       "identifier" ":=" exp { driver.variables[$1] = $3; };
     
     %left "+" "-";
     %left "*" "/";
     exp:
       exp "+" exp   { $$ = $1 + $3; }
     | exp "-" exp   { $$ = $1 - $3; }
     | exp "*" exp   { $$ = $1 * $3; }
     | exp "/" exp   { $$ = $1 / $3; }
     | "(" exp ")"   { std::swap ($$, $2); }
     | "identifier"  { $$ = driver.variables[$1]; }
     | "number"      { std::swap ($$, $1); };
     %%

Finally the error member function registers the errors to the driver.

     void
     yy::calcxx_parser::error (const location_type& l,
                               const std::string& m)
     {
       driver.error (l, m);
     }