#include #include #include #include #include #include #include #include #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(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 _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; symtab_obj[cur_name] = new SymbolTable; 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(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(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(cur_formals->nth(cur_formal_i)); auto overridden_formal = static_cast(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(""); // 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(Object_class); auto io_class = static_cast(IO_class); auto bool_class = static_cast(Bool_class); auto int_class = static_cast(Int_class); auto str_class = static_cast(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() { }