Next: , Previous: Calc++ --- C++ Calculator, Up: A Complete C++ Example


10.1.6.2 Calc++ Parsing Driver

To support a pure interface with the parser (and the scanner) the technique of the “parsing context” is convenient: a structure containing all the data to exchange. Since, in addition to simply launch the parsing, there are several auxiliary tasks to execute (open the file for parsing, instantiate the parser etc.), we recommend transforming the simple parsing context structure into a fully blown parsing driver class.

The declaration of this driver class, calc++-driver.hh, is as follows. The first part includes the CPP guard and imports the required standard library components, and the declaration of the parser class.

     #ifndef CALCXX_DRIVER_HH
     # define CALCXX_DRIVER_HH
     # include <string>
     # include <map>
     # include "calc++-parser.hh"

Then comes the declaration of the scanning function. Flex expects the signature of yylex to be defined in the macro YY_DECL, and the C++ parser expects it to be declared. We can factor both as follows.

     // Tell Flex the lexer's prototype ...
     # define YY_DECL \
       yy::calcxx_parser::symbol_type yylex (calcxx_driver& driver)
     // ... and declare it for the parser's sake.
     YY_DECL;

The calcxx_driver class is then declared with its most obvious members.

     // Conducting the whole scanning and parsing of Calc++.
     class calcxx_driver
     {
     public:
       calcxx_driver ();
       virtual ~calcxx_driver ();
     
       std::map<std::string, int> variables;
     
       int result;

To encapsulate the coordination with the Flex scanner, it is useful to have member functions to open and close the scanning phase.

       // Handling the scanner.
       void scan_begin ();
       void scan_end ();
       bool trace_scanning;

Similarly for the parser itself.

       // Run the parser on file F.
       // Return 0 on success.
       int parse (const std::string& f);
       // The name of the file being parsed.
       // Used later to pass the file name to the location tracker.
       std::string file;
       // Whether parser traces should be generated.
       bool trace_parsing;

To demonstrate pure handling of parse errors, instead of simply dumping them on the standard error output, we will pass them to the compiler driver using the following two member functions. Finally, we close the class declaration and CPP guard.

       // Error handling.
       void error (const yy::location& l, const std::string& m);
       void error (const std::string& m);
     };
     #endif // ! CALCXX_DRIVER_HH

The implementation of the driver is straightforward. The parse member function deserves some attention. The error functions are simple stubs, they should actually register the located error messages and set error state.

     #include "calc++-driver.hh"
     #include "calc++-parser.hh"
     
     calcxx_driver::calcxx_driver ()
       : trace_scanning (false), trace_parsing (false)
     {
       variables["one"] = 1;
       variables["two"] = 2;
     }
     
     calcxx_driver::~calcxx_driver ()
     {
     }
     
     int
     calcxx_driver::parse (const std::string &f)
     {
       file = f;
       scan_begin ();
       yy::calcxx_parser parser (*this);
       parser.set_debug_level (trace_parsing);
       int res = parser.parse ();
       scan_end ();
       return res;
     }
     
     void
     calcxx_driver::error (const yy::location& l, const std::string& m)
     {
       std::cerr << l << ": " << m << std::endl;
     }
     
     void
     calcxx_driver::error (const std::string& m)
     {
       std::cerr << m << std::endl;
     }