TC-2 FAQ

A NameTy, or a symbol

At some places, you may use one or the other. Just ask yourself which is the most appropriate given the context. Appel is not always right.

Bison

Be sure to read its dedicated section: RE/Flex & Bison.

Memory leaks in the parser during error recovery

To reclaim the memory during error recovery, use the %destructor directive:

%type <ast::Exp*> exp
%type <ast::Var*> lvalue
%destructor { delete $$; } <ast::Exp*> <ast::Var*> /* ... */;
Memory leaks in the standard containers

See Valgrind, The Ultimate Memory Debugger, for a pointer to the explanation and solution.

How do I use misc::error

See The lib/misc Directory, for a description of this component. In the case of the parse module, TigerDriver aggregates the local error handler. From scan_open, for instance, your code should look like:

if (!yyin)
  error_ << misc::error::failure
         << program_name << ": cannot open `" << name << "': "
         << strerror(errno) << std::endl
         << &misc::error::exit;
ast::fields_type vs. ast::VarChunk, Record definition vs. Function declaration

The grammar of the Tiger language (see Syntactic Specifications in Tiger Compiler Reference Manual) includes:

# Function, primitive and method declarations.
<dec> ::=
    "function"  <id> "(" <tyfields> ")" [ ":" <type-id> ] "=" <exp>
  | "primitive" <id> "(" <tyfields> ")" [ ":" <type-id> ]
<classfield> ::=
    "method"    <id> "(" <tyfields> ")" [ ":" <type-id> ] "=" <exp>

# Record type declaration.
<ty> ::= "{" <tyfields>  "}"

# List of "id : type".
<tyfields> ::= [ <id> ":" <type-id> { "," <id> ":" <type-id> } ]

This grammar snippet shows that we used tyfields several times, in two very different contexts: a list of formal arguments of a function, primitive or method; and a list of record fields. The fact that the syntax is similar in both cases is an “accident”: it is by no means required by the language. A. Appel could have chosen to make them different, but what would have been the point then? It does make sense, sometimes, to make two different things look alike, that’s a form of economy - a sane engineering principle.

If the concrete syntaxes were chosen to be identical, should it be the case for abstract too? We would say it depends: the inert data is definitely the same, but the behaviors (i.e., the handling in the various visitors) are very different. So if your language features “inert data”, say C or ML, then keeping the same abstract syntax makes sense if your language features “active data” - let’s call this… objects - then it is a mistake. Sadly enough, the first edition of Red Tiger book made this mistake, and we also did it for years.

The second edition of the Tiger in Java introduces a dedicated abstract syntax for formal arguments; we made a different choice: there is little difference between formal arguments and local variables, so we use a VarChunk, which fits nicely with the semantics of chunks.

Regarding the abstract syntax of a record type declaration, we use a list of Fields (aka fields_type).

Of course this means that you will have to duplicate your parsing of the tyfields non-terminal in your parser.

ast::DefaultVisitor and ast::NonObjectVisitor

The existence of ast::NonObjectVisitor is the result of a reasonable compromise between (relative) safety and complexity.

The problem is: as object-aware programs are to be desugared into object-free ones, (a part of) our front-end infrastructure must support two kinds of traversals:

  • Traversals dealing with AST with objects: ast::PrettyPrinter, object::Binder, object::TypeChecker, object::DesugarVisitor.

  • Traversals dealing with AST without objects bind::Binder, type::TypeChecker, and all other AST visitors.

The first category has visit methods for all type of nodes of our (object-oriented) AST, so they raise no issue. On the other hand, the second category of visitors knows nothing about objects, and should either be unable to visit AST w/ objects (static solution) or raise an error if they encounter objects (dynamic solution).

Which led us to several solutions:

  1. Consider that we have two kinds of visitors, and thus two hierarchies of visitors. Two hierarchies might confuse the students, and make the maintenance harder. Hooks in the AST nodes (accept methods) must be duplicated, too.

  2. Have a single hierarchy of visitors, but equip all concrete visitors traversing ASTs w/o objects with methods visiting object-related node aborting at run time.

  3. Likewise, but factor the aborting methods in a single place, namely ast::NonObjectVisitor. That is is the solution we chose.

Solutions 2 and 3 let us provide a default visitor for ASTs without objects, but it’s harder to have a meaningful default visitor for ASTs with objects: indeed, concrete visitors on ASTs w/ objects inherit from their non-object counterparts, where methods visiting object nodes are already defined! (Though they abort at run time.)

We have found that having two visitors (ast::DefaultVisitor and ast::NonObjectVisitor) to solve this problem was more elegant, rather than merging both of them in ast::DefaultVisitor. The pros are that ast::DefaultVisitor remains a default visitor; the cons are that this visitor is now abstract, since object-related nodes have no visit implementation. Therefore, we also introduced an ast::ObjectVisitor performing default visits of the remaining node types; the combined inheritance of both ast::DefaultVisitor and ast::ObjectVisitor provides a complete default visitor.