518 lines
21 KiB
C++
518 lines
21 KiB
C++
|
|
|
|
#include <cassert>
|
|
#include <iostream>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <queue>
|
|
#include "cool-tree.h"
|
|
#include "semant.h"
|
|
#include "utilities.h"
|
|
|
|
|
|
extern int semant_debug;
|
|
extern char *curr_filename;
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Symbols
|
|
//
|
|
// For convenience, a large number of symbols are predefined here.
|
|
// These symbols include the primitive type and method names, as well
|
|
// as fixed names used by the runtime system.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
static Symbol
|
|
arg,
|
|
arg2,
|
|
Bool,
|
|
concat,
|
|
cool_abort,
|
|
copy,
|
|
Int,
|
|
in_int,
|
|
in_string,
|
|
IO,
|
|
length,
|
|
Main,
|
|
main_meth,
|
|
No_class,
|
|
No_type,
|
|
Object,
|
|
out_int,
|
|
out_string,
|
|
prim_slot,
|
|
self,
|
|
SELF_TYPE,
|
|
Str,
|
|
str_field,
|
|
substr,
|
|
type_name,
|
|
val;
|
|
//
|
|
// Initializing the predefined symbols.
|
|
//
|
|
static void initialize_constants(void)
|
|
{
|
|
arg = idtable.add_string("arg");
|
|
arg2 = idtable.add_string("arg2");
|
|
Bool = idtable.add_string("Bool");
|
|
concat = idtable.add_string("concat");
|
|
cool_abort = idtable.add_string("abort");
|
|
copy = idtable.add_string("copy");
|
|
Int = idtable.add_string("Int");
|
|
in_int = idtable.add_string("in_int");
|
|
in_string = idtable.add_string("in_string");
|
|
IO = idtable.add_string("IO");
|
|
length = idtable.add_string("length");
|
|
Main = idtable.add_string("Main");
|
|
main_meth = idtable.add_string("main");
|
|
// _no_class is a symbol that can't be the name of any
|
|
// user-defined class.
|
|
No_class = idtable.add_string("_no_class");
|
|
No_type = idtable.add_string("_no_type");
|
|
Object = idtable.add_string("Object");
|
|
out_int = idtable.add_string("out_int");
|
|
out_string = idtable.add_string("out_string");
|
|
prim_slot = idtable.add_string("_prim_slot");
|
|
self = idtable.add_string("self");
|
|
SELF_TYPE = idtable.add_string("SELF_TYPE");
|
|
Str = idtable.add_string("String");
|
|
str_field = idtable.add_string("_str_field");
|
|
substr = idtable.add_string("substr");
|
|
type_name = idtable.add_string("type_name");
|
|
val = idtable.add_string("_val");
|
|
}
|
|
|
|
|
|
/*
|
|
* In ClassTable's constructor, the inheritance graph is built and checked,
|
|
* with all defined classes recorded for later check.
|
|
* In addition, scan attribute and method definition into global Symbol Table
|
|
*/
|
|
ClassTable::ClassTable(Classes classes) : semant_errors(0) , error_stream(cerr) {
|
|
install_basic_classes();
|
|
/* first scan: de-duplicate class definitions*/
|
|
for (auto i = classes->first(); classes->more(i); i = classes->next(i)) {
|
|
auto class_i = static_cast<class__class*>(classes->nth(i));
|
|
if (name_to_node.find(class_i->get_name()) != name_to_node.end()) {
|
|
semant_error(class_i) << "Class " << class_i->get_name() << " was previously defined.\n";
|
|
}
|
|
else {
|
|
// null means we have this class, but not yet build inheritence graph for it
|
|
name_to_node[class_i->get_name()] = new ClassGraphNode(class_i, nullptr);
|
|
// discard redefined classes by constructing a new vector of classes
|
|
_class_vec.push_back(class_i);
|
|
}
|
|
}
|
|
|
|
/* second scan: check base class inheritable */
|
|
auto sym_Bool = idtable.lookup_string("Bool");
|
|
auto sym_Int = idtable.lookup_string("Int");
|
|
auto sym_String = idtable.lookup_string("String");
|
|
|
|
for (auto class_i : _class_vec) {
|
|
auto sym_parent = class_i->get_parent();
|
|
// Cool has restrictions on inheriting from the basic classes
|
|
if ( sym_parent == sym_Bool || sym_parent == sym_Int || sym_parent == sym_String) {
|
|
semant_error(class_i) << "Class " << class_i->get_name()
|
|
<< " cannot inherit class " << sym_parent << ".\n";
|
|
}
|
|
else if (name_to_node.find(sym_parent) == name_to_node.end()) {
|
|
semant_error(class_i) << "Class `" << class_i->get_name() << "` inherits from an undefined class `"
|
|
<< sym_parent << "`\n";
|
|
}
|
|
else {
|
|
auto class_parent = name_to_node[sym_parent];
|
|
// assert(class_parent != nullptr);
|
|
class_parent->append_child(name_to_node[class_i->get_name()]);
|
|
}
|
|
}
|
|
|
|
if (semant_errors) return;
|
|
// we abort here before check cyclic inheritence if error once occurred
|
|
|
|
// In COOL's case, every class could have only one base class
|
|
// One simple judgement is that, if it cannot go up to object, then the class or its ancestor involves in a cycle
|
|
// Thus, we can start from object and mark all reachable nodes, error report those unreachable nodes
|
|
|
|
/* third scan: check cyclic inheritance */
|
|
class_root->traverse_reachable();
|
|
for (auto i : name_to_node) {
|
|
if (!i.second->reachable) {
|
|
semant_error(i.second->get_class()) << "Class `" << i.first
|
|
<< "` or its ancestor, is involved in an inheritance cycle.\n";
|
|
}
|
|
}
|
|
if (semant_errors) return;
|
|
if (semant_debug) {
|
|
std::cerr<< "Class Inheritance Analysis done.\n";
|
|
class_root->traverse_dump_name(std::cout, 0);
|
|
}
|
|
}
|
|
|
|
void ClassTable::symtab_dump(Symbol class_name) {
|
|
std::cerr << "SymTab of " << class_name << "\n";
|
|
std::cerr << "M(" << class_name << ") =";
|
|
symtab_met[class_name]->dump();
|
|
std::cerr << "O(" << class_name << ") =";
|
|
symtab_obj[class_name]->dump();
|
|
std::cerr << "--------\n\n";
|
|
}
|
|
|
|
Symbol ClassTable::symtab_object_lookup_parent(Symbol class_name, Symbol object_name) {
|
|
assert(name_to_node.find(class_name) != name_to_node.end());
|
|
auto class_node = name_to_node[class_name];
|
|
auto parent_node= class_node->parent;
|
|
while (parent_node) {
|
|
auto result = symtab_obj[parent_node->get_class()->get_name()]->probe(object_name);
|
|
// only need to lookup in the top scope, because attributes all reside in top scope
|
|
if (result) return result;
|
|
parent_node = parent_node->parent;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
method_class* ClassTable::symtab_method_lookup_parent(Symbol class_name, Symbol method_name) {
|
|
assert(name_to_node.find(class_name) != name_to_node.end());
|
|
auto class_node = name_to_node[class_name];
|
|
auto parent_node= class_node->parent;
|
|
while (parent_node) {
|
|
auto result = symtab_met[parent_node->get_class()->get_name()]->probe(method_name);
|
|
// only need to lookup in the top scope, because attributes all reside in top scope
|
|
if (result) return result;
|
|
parent_node = parent_node->parent;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void ClassTable::install_all_features() {
|
|
/* fourth scan: gather attr and method definition */
|
|
auto sym_Main = idtable.lookup_string("Main");
|
|
auto sym_main = idtable.lookup_string("main");
|
|
class__class* class_Main = nullptr;
|
|
method_class* method_main = nullptr; // main in class Main
|
|
std::queue<ClassGraphNode*> _iter_queue;
|
|
_iter_queue.push(class_root);
|
|
while (!_iter_queue.empty()) {
|
|
auto cur_node = _iter_queue.front();
|
|
_iter_queue.pop();
|
|
// std::cerr << "Pop " << cur_node->get_class()->get_name() << "\n";
|
|
|
|
for (auto child : cur_node->children) {
|
|
_iter_queue.push(child);
|
|
// std::cerr << "Push " << child->get_class()->get_name() << "\n";
|
|
}
|
|
auto cur_class = cur_node->get_class();
|
|
auto cur_name = cur_class->get_name();
|
|
if (cur_name == sym_Main) {
|
|
class_Main = cur_class;
|
|
}
|
|
symtab_met[cur_name] = new SymbolTable<Symbol, method_class>;
|
|
symtab_obj[cur_name] = new SymbolTable<Symbol, Entry>;
|
|
symtab_met[cur_name]->enterscope();
|
|
symtab_obj[cur_name]->enterscope();
|
|
|
|
auto cur_features = cur_class->get_features();
|
|
for (auto j = cur_features->first(); cur_features->more(j); j = cur_features->next(j)) {
|
|
auto cur_feature = cur_features->nth(j);
|
|
// std::cerr << "feature " << j << " ";
|
|
if (typeid(*cur_feature) == typeid(attr_class)) {
|
|
auto cur_attr = static_cast<attr_class*>(cur_feature);
|
|
// std::cerr << "attr " << cur_attr->get_name() << "\n";
|
|
if (symtab_obj[cur_name]->lookup(cur_attr->get_name()) != nullptr) {
|
|
semant_error(cur_class->get_filename(), cur_attr) << "Attribute " << cur_attr->get_name()
|
|
<< " is multiply defined.\n";
|
|
}
|
|
else if (symtab_object_lookup_parent(cur_name, cur_attr->get_name()) != nullptr) {
|
|
// check duplicate name for attributes
|
|
semant_error(cur_class->get_filename(), cur_attr) << "Attribute " << cur_attr->get_name()
|
|
<< " is an attribute of an inherited class.\n";
|
|
}
|
|
else {
|
|
symtab_obj[cur_name]->addid(cur_attr->get_name(), cur_attr->get_type_decl());
|
|
}
|
|
}
|
|
else if (typeid(*cur_feature) == typeid(method_class)) {
|
|
auto cur_method = static_cast<method_class*>(cur_feature);
|
|
// std::cerr << "method " << cur_method->get_name() << "\n";
|
|
if (symtab_met[cur_name]->lookup(cur_method->get_name()) != nullptr) {
|
|
semant_error(cur_class->get_filename(), cur_method) << "Method " << cur_method->get_name()
|
|
<< " is multiply defined.\n";
|
|
}
|
|
else {
|
|
auto overridden_method = symtab_method_lookup_parent(cur_name, cur_method->get_name());
|
|
auto _error_flag = false;
|
|
if (overridden_method != nullptr) {
|
|
// additional check is necessary for overridden methods
|
|
if (semant_debug) std::cerr << "Overriden method" << cur_method->get_name() << "\n";
|
|
if (cur_method->get_return_type() != overridden_method->get_return_type()) {
|
|
// direct comaprison between Symbols is okay, 'cause Symbols(ptr to Entry) are distinct
|
|
// when talking about classes or types
|
|
semant_error(cur_class->get_filename(), cur_method)
|
|
<< "In redefined method " << cur_method->get_name()
|
|
<< ", return type " << cur_method->get_return_type()
|
|
<< " is different from original return type" << overridden_method->get_return_type()
|
|
<< "\n";
|
|
_error_flag = true;
|
|
}
|
|
else if (cur_method->get_formals()->len() != overridden_method->get_formals()->len()) {
|
|
semant_error(cur_class->get_filename(), cur_method)
|
|
<< "Incompatible number of formal parameters in redefined method "
|
|
<< cur_method->get_name()
|
|
<< ".\n";
|
|
_error_flag = true;
|
|
}
|
|
else {
|
|
// we cannot directly compare 2 formal lists
|
|
auto cur_formals = cur_method->get_formals();
|
|
auto overridden_formals = overridden_method->get_formals();
|
|
auto cur_formal_i = cur_formals->first();
|
|
auto overridden_formal_i = overridden_formals->first();
|
|
// by last check, we get ensured that their formal list should have the same length
|
|
while (cur_formals->more(cur_formal_i)) {
|
|
auto cur_formal = static_cast<formal_class*>(cur_formals->nth(cur_formal_i));
|
|
auto overridden_formal = static_cast<formal_class*>(overridden_formals->nth(cur_formal_i));
|
|
if (cur_formal->get_type_decl() != overridden_formal->get_type_decl()) {
|
|
semant_error(cur_class->get_filename(), cur_method)
|
|
<< "In redefined method " << cur_method->get_name()
|
|
<< ", parameter type " << cur_formal->get_type_decl()
|
|
<< " is different from original type" << overridden_formal->get_type_decl()
|
|
<< "\n";
|
|
_error_flag = true;
|
|
break;
|
|
}
|
|
cur_formal_i = cur_formals->next(cur_formal_i);
|
|
overridden_formal_i = overridden_formals->next(overridden_formal_i);
|
|
}
|
|
}
|
|
}
|
|
if (!_error_flag) {
|
|
// std::cerr << "method done " << cur_method->get_name() << "\n";
|
|
symtab_met[cur_name]->addid(cur_method->get_name(), cur_method);
|
|
if (class_Main != nullptr && cur_name == sym_Main && cur_method->get_name() == sym_main) {
|
|
// main in class Main
|
|
method_main = cur_method;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else assert(0);
|
|
}
|
|
if (semant_debug) {
|
|
symtab_dump(cur_name);
|
|
}
|
|
}
|
|
|
|
if (!class_Main) {
|
|
semant_error() << "Class Main is not defined.\n";
|
|
return;
|
|
}
|
|
else if (!method_main) {
|
|
semant_error(class_Main) << "No 'main' method in class Main.\n";
|
|
return;
|
|
}
|
|
else
|
|
if (semant_debug) {
|
|
std::cerr << "Attrs & Methods Collection done.\n";
|
|
}
|
|
}
|
|
|
|
void ClassTable::install_basic_classes() {
|
|
|
|
// The tree package uses these globals to annotate the classes built below.
|
|
// curr_lineno = 0;
|
|
Symbol filename = stringtable.add_string("<basic class>");
|
|
|
|
// The following demonstrates how to create dummy parse trees to
|
|
// refer to basic Cool classes. There's no need for method
|
|
// bodies -- these are already built into the runtime system.
|
|
|
|
// IMPORTANT: The results of the following expressions are
|
|
// stored in local variables. You will want to do something
|
|
// with those variables at the end of this method to make this
|
|
// code meaningful.
|
|
|
|
//
|
|
// The Object class has no parent class. Its methods are
|
|
// abort() : Object aborts the program
|
|
// type_name() : Str returns a string representation of class name
|
|
// copy() : SELF_TYPE returns a copy of the object
|
|
//
|
|
// There is no need for method bodies in the basic classes---these
|
|
// are already built in to the runtime system.
|
|
|
|
Class_ Object_class =
|
|
class_(Object,
|
|
No_class,
|
|
append_Features(
|
|
append_Features(
|
|
single_Features(method(cool_abort, nil_Formals(), Object, no_expr())),
|
|
single_Features(method(type_name, nil_Formals(), Str, no_expr()))),
|
|
single_Features(method(copy, nil_Formals(), SELF_TYPE, no_expr()))),
|
|
filename);
|
|
|
|
//
|
|
// The IO class inherits from Object. Its methods are
|
|
// out_string(Str) : SELF_TYPE writes a string to the output
|
|
// out_int(Int) : SELF_TYPE " an int " " "
|
|
// in_string() : Str reads a string from the input
|
|
// in_int() : Int " an int " " "
|
|
//
|
|
Class_ IO_class =
|
|
class_(IO,
|
|
Object,
|
|
append_Features(
|
|
append_Features(
|
|
append_Features(
|
|
single_Features(method(out_string, single_Formals(formal(arg, Str)),
|
|
SELF_TYPE, no_expr())),
|
|
single_Features(method(out_int, single_Formals(formal(arg, Int)),
|
|
SELF_TYPE, no_expr()))),
|
|
single_Features(method(in_string, nil_Formals(), Str, no_expr()))),
|
|
single_Features(method(in_int, nil_Formals(), Int, no_expr()))),
|
|
filename);
|
|
|
|
//
|
|
// The Int class has no methods and only a single attribute, the
|
|
// "val" for the integer.
|
|
//
|
|
Class_ Int_class =
|
|
class_(Int,
|
|
Object,
|
|
single_Features(attr(val, prim_slot, no_expr())),
|
|
filename);
|
|
|
|
//
|
|
// Bool also has only the "val" slot.
|
|
//
|
|
Class_ Bool_class =
|
|
class_(Bool, Object, single_Features(attr(val, prim_slot, no_expr())),filename);
|
|
|
|
//
|
|
// The class Str has a number of slots and operations:
|
|
// val the length of the string
|
|
// str_field the string itself
|
|
// length() : Int returns length of the string
|
|
// concat(arg: Str) : Str performs string concatenation
|
|
// substr(arg: Int, arg2: Int): Str substring selection
|
|
//
|
|
Class_ Str_class =
|
|
class_(Str,
|
|
Object,
|
|
append_Features(
|
|
append_Features(
|
|
append_Features(
|
|
append_Features(
|
|
single_Features(attr(val, Int, no_expr())),
|
|
single_Features(attr(str_field, prim_slot, no_expr()))),
|
|
single_Features(method(length, nil_Formals(), Int, no_expr()))),
|
|
single_Features(method(concat,
|
|
single_Formals(formal(arg, Str)),
|
|
Str,
|
|
no_expr()))),
|
|
single_Features(method(substr,
|
|
append_Formals(single_Formals(formal(arg, Int)),
|
|
single_Formals(formal(arg2, Int))),
|
|
Str,
|
|
no_expr()))),
|
|
filename);
|
|
auto object_class = static_cast<class__class*>(Object_class);
|
|
auto io_class = static_cast<class__class*>(IO_class);
|
|
auto bool_class = static_cast<class__class*>(Bool_class);
|
|
auto int_class = static_cast<class__class*>(Int_class);
|
|
auto str_class = static_cast<class__class*>(Str_class);
|
|
|
|
class_root = new ClassGraphNode(Object_class, nullptr);
|
|
name_to_node[object_class->get_name()] = class_root;
|
|
name_to_node[io_class->get_name()] = class_root->new_child(IO_class);
|
|
name_to_node[int_class->get_name()] = class_root->new_child(Int_class);
|
|
name_to_node[bool_class->get_name()] = class_root->new_child(Bool_class);
|
|
name_to_node[str_class->get_name()] = class_root->new_child(Str_class);
|
|
|
|
if (semant_debug) {
|
|
std::cout << "Basic classed installed\n";
|
|
class_root->traverse_dump_name(std::cout, 0);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// semant_error is an overloaded function for reporting errors
|
|
// during semantic analysis. There are three versions:
|
|
//
|
|
// ostream& ClassTable::semant_error()
|
|
//
|
|
// ostream& ClassTable::semant_error(Class_ c)
|
|
// print line number and filename for `c'
|
|
//
|
|
// ostream& ClassTable::semant_error(Symbol filename, tree_node *t)
|
|
// print a line number and filename
|
|
//
|
|
///////////////////////////////////////////////////////////////////
|
|
|
|
ostream& ClassTable::semant_error(Class_ c)
|
|
{
|
|
return semant_error(c->get_filename(),c);
|
|
}
|
|
|
|
ostream& ClassTable::semant_error(Symbol filename, tree_node *t)
|
|
{
|
|
error_stream << filename << ":" << t->get_line_number() << ": ";
|
|
return semant_error();
|
|
}
|
|
|
|
ostream& ClassTable::semant_error()
|
|
{
|
|
semant_errors++;
|
|
return error_stream;
|
|
}
|
|
|
|
|
|
|
|
/* This is the entry point to the semantic checker.
|
|
|
|
Your checker should do the following two things:
|
|
|
|
1) Check that the program is semantically correct
|
|
2) Decorate the abstract syntax tree with type information
|
|
by setting the `type' field in each Expression node.
|
|
(see `tree.h')
|
|
|
|
You are free to first do 1), make sure you catch all semantic
|
|
errors. Part 2) can be done in a second stage, when you want
|
|
to build mycoolc.
|
|
*/
|
|
void program_class::semant()
|
|
{
|
|
initialize_constants();
|
|
|
|
/* ClassTable constructor may do some semantic analysis */
|
|
ClassTable *classtable = new ClassTable(classes);
|
|
|
|
/* Without confidence in inheritance consistency, abort before type checking*/
|
|
if (classtable->errors()) {
|
|
cerr << "Compilation halted due to static semantic errors." << endl;
|
|
exit(1);
|
|
}
|
|
/* Gather declared types of features for later use */
|
|
classtable->install_all_features();
|
|
/* Top down type checking */
|
|
for (auto class_i : classtable->class_vec()) {
|
|
class_i->semant();
|
|
}
|
|
|
|
if (classtable->errors()) {
|
|
cerr << "Compilation halted due to static semantic errors." << endl;
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
void class__class::semant()
|
|
{
|
|
|
|
}
|