from AST import ASTNode, getParseTreeNodes from MemberNodes import FieldNode, MethodNode from Environment import Env # types: class , interface class ClassInterNode(ASTNode): def checkHierarchy(self): # 3. An interface must not be repeated in an implements clause if self.superInter: unique = [] for inter in self.superInter: if inter.name in unique: raise Exception("ERROR: Class/Interface '{}' implements duplicate interfaces '{}'".format(self.name, inter.name)) unique.append(inter.name) # 7. A class or interface must not declare two methods with the same signature (name and parameter types). unique = [] for method in self.methods: key = (method.name, method.paramTypes) if key in unique: raise Exception("ERROR: Class/Interface '{}' declares 2 methods with the same signature '{}'".format(self.name, key[0])) unique.append(key) # 6. The hierarchy must be acyclic # 9. A class or interface must not contain (declare or inherit) two methods with the same signature but different return types # 11. A nonstatic method must not replace a static method # 13. A protected method must not replace a public method # 14. A method must not replace a final method return self.getContains([]) def getContains(self, hierarchy): # check if not acyclic if self.name in hierarchy: raise Exception("ERROR: The hierarchy is not acyclic '{}', saw '{}'".format(hierarchy, self.name)) hierarchy.append(self.name) # get contains contains = self.methods for inter in self.superInter: superContains = inter.getContains(hierarchy) for con in superContains: conOverwritten = False for method in self.methods: if (method == con): # cannot have same signiture but different return types if (method.methodType != con.methodType): raise Exception("ERROR: Class '{}' contains 2 methods '{}' with the same signature but different return types".format(self.name, method.name)) # protected must not replace public if 'protected' in method.mods and 'public' in con.mods: raise Exception("ERROR: Protected method '{}' in class '{}' replaces public method '{}' in class {}".format(method.name, self.name, con.name, inter.name)) conOverwritten = True break if not conOverwritten: contains.append(con) return contains # class class ClassNode(ClassInterNode): # always list all fields in the init method to show the class structure def __init__(self, parseTree): self.parseTree = parseTree self.name = '' self.fields = [] self.methods = [] self.constructors = [] self.mods = [] self.superClass = '' # these fields initially stores a string that represent the super self.superInter = [] # class/Interface's name, then stores a pointer to the class after type linking self.env = None self.children = [] for node in parseTree.children: if node.name == 'classMod': for m in node.children: self.mods.append(m.lex) elif node.name == 'ID': self.name = node.lex elif node.name == 'superClass': nameNodes = getParseTreeNodes(['ID', 'COMPID'], node) for n in nameNodes: # use for loop to handle no superclass case self.superClass = n.lex elif node.name == 'superInterface': nameNodes = getParseTreeNodes(['ID', 'COMPID'], node) for n in nameNodes: self.superInter.append(n.lex) elif node.name == 'classBody': fieldNodes = getParseTreeNodes(['fieldDcl'], node, ['constructorDcl', 'methodDcl']) for f in fieldNodes: self.fields.append(FieldNode(f)) constructorDcl = getParseTreeNodes(['constructorDcl'], node, ['fieldDcl', 'methodDcl']) for c in constructorDcl: self.constructors.append(MethodNode(c)) methodNodes = getParseTreeNodes(['methodDcl'], node, ['constructorDcl', 'fieldDcl']) for m in methodNodes: self.methods.append(MethodNode(m)) self.children += self.fields + self.methods + self.constructors def buildEnv(self, parentEnv): env = Env(parentEnv) for m in self.methods: env.addtoEnv(m) for f in self.fields: env.addtoEnv(f) # not adding constructor to the environment, since it's in the type namespace # when looking for a constructor, look for a class with the same name, and look in its constructors field self.env = env return env def linkType(self): # link types to the actual nodes fom the environment (envs already created) if self.env is not None: if self.superClass: newSuperClass = self.env.getNode(self.superClass, 'type') self.superClass = newSuperClass if self.superInter: for (index, inter) in enumerate(self.superInter): newSuperInter = self.env.getNode(inter, 'type') self.superInter[index] = newSuperInter def checkHierarchy(self): # 1. A class must not extend an interface. # 4. A class must not extend a final class. if self.superClass: if not isinstance(self.superClass, ClassNode): raise Exception("ERROR: Class '{}' extends non-class '{}'".format(self.name, self.superClass.name)) if 'final' in self.superClass.mods: raise Exception("ERROR: Class '{}' extends final class '{}'".format(self.name, self.superClass.name)) # 2. A class must not implement a class # 3. An interface must not be repeated in an implements clause if self.superInter: unique = [] for inter in self.superInter: if not isinstance(inter, InterNode): raise Exception("ERROR: Class '{}' implements non-interface '{}'".format(self.name, inter.name)) unique.append(inter.name) # 8. A class must not declare two constructors with the same parameter types unique = [] for cons in self.constructors: key = (cons.name, cons.paramTypes) if key in unique: raise Exception("ERROR: Class '{}' declares 2 constructors with the same parameter types".format(self.name)) unique.append(key) # centralized point for overlapping class & interface logic contains = super().checkHierarchy() # 10. A class that contains (declares or inherits) any abstract methods must be abstract. for con in contains: if ('abstract' in con.mods or not con.body) and 'abstract' not in self.mods: raise Exception("ERROR: Non-abstract Class '{}' contains an abstract method".format(self.name)) # hierarchy: string[] def getContains(self, hierarchy): # centralized logic contains = super().getContains(hierarchy) # get contains from extends class if self.superClass: addToContains = [] superContains = self.superClass.getContains(hierarchy) for con in superContains: conOverwritten = False for method in contains: if (method == con): # cannot have same signiture but different return types if (method.methodType != con.methodType): raise Exception("ERROR: Class '{}' contains 2 methods '{}' with the same signature but different return types".format(self.name, method.name)) # must not replace final if 'final' in con.mods: raise Exception("ERROR: Method '{}' in class '{}' replaces final method '{}' in class '{}'".format(method.name, self.name, con.name, self.superClass.name)) # nonstatic must not replace static if 'static' not in method.mods and 'static' in con.mods: raise Exception("ERROR: Non-static method '{}' in class '{}' replaces static method '{}' in class '{}'".format(method.name, self.name, con.name, self.superClass.name)) # protected must not replace public if 'protected' in method.mods and 'public' in con.mods: raise Exception("ERROR: Protected method '{}' in class '{}' replaces public method '{}' in class {}".format(method.name, self.name, con.name, self.superClass.name)) conOverwritten = True break if not conOverwritten: addToContains.append(con) contains += addToContains return contains def getConstructor(self, argTypes): for c in self.constructors: if c.paramTypes == argTypes: return c ##################################################################### # interface class InterNode(ClassInterNode): # always list all fields in the init method to show the class structure def __init__(self, parseTree): self.parseTree = parseTree self.name = '' self.methods = [] self.superInter = [] # list of strings of extendInterface's name, then stores a pointer to the node after type linking self.env = None self.children = [] for node in parseTree.children: if node.name == 'ID': self.name = node.lex elif node.name == 'extendInterface': nameNodes = getParseTreeNodes(['ID', 'COMPID'], node) for n in nameNodes: self.superInter.append(n.lex) elif node.name == 'interfaceBody': nodes = getParseTreeNodes(['interfaceMethodDcl'], node) for n in nodes: self.methods.append(MethodNode(n)) self.children = self.methods def buildEnv(self, parentEnv): env = Env(parentEnv) for c in self.children: env.addtoEnv(c) self.env = env return env def linkType(self): # link types to the actual nodes fom the environment (envs already created) if self.env is not None: if self.superInter: for (index, inter) in enumerate(self.superInter): newSuperInter = self.env.getNode(inter, 'type') self.superInter[index] = newSuperInter def checkHierarchy(self): # 5. An interface must not extend a class. # 3. An interface must not be repeated in an extends clause of an interface if self.superInter: unique = [] for inter in self.superInter: if not isinstance(inter, InterNode): raise Exception("ERROR: Interface '{}' extends non-interface '{}'".format(self.name, inter.name)) if inter.name in unique: raise Exception("ERROR: Interface '{}' extends duplicate interfaces '{}'".format(self.name, inter.name)) unique.append(inter.name) # centralized point for overlapping class & interface logic contains = super().checkHierarchy() # hierarchy: string[] def getContains(self, hierarchy): # centralized logic return super().getContains(hierarchy)