CS143-Lab/assignments/PA4/semant.cc
2023-03-27 19:12:54 +08:00

1307 lines
48 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <cassert>
#include <iostream>
#include <set>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <vector>
#include <queue>
#include <memory.h>
#include "cool-tree.h"
#include "cool-tree.handcode.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 (class_i->get_name() == SELF_TYPE) {
semant_error(class_i) << "Redefinition of basic class SELF_TYPE.\n";
}
else 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 */
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 == Bool || sym_parent == Int || sym_parent == Str) {
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(0);
for (auto i : name_to_node) {
if (i.second->depth == 0) {
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(Class_ class_i, Symbol object_name) const {
return symtab_object_lookup_parent(class_to_name(class_i), object_name);
}
Symbol ClassTable::symtab_object_lookup_parent(Symbol class_name, Symbol object_name) const {
assert(name_to_node.find(class_name) != name_to_node.end());
auto class_node = name_to_node.find(class_name)->second;
auto parent_node= class_node;
while (parent_node) {
auto result = _symtab_obj.find(parent_node->get_class()->get_name())->second->lookup(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(Class_ class_i, Symbol method_name) const {
return symtab_method_lookup_parent(class_to_name(class_i), method_name);
}
method_class* ClassTable::symtab_method_lookup_parent(Symbol class_name, Symbol method_name) const {
assert(name_to_node.find(class_name) != name_to_node.end());
auto class_node = name_to_node.find(class_name)->second;
auto parent_node= class_node;
while (parent_node) {
auto result = _symtab_met.find(parent_node->get_class()->get_name())->second->lookup(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 */
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 == 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();
_symtab_obj[cur_name]->addid(self, SELF_TYPE);
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 (cur_attr->get_name() == self) {
semant_error(cur_class->get_filename(), cur_attr)
<< "\'self\' cannot be the name of an attribute.\n";
}
else 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 == Main && cur_method->get_name() == main_meth) {
// 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;
}
/*
* Query the Inheritance Graph if T1 <= T0
*
* Note: if T1 inherits from(or is) T0, then T1 <= T0
*
* Warning: if the type doesn't exist, by default return `TRUE` to stop
* error cascading
*/
bool ClassTable::conform(Symbol T1, Symbol T0) const {
if (T1 == T0) return true;
if (!class_defined(T0) || !class_defined(T1))
return true;
auto cur_node_itr = name_to_node.find(T1);
if (cur_node_itr == name_to_node.end()){
std::cerr << "Comform: Escaping type T1 = " << T1 << "\n";
assert(cur_node_itr != name_to_node.end());
}
auto cur_node = cur_node_itr->second;
while (cur_node) {
if (cur_node->get_class()->get_name() == T0) {
return true;
}
cur_node = cur_node->parent;
}
return false;
}
/*
* conformance check with SELF_TYPE support
*/
bool ClassTable::conform_self(Symbol T1, Symbol T0, Symbol env_class) const {
if (T1 == T0) return true;
/*
* Add a special rule for SELF_TYPE: SELF_TYPE_c <= T0 if C <= T0
*/
if (T1 == SELF_TYPE) {
return conform(env_class, T0);
}
if (T0 == SELF_TYPE) {
return false;
}
else {
return conform(T1, T0);
}
}
/*
* conformance check with SELF_TYPE support
*/
bool ClassTable::conform_self(Symbol T1, Symbol T0, Class_ env_class) const {
return conform_self(T1, T0, class_to_name(env_class));
}
/*
* Check whether a class is defined (not including SELF_TYPE)
*/
bool ClassTable::class_defined(Symbol class_id) const {
return name_to_node.find(class_id) != name_to_node.end();
}
/*
* Check Class existence allowing `SELF_TYPE` to be a valid type name
* `SELF_TYPE` may be used in the following places: No other uses are permitted.
* - new SELF TYPE
* - return type of a method,
* - declared type of a let variable
* - declared type of an attribute
*/
bool ClassTable::class_defined_self(Symbol class_id) const {
if (class_id == SELF_TYPE) return true;
else return class_defined(class_id);
}
/*
* Get the `join` of two types, aka Lowest Upper Bound
* Actually it is the LCA of the two nodes on inheritence tree
*
* Warning:
* If either of the inputs is undefined, the operation returns Object
*/
Symbol ClassTable::lub(Symbol T1, Symbol T2) const {
if (T1 == T2) return T1;
auto node1_iter = name_to_node.find(T1);
auto node2_iter = name_to_node.find(T2);
/* Return Object if one of the argument is undefined for recovery */
if (node1_iter == name_to_node.end() || node2_iter == name_to_node.end()) {
return Object;
}
auto node1 = node1_iter->second;
auto node2 = node2_iter->second;
ClassGraphNode* node_lower = nullptr;
ClassGraphNode* node_upper = nullptr;
/* select the lower (further away to root) node */
if (node1->depth < node2->depth) {
node_lower = node2;
node_upper = node1;
node2 = node1 = nullptr;
}
else {
node_lower = node1;
node_upper = node2;
node2 = node1 = nullptr;
}
/* first let the lower node jump up to the same depth with the other node */
while (node_lower->depth > node_upper->depth) {
node_lower = node_lower->parent;
assert(node_lower != nullptr);
}
/* then jump together until they meet */
while (node_lower != node_upper) {
node_lower = node_lower->parent;
node_upper = node_upper->parent;
assert(node_lower != nullptr);
assert(node_upper != nullptr);
}
return node_lower->get_class()->get_name();
}
/*
* lub operation with SELF_TYPE support
*/
Symbol ClassTable::lub(Symbol T1, Symbol T2, Symbol env_class) const {
if (T1 == SELF_TYPE) T1 = env_class;
if (T2 == SELF_TYPE) T2 = env_class;
return lub(T1, T2);
}
/*
* Wrapper for the Object Symbol Table
*/
SymbolTable<Symbol, Entry>* ClassTable::symtab_obj(Class_ class_i) const {
auto class_id = static_cast<class__class*>(class_i)->get_name();
auto _iter = _symtab_obj.find(class_id);
if (_iter == _symtab_obj.end()) return nullptr;
return _iter->second;
};
/* 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(classtable);
}
if (classtable->errors()) {
cerr << "Compilation halted due to static semantic errors." << endl;
exit(1);
}
}
void class__class::semant(ClassTable * classtable)
{
for (auto i = features->first(); features->more(i); i = features->next(i)) {
auto cur_feature = features->nth(i);
cur_feature->semant(this, classtable);
}
}
void attr_class::semant(Class_ cur_class, ClassTable * classtable)
{
/*
* Attr-No-Init:
* Check if Declared Type is defined, if so, do nothing because already
* in AST; else, error report and set its type to Object
*/
if (!classtable->class_defined_self(type_decl)) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Class " << type_decl
<< " of attribute " << name
<< " is undefined.\n";
}
/*
* Attr-Init:
* Except checking existence of type_decl, type the init expression and
* check rhs's type conformance with type_decl
*/
if (typeid(*init) != typeid(no_expr_class)) {
auto rhs_type = init->semant(cur_class, classtable);
// std::cerr << "attr init rhs type=" << rhs_type << "\n";
if (!classtable->conform_self(rhs_type, type_decl, cur_class)) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Inferred type " << rhs_type
<< " of initialization of attribute " << name
<< " does not conform to declared type " << type_decl
<< ".\n";
this->type_decl = Object;
}
}
}
/*
* Method:
* Check formal types and and them to scope
* Checks the body of the method in an env in which formals and `self` bounded
* Check body type conforms to the declared return type
*/
void method_class::semant(Class_ cur_class, ClassTable * classtable)
{
auto symtab_obj = classtable->symtab_obj(cur_class);
symtab_obj->enterscope();
for (auto i = formals->first(); formals->more(i); i = formals->next(i)) {
auto formal_i = static_cast<formal_class*>(formals->nth(i));
auto formal_i_name = formal_i->get_name();
auto formal_i_type = formal_i->get_type_decl();
if (formal_i_name == self) {
classtable->semant_error(cur_class->get_filename(), formal_i)
<< "\'self\' cannot be the name of a formal parameter.\n";
}
else if (symtab_obj->probe(formal_i_name) == nullptr) {
symtab_obj->addid(formal_i_name, formal_i_type);
}
else {
classtable->semant_error(cur_class->get_filename(), formal_i)
<< "Formal parameter " << formal_i_name
<< " is multiply defined.\n";
}
if (!classtable->class_defined(formal_i_type)) {
classtable->semant_error(cur_class->get_filename(), formal_i)
<< "Class " << formal_i_type
<< " of formal parameter " << formal_i_name
<< " is undefined.\n";
}
}
auto expr_type = this->expr->semant(cur_class, classtable);
if (!classtable->class_defined_self(this->return_type)) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Undefined return type " << this->return_type
<< " in method " << this->name
<< ".\n";
}
symtab_obj->exitscope();
if (!classtable->conform_self(expr_type, this->return_type, cur_class)) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Inferred return type " << expr_type
<< " of method " << this->name
<< " does not conform to declared return type " << this->return_type
<< ".\n";
}
}
/*
* ASSIGN:
* Given RHS:T' and Id:T, T is the declared type of Id in the env,
* there is T' <= T. Note that the type of the whole expression is T
*/
Symbol assign_class::semant(Class_ cur_class, ClassTable* classtable)
{
if (this->name == self) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Cannot assign to 'self'.\n";
}
auto lhs_type = classtable->symtab_object_lookup_parent(cur_class, this->name);
if (lhs_type == nullptr) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Assignment to undeclared variable " << this->name << ".\n";
lhs_type = Object;
}
auto rhs_type = this->expr->semant(cur_class, classtable);
if (!classtable->conform_self(rhs_type, lhs_type, cur_class)) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Type " << rhs_type
<< " of assigned expression does not conform to declared type " << lhs_type
<< " of identifier " << this->name
<< ".\n";
}
this->type = rhs_type;
return this->type;
}
Symbol static_dispatch_class::semant(Class_ cur_class, ClassTable* classtable)
{
auto expr_type = this->expr->semant(cur_class, classtable);
std::vector<Symbol> _param_type;
for (auto i = this->actual->first(); this->actual->more(i); i = this->actual->next(i)) {
auto param_i = this->actual->nth(i);
auto param_i_type = param_i->semant(cur_class, classtable);
_param_type.push_back(param_i_type);
}
if (!classtable->class_defined(this->type_name)) {
if (this->type_name == SELF_TYPE){
classtable->semant_error(cur_class->get_filename(), this)
<< "Static dispatch to SELF_TYPE.\n";
}
else {
classtable->semant_error(cur_class->get_filename(), this)
<< "Static dispatch on undefined class " << this->type_name
<< ".\n";
}
return (this->type = Object);
}
if (!classtable->conform_self(expr_type, this->type_name, cur_class)) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Expression type " << expr_type
<< " does not conform to declared static dispatch type " << this->type_name
<< ".\n";
return (this->type = Object);
}
auto cur_method = classtable->symtab_method_lookup_parent(this->type_name, this->name);
if (cur_method == nullptr) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Static dispatch to undefined method " << this->name
<< ".\n";
return (this->type = Object);
}
if (actual->len() != cur_method->get_formals()->len()) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Method " << this->name
<< " called with wrong number of arguments.\n";
return (this->type = Object);
}
auto formals = cur_method->get_formals();
auto _param_type_iter = _param_type.begin();
for (auto i = formals->first(); formals->more(i); i = formals->next(i)) {
auto formal_i = static_cast<formal_class*>(formals->nth(i));
if (!classtable->conform_self(*_param_type_iter, formal_i->get_type_decl(), cur_class)) {
classtable->semant_error(cur_class->get_filename(), this)
<< "In call of method " << cur_method->get_name()
<< ", type " << *_param_type_iter
<< " of parameter " << formal_i->get_name()
<< " does not conform to declared type " << formal_i->get_type_decl()
<< ".\n";
}
++_param_type_iter;
}
auto return_type = cur_method->get_return_type();
if (return_type == SELF_TYPE) {
return_type = expr_type;
// if expr_type (T_0) is SELF_TYPE, then dispath type just keep it
}
return (this->type = return_type);
}
/*
* Dispatch:
* Object type check
* Each of the param exprs get typed
* Check conformance of each param-formal pair
*/
Symbol dispatch_class::semant(Class_ cur_class, ClassTable* classtable)
{
auto expr_type = this->expr->semant(cur_class, classtable); // T_0
std::vector<Symbol> _param_type; // T_i
for (auto i = this->actual->first(); this->actual->more(i); i = this->actual->next(i)) {
auto param_i = this->actual->nth(i);
auto param_i_type = param_i->semant(cur_class, classtable);
_param_type.push_back(param_i_type);
}
Symbol method_env_class = expr_type; // T_0'
if (expr_type == SELF_TYPE) {
method_env_class = classtable->class_to_name(cur_class);
}
if (!classtable->class_defined(method_env_class)) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Dispatch on undefined class " << method_env_class
<< ".\n";
this->type = Object;
return this->type;
}
auto cur_method = classtable->symtab_method_lookup_parent(method_env_class, this->name);
if (cur_method == nullptr) {
if (semant_debug) {
std::cerr << "lookup method " << this->name << " in class " << method_env_class << "\n";
}
classtable->semant_error(cur_class->get_filename(), this)
<< "Dispatch to undefined method " << this->name
<< ".\n";
this->type = Object;
return Object;
}
if (actual->len() != cur_method->get_formals()->len()) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Method " << this->name
<< " called with wrong number of arguments.\n";
this->type = Object;
return Object;
}
auto formals = cur_method->get_formals(); // T_i'
auto _param_type_iter = _param_type.begin();
for (auto i = formals->first();
formals->more(i);
i = formals->next(i), ++_param_type_iter)
{
auto formal_i = static_cast<formal_class*>(formals->nth(i));
auto formal_i_type = formal_i->get_type_decl();
if (!classtable->conform_self(*_param_type_iter, formal_i_type, cur_class)) {
classtable->semant_error(cur_class->get_filename(), this)
<< "In call of method " << cur_method->get_name()
<< ", type " << *_param_type_iter
<< " of parameter " << formal_i->get_name()
<< " does not conform to declared type " << formal_i->get_type_decl()
<< ".\n";
}
}
auto return_type = cur_method->get_return_type(); // T_n+1
if (return_type == SELF_TYPE) {
return_type = expr_type;
}
return (this->type = return_type);
}
/*
* If:
* Predicate must be Bool type
* Branches may have any static types
* Type of the whole expr is `lub(type(e1), type(e2))`
*/
Symbol cond_class::semant(Class_ cur_class, ClassTable* classtable)
{
auto pred_type = this->pred->semant(cur_class, classtable);
if (pred_type != Bool) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Predicate of 'if' does not have type Bool.\n";
}
auto else_type = this->else_exp->semant(cur_class, classtable);
auto then_type = this->then_exp->semant(cur_class, classtable);
/* TODO: It seems unnecessary to check existence of else/then_type */
this->type = classtable->lub(else_type, then_type);
return this->type;
}
/*
* Loop:
* Predicate must be Bool
* Type of the entire loop is always Object
* Not care about loop body type, though still typing it
*/
Symbol loop_class::semant(Class_ cur_class, ClassTable* classtable)
{
auto pred_type = this->pred->semant(cur_class, classtable);
if (pred_type != Bool) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Loop condition does not have type Bool.\n";
}
auto _ = this->body->semant(cur_class, classtable);
return (this->type = Object);
}
/*
* Case:
* For each branch, add xi:Ti to the env. Meanwhile they must have distinct types
* Type of the entire case is the join of its branches' types
* Note that SELF_TYPE is not allow here
*/
Symbol typcase_class::semant(Class_ cur_class, ClassTable* classtable)
{
auto _ = this->expr->semant(cur_class, classtable);
std::set<Symbol> _existed_type;
Symbol entire_type = nullptr;
for (auto i = this->cases->first(); this->cases->more(i); i = this->cases->next(i)) {
auto branch_i = static_cast<branch_class*>(this->cases->nth(i));
auto type_decl_i = branch_i->get_type_decl();
auto name_i = branch_i->get_name();
// first check duplicated type
if (_existed_type.count(type_decl_i) == 0) {
_existed_type.insert(type_decl_i);
}
else {
classtable->semant_error(cur_class->get_filename(), branch_i)
<< "Duplicate branch " << type_decl_i
<< " in case statement.\n";
}
/*then check existence of declared type
The reference semant program's behavior is quite strange here,
if the type does not exist, it continue to use the undefined type
for later expr typing
*/
if (name_i == self) {
classtable->semant_error(cur_class->get_filename(), branch_i)
<< "\'self\' bound in 'case'.\n";
}
if (type_decl_i == SELF_TYPE) {
classtable->semant_error(cur_class->get_filename(), branch_i)
<< "Identifier " << name_i
<< " declared with type SELF_TYPE in case branch.\n";
}
else if (!classtable->class_defined(type_decl_i)) {
classtable->semant_error(cur_class->get_filename(), branch_i)
<< "Class " << type_decl_i
<< " of case branch is undefined.\n";
}
/* next add x[i]:T[i] to scope and type the rhs expr */
classtable->symtab_obj(cur_class)->enterscope();
classtable->symtab_obj(cur_class)->addid(name_i, type_decl_i);
auto branch_expr_type = branch_i->get_expr()->semant(cur_class, classtable);
classtable->symtab_obj(cur_class)->exitscope();
/* finally, remember to join expr types for the entire case's type*/
if (entire_type == nullptr) {
entire_type = branch_expr_type;
}
else {
entire_type = classtable->lub(entire_type, branch_expr_type);
}
}
return (this->type = entire_type);
}
/*
* Sequence:
* Iterate over the expr_list, type each expr and use the type of the last expr
*/
Symbol block_class::semant(Class_ cur_class, ClassTable* classtable)
{
Symbol type_i;
for (auto i = this->body->first(); this->body->more(i); i = this->body->next(i)) {
type_i = this->body->nth(i)->semant(cur_class, classtable);
}
return (this->type = type_i);
}
/*
* Let:
* Since `let` exprs are nested in AST, we only need to handle only 1 identifier
* Check existence of declared type
* If there is an initializer, type it and check conformance with declared type
* Add the identifier to the scope and type the rhs expr
* Set the `let` expr's type to the type of rhs
*/
Symbol let_class::semant(Class_ cur_class, ClassTable* classtable)
{
if (this->identifier == self) {
classtable->semant_error(cur_class->get_filename(), this)
<< "\'self\' cannot be bound in a \'let\' expression.\n";
}
if (!classtable->class_defined_self(this->type_decl)) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Class " << this->type_decl
<< " of let-bound identifier " << this->identifier
<< " is undefined.\n";
}
if (typeid(*this->init) != typeid(no_expr_class)) {
auto init_type = this->init->semant(cur_class, classtable);
if (!classtable->conform_self(init_type, this->type_decl, cur_class)) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Inferred type " << init_type
<< " of initialization of " << this->identifier
<< " does not conform to identifier's declared type " << this->type_decl
<< ".\n";
}
}
classtable->symtab_obj(cur_class)->enterscope();
classtable->symtab_obj(cur_class)->addid(this->identifier, this->type_decl);
auto expr_type = this->body->semant(cur_class, classtable);
classtable->symtab_obj(cur_class)->exitscope();
return (this->type = expr_type);
}
/*
* Arith(+):
* Check type(e1) == Int == type(e2), and whatever the result, derive to Int
*/
Symbol plus_class::semant(Class_ cur_class, ClassTable* classtable)
{
auto e1_type = this->e1->semant(cur_class, classtable);
auto e2_type = this->e2->semant(cur_class, classtable);
if (e1_type != Int || e2_type != Int) {
classtable->semant_error(cur_class->get_filename(), this)
<< "non-Int arguments: " << e1_type
<< " + " << e2_type
<< "\n";
}
return (this->type = Int);
}
/*
* Arith(-):
* Check type(e1) == Int == type(e2), and whatever the result, derive to Int
*/
Symbol sub_class::semant(Class_ cur_class, ClassTable* classtable)
{
auto e1_type = this->e1->semant(cur_class, classtable);
auto e2_type = this->e2->semant(cur_class, classtable);
if (e1_type != Int || e2_type != Int) {
classtable->semant_error(cur_class->get_filename(), this)
<< "non-Int arguments: " << e1_type
<< " - " << e2_type
<< "\n";
}
return (this->type = Int);
}
/*
* Arith(*):
* Check type(e1) == Int == type(e2), and whatever the result, derive to Int
*/
Symbol mul_class::semant(Class_ cur_class, ClassTable* classtable)
{
auto e1_type = this->e1->semant(cur_class, classtable);
auto e2_type = this->e2->semant(cur_class, classtable);
if (e1_type != Int || e2_type != Int) {
classtable->semant_error(cur_class->get_filename(), this)
<< "non-Int arguments: " << e1_type
<< " * " << e2_type
<< "\n";
}
return (this->type = Int);
}
/*
* Arith(/):
* Check type(e1) == Int == type(e2), and whatever the result, derive to Int
*/
Symbol divide_class::semant(Class_ cur_class, ClassTable* classtable)
{
auto e1_type = this->e1->semant(cur_class, classtable);
auto e2_type = this->e2->semant(cur_class, classtable);
if (e1_type != Int || e2_type != Int) {
classtable->semant_error(cur_class->get_filename(), this)
<< "non-Int arguments: " << e1_type
<< " / " << e2_type
<< "\n";
}
return (this->type = Int);
}
/*
* Neg:
* Check type(e1) == Int, and whatever the result, derive to Int
*/
Symbol neg_class::semant(Class_ cur_class, ClassTable* classtable)
{
auto type_subexpr = this->e1->semant(cur_class, classtable);
if (type_subexpr != Int) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Argument of '~' has type " << type_subexpr
<< " instead of Int.\n";
}
return (this->type = Int);
}
/*
* Compare(<):
* Check type(e1) == Int == type(e2), and whatever the result, derive to Bool
*/
Symbol lt_class::semant(Class_ cur_class, ClassTable* classtable)
{
auto e1_type = this->e1->semant(cur_class, classtable);
auto e2_type = this->e2->semant(cur_class, classtable);
if (e1_type != Int || e2_type != Int) {
classtable->semant_error(cur_class->get_filename(), this)
<< "non-Int arguments: " << e1_type
<< " < " << e2_type
<< "\n";
}
return (this->type = Bool);
}
/*
* Equal:
* Any types may be freely compared (compare their Runtime addresses)
* except for Int, String, Bool. These 3 basic types can only be compared with
* exprs with the same type (compare their content values)
*/
Symbol eq_class::semant(Class_ cur_class, ClassTable* classtable)
{
auto e1_type = this->e1->semant(cur_class, classtable);
auto e2_type = this->e2->semant(cur_class, classtable);
if ((e1_type == Int || e2_type == Int
|| e1_type == Str || e2_type == Str
|| e1_type == Bool || e2_type == Bool)
&& e1_type != e2_type) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Illegal comparison with a basic type.\n";
}
return (this->type = Bool);
}
/*
* Compare(<=):
* Check type(e1) == Int == type(e2), and whatever the result, derive to Bool
*/
Symbol leq_class::semant(Class_ cur_class, ClassTable* classtable)
{
auto e1_type = this->e1->semant(cur_class, classtable);
auto e2_type = this->e2->semant(cur_class, classtable);
if (e1_type != Int || e2_type != Int) {
classtable->semant_error(cur_class->get_filename(), this)
<< "non-Int arguments: " << e1_type
<< " <= " << e2_type
<< "\n";
}
return (this->type = Bool);
}
/*
* Not:
* Check type(e1) == Bool, and whatever the result, derive to Bool
*
* Note:
* I still cannot understand why operator `not` is named `comp` in AST
*/
Symbol comp_class::semant(Class_ cur_class, ClassTable* classtable)
{
auto type_subexpr = this->e1->semant(cur_class, classtable);
/* It is safe to compare expr_type with Bool because neither Bool is inheritable
nor COOL support overloaded operator.
Thus we dont need to care about SELF_TYPE(self can never be Bool) and no
other type could use these operators.
*/
if (type_subexpr != Bool) {
classtable->semant_error(cur_class->get_filename(), this)
<< "Argument of 'not' has type " << type_subexpr
<< " instead of Bool.\n";
}
return (this->type = Bool);
}
/*
* Int:
* Integer constants derive to Int type
*/
Symbol int_const_class::semant(Class_ cur_class, ClassTable* classtable)
{
return (this->type = Int);
}
/*
* True | False:
* true or false both derive to Bool type
*/
Symbol bool_const_class::semant(Class_ cur_class, ClassTable* classtable)
{
return (this->type = Bool);
}
/*
* String
* String constants derive to String type
*/
Symbol string_const_class::semant(Class_ cur_class, ClassTable* classtable)
{
return (this->type = Str);
}
/*
* New:
* Two cases for new, `SELF_TYPE` and any other
* But here we dont check `SELF_TYPE[C]` here, if encountered `SELF_TYPE`, just
* annotate it as `SELF_TYPE` and defer the type check to the conformance part
* The only check we do here is to make sure its `type_name` exists
*
* Warning:
* there is a static `Symbol` variable also called `type_name`, which conflicts
* with `new__class::type_name`
*/
Symbol new__class::semant(Class_ cur_class, ClassTable* classtable)
{
if (classtable->class_defined_self(this->type_name)) {
this->type = this->type_name;
}
else {
classtable->semant_error(cur_class->get_filename(), this)
<< "\'new\' used with undefined class " << this->type_name
<< ".\n";
this->type = Object;
}
return this->type;
}
/*
* Isvoid:
* An `isvoid` test has type `Bool`, dont forget to eval its sub expr
*/
Symbol isvoid_class::semant(Class_ cur_class, ClassTable* classtable)
{
auto _ = this->e1->semant(cur_class, classtable);
return (this->type = Bool);
}
/*
* NoExpr:
* `no_expr` always has type `_no_type`
* However, this class should never be visited during type checking
*/
Symbol no_expr_class::semant(Class_ cur_class, ClassTable* classtable)
{
assert(0);
return No_type;
}
/*
* Var:
* If the env assigns an identifier ID type T, then ID has type T
* Otherwise, error report and set type Object
*/
Symbol object_class::semant(Class_ cur_class, ClassTable* classtable)
{
auto object_type = classtable->symtab_object_lookup_parent(cur_class, this->name);
if (object_type != nullptr) {
return (this->type = object_type);
}
else {
classtable->semant_error(cur_class->get_filename(), this)
<< "Undeclared identifier " << this->name<< ".\n";
return (this->type = Object);
}
}