def check_overridden_signatures(table): for c in table: if table[c].parent is None: continue for field in table[c].fields: cur = table[c].parent while cur is not None: if field in table[cur].fields: raise CompilationError( ErrorType.variableOverloading, f'Field with name "{field}" already exists in the superclass', table[c].lineno) cur = table[cur].parent for method in table[c].methods: method_params = list(table[c].methods[method].params.items()) method_ret = table[c].methods[method].return_type method_is_public = table[c].methods[method].is_public cur = table[c].parent while cur is not None: if method in table[cur].methods: if (method_params != list( table[cur].methods[method].params.items()) or method_ret != table[cur].methods[method].return_type or method_is_public != table[cur].methods[method].is_public): raise CompilationError( ErrorType.methodInBaseWithDifferentSignature, f'Method with name "{method}" already exists in the superclass ', 'with a different signature', table[c].methods[method].lineno) table[cur].methods[method].is_virtual = True cur = table[cur].parent
def visit_method_parameter(self, node, table, *args): if node.cur_id == 'this': raise CompilationError(ErrorType.invalidName, '"this" is not a valid parameter name', node.lineno) if node.cur_id in table: raise CompilationError(ErrorType.dublicatedParam, 'Duplicate parameter name: ' + node.cur_id, node.lineno) table[node.cur_id] = node.cur_type
def visit_var_declaration(self, node, table, check_table=None, *args): if node.varid == 'this': raise CompilationError(ErrorType.invalidVariableName, '"this" is not a valid variable name', node.lineno) if node.varid in table or (check_table and node.varid in check_table): raise CompilationError(ErrorType.dublicatedVariable, 'Duplicate variable name: ' + node.varid, node.lineno) table[node.varid] = node.vartype
def assert_is_subclass(self, node_type, expected_type, lineno): if expected_type in self.ATOMS or node_type in self.ATOMS: if node_type != expected_type: raise CompilationError(ErrorType.wrongType, f'Expected type "{expected_type}", got "{node_type}"', lineno) t = node_type while True: if t == expected_type: return t = self.table[t].parent if t is None: raise CompilationError(ErrorType.wrongType, f'Expected type "{expected_type}", got "{node_type}"', lineno)
def resolve_method(self, class_name, method_name, lineno): if class_name not in self.table: raise CompilationError(ErrorType.noMethodsExist, f'Type "{class_name}" has no methods', lineno) class_table = self.table[class_name] current = class_name while True: if method_name in class_table.methods: return (class_table.methods[method_name], current) current = class_table.parent class_table = self.table.get(current) if class_table is None: raise CompilationError(ErrorType.undefinedMethod, f'Undefined method: {class_name}.{method_name}', lineno)
def visit_call_expression(self, node, *args): obj = self.visit(node.obj) method, method_owner = self.resolve_method(obj, node.method, node.lineno) if not method.is_public and method_owner != self.class_name: raise CompilationError(ErrorType.privateMethod, f'Method {method_owner}.{node.method} is private', node.lineno) node.method_owner = method_owner if len(method.params) != len(node.args): raise CompilationError(ErrorType.wrongArgument, f'Expected {len(method.params)} argument{"s" if len(method.params) != 1 else ""} ' f'for {obj}.{node.method}', node.lineno) for arg, param in zip(node.args, method.params.values()): arg = self.visit(arg) self.assert_is_subclass(arg, param, node.lineno) return method.return_type
def p_mainclass(self, p): '''mainclass : CLASS ID LPARBR PUBLIC STATIC VOID ID LPAREN ID LPARSQ RPARSQ ID RPAREN LPARBR statement RPARBR RPARBR''' if p[7] != 'main': raise CompilationError(ErrorType.wrongMainMethod, 'Wrong main method name', p.lineno(7)) p[0] = mj_ast.MainClass(p[2], p[15]) p[0].lineno = p.lineno(2)
def p_term_number(self, p): '''term : NUMBER''' number = int(p[1]) if not constexpr_eval.is_mj_int(number): raise CompilationError(ErrorType.invalidInt, 'Too large for an integer', p.lineno(1)) p[0] = mj_ast.IntLiteral(number)
def p_statement_print(self, p): '''statement : ID DOT ID DOT ID LPAREN expression RPAREN SEMICOL''' if p[1] != 'System' or p[3] != 'out' or p[5] != 'println': raise CompilationError( ErrorType.onlyPrintlnCall, 'Only System.out.println can be called this way', p.lineno(1)) p[0] = mj_ast.PrintStatement(p[7]) p[0].lineno = p.lineno(5)
def dfs(c): if c in visited and c not in finalized: raise CompilationError(ErrorType.cycleInheritance, 'Inheritance loop in class ' + c, table[c].lineno) visited.add(c) if table[c].parent: dfs(table[c].parent) finalized.add(c)
def resolve_id(self, name, lineno): if not self.class_name or not self.method_name: raise CompilationError(ErrorType.undefinedVar, 'Undefined variable: ' + name, lineno) if name == 'this': return mj_ast.PARAM, self.class_name current = self.class_name class_table = self.table[current] if name in class_table.methods[self.method_name].vars: return mj_ast.LOCAL, class_table.methods[self.method_name].vars[name] if name in class_table.methods[self.method_name].params: return mj_ast.PARAM, class_table.methods[self.method_name].params[name] while True: if name in class_table.fields: return (mj_ast.FIELD, current), class_table.fields[name] current = class_table.parent class_table = self.table.get(current) if class_table is None: raise CompilationError(ErrorType.undefinedVar, 'Undefined variable: ' + name, lineno)
def visit_method_declaration(self, node, table, *args): if node.name in table: raise CompilationError(ErrorType.dublicatedMethod, 'Duplicate method name: ' + node.name, node.lineno) table[node.name] = method_table = MethodSymbolTable( node.is_public, node.return_type, node.lineno) for param in node.argseq: self.visit(param, method_table.params) for var in node.vardecl: self.visit(var, method_table.vars, method_table.params)
def visit_class_declaration(self, node, *args): if node.name in self.table: raise CompilationError(ErrorType.dublicatedClass, 'Duplicate class: ' + node.name, node.lineno) self.table[node.name] = class_table = ClassSymbolTable( node.parent, node.lineno) for var in node.vardecl: self.visit(var, class_table.fields) for method in node.methoddecl: self.visit(method, class_table.methods)
def p_term(self, p): '''term : term DOT ID | term DOT ID LPAREN paramseq RPAREN''' if len(p) == 4: if p[3] != 'length': raise CompilationError(ErrorType.onlyLengthAccess, 'Only length can be accessed this way', p.lineno(3)) p[0] = mj_ast.LengthExpression(p[1]) else: p[0] = mj_ast.CallExpression(p[1], p[3], p[5]) p[0].lineno = p.lineno(3)
def check_inheritance_loops(table): visited = set() finalized = set() def dfs(c): if c in visited and c not in finalized: raise CompilationError(ErrorType.cycleInheritance, 'Inheritance loop in class ' + c, table[c].lineno) visited.add(c) if table[c].parent: dfs(table[c].parent) finalized.add(c) for c in table: if table[c].parent: if table[c].parent not in table: raise CompilationError(ErrorType.classNotFound, 'No such class: ' + table[c].parent, table[c].lineno) if c in finalized: continue dfs(c)
def p_error(self, p): if p is None: raise CompilationError(ErrorType.endOfFile, 'Unexpected end of file') raise CompilationError(ErrorType.unexpectedToken, f'Unexpected token "{p.value}"', p.lineno)
def visit_assign_statement(self, node, *args): if node.obj == 'this': raise CompilationError(ErrorType.assignToThis, f'Cannot assign to this', node.lineno) node.type, obj = self.resolve_id(node.obj, node.lineno) val = self.visit(node.value) self.assert_is_subclass(val, obj, node.lineno)
def t_error(self, t): raise CompilationError(ErrorType.illegalCharacter, f'Illegal character "{t.value[0]}"', t.lineno)
def assert_type_exists(self, node_type, lineno): if node_type not in self.ATOMS and node_type not in self.table: raise CompilationError(ErrorType.nonexistentType, f'Type "{node_type}" does not exist', lineno)