Next: , Previous: Calc++ Parser, Up: A Complete C++ Example


10.1.6.4 Calc++ Scanner

The Flex scanner first includes the driver declaration, then the parser's to get the set of defined tokens.

     %{ /* -*- C++ -*- */
     # include <cerrno>
     # include <climits>
     # include <cstdlib>
     # include <string>
     # include "calc++-driver.hh"
     # include "calc++-parser.hh"
     
     // Work around an incompatibility in flex (at least versions
     // 2.5.31 through 2.5.33): it generates code that does
     // not conform to C89.  See Debian bug 333231
     // <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=333231>.
     # undef yywrap
     # define yywrap() 1
     
     // The location of the current token.
     static yy::location loc;
     %}

Because there is no #include-like feature we don't need yywrap, we don't need unput either, and we parse an actual file, this is not an interactive session with the user. Finally, we enable scanner tracing.

     %option noyywrap nounput batch debug noinput

Abbreviations allow for more readable rules.

     id    [a-zA-Z][a-zA-Z_0-9]*
     int   [0-9]+
     blank [ \t]

The following paragraph suffices to track locations accurately. Each time yylex is invoked, the begin position is moved onto the end position. Then when a pattern is matched, its width is added to the end column. When matching ends of lines, the end cursor is adjusted, and each time blanks are matched, the begin cursor is moved onto the end cursor to effectively ignore the blanks preceding tokens. Comments would be treated equally.

     %{
       // Code run each time a pattern is matched.
       # define YY_USER_ACTION  loc.columns (yyleng);
     %}
     %%
     %{
       // Code run each time yylex is called.
       loc.step ();
     %}
     {blank}+   loc.step ();
     [\n]+      loc.lines (yyleng); loc.step ();

The rules are simple. The driver is used to report errors.

     "-"      return yy::calcxx_parser::make_MINUS(loc);
     "+"      return yy::calcxx_parser::make_PLUS(loc);
     "*"      return yy::calcxx_parser::make_STAR(loc);
     "/"      return yy::calcxx_parser::make_SLASH(loc);
     "("      return yy::calcxx_parser::make_LPAREN(loc);
     ")"      return yy::calcxx_parser::make_RPAREN(loc);
     ":="     return yy::calcxx_parser::make_ASSIGN(loc);
     
     {int}      {
       errno = 0;
       long n = strtol (yytext, NULL, 10);
       if (! (INT_MIN <= n && n <= INT_MAX && errno != ERANGE))
         driver.error (loc, "integer is out of range");
       return yy::calcxx_parser::make_NUMBER(n, loc);
     }
     {id}       return yy::calcxx_parser::make_IDENTIFIER(yytext, loc);
     .          driver.error (loc, "invalid character");
     <<EOF>>    return yy::calcxx_parser::make_END(loc);
     %%

Finally, because the scanner-related driver's member-functions depend on the scanner's data, it is simpler to implement them in this file.

     void
     calcxx_driver::scan_begin ()
     {
       yy_flex_debug = trace_scanning;
       if (file.empty () || file == "-")
         yyin = stdin;
       else if (!(yyin = fopen (file.c_str (), "r")))
         {
           error ("cannot open " + file + ": " + strerror(errno));
           exit (EXIT_FAILURE);
         }
     }
     
     void
     calcxx_driver::scan_end ()
     {
       fclose (yyin);
     }