With the scanner and parser classes complete, we need to create a main program which uses them both. The parser needs to know about the scanner (the parser class has a member variable which is a pointer to the scanner object), so the scanner needs to be created first. A C++ flex
scanner reads by default from the standard input, and whilst member functions for switching streams are available, it was decided that using member function std::istream::rdbuf()
to link a disk-file to std::cin
was a better solution, and similarly for std::cout
where class Tree
‘s member output
object is hard-wired:
int main() {
// ...
std::istream *infile{ nullptr };
std::ostream *outfile{ nullptr };
// initalize std::fstream objects
if (infile && *infile) {
std::cin.rdbuf(infile->rdbuf()); // switch std::streambuf's
// ...
if (*outfile) {
std::cout.rdbuf(outfile->rdbuf()); // switch std::streambuf's
}
else {
// report error
Initializing the scanner and parser classes is now not difficult, with the parser class constructor taking four parameters, these being pointers to all of: scanner object, global symbol table, head node and output stream for error reporting. The head node is defined as being the variable declarations for the global part of the program being compiled; the address of this object becomes prev
in the parser, meaning that the completed syntax tree doesn’t have to be attached to a head node as a final step of successful parsing. Error reporting is achieved by a call to yy::Parser::error()
with a location and error message; only here are the location tracking values ever used.
yy::Lexer scanner{};
Symbol table{};
Decls start{ &table };
yy::Parser parser{ &scanner, &table, &start, &std::cerr };
The part of the main program which does all of the hard work is the call parser.parse()
, which takes no parameters and returns zero on successful parsing, or non-zero otherwise. This function should not throw an exception, but the code generation stage may do so, meaning exceptions need to be guarded against in the call to start.emit()
. Code generation is only attempted in the case of successful parsing:
int returncode = 1;
// ...
try {
if (!parser.parse()) {
if (support_nodejs) {
std::cout << support_nodejs_code;
}
start.emit();
returncode = 0;
}
}
catch (std::exception& e) {
std::cerr << "Fatal compiler inconsistency: " << e.what() << '\n';
}
catch (...) {
std::cerr << "Fatal compiler inconsistency: unknown\n";
}
return returncode;
}
That’s this mini-series wrapped up for now, any major changes in this compiler program are unlikely, but should there be any, then the relevant articles will be updated.