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
(akafields_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:
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.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.
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
andast::NonObjectVisitor
) to solve this problem was more elegant, rather than merging both of them inast::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 anast::ObjectVisitor
performing default visits of the remaining node types; the combined inheritance of bothast::DefaultVisitor
andast::ObjectVisitor
provides a complete default visitor.