import hutnparser as hp import symbol_table as st import sys import types_mv from declare_functions_visitor import DeclareFunctionsVisitor from visitor import Visitor class SemanticsVisitor(Visitor): def __init__(self, args): Visitor.__init__(self, args) self.symbol_table = st.SymbolTable() # there is only one input file, list is for sharing it among visitors self.inputfiles = [] # inherited attribute, set in funcdecl and used in return, # to ensure that (returned type == declared type) self.current_funcdecl = None self.declare_functions_visitor =\ DeclareFunctionsVisitor(self.symbol_table, self.inputfiles) @staticmethod def incompatible_types(l_type, r_type): if type(l_type) != type(r_type): if types_mv.Void in (type(l_type), type(r_type)): return True if types_mv.Element in (type(l_type), type(r_type)): return False if l_type.isNotNumber() or r_type.isNotNumber(): return True return False def do_check_binary_ops_arithmetic(self, l, r): l_type, r_type = self.get_type(l), self.get_type(r) if SemanticsVisitor.incompatible_types(l_type, r_type): raise RuntimeError( "{}:{}:{}: error: invalid operands to binary operator " "(have {} and {})".format(self.inputfiles[0], l.startpos['line'], l.startpos['column'], str(l_type), str(r_type))) def check_binary_ops_arithmetic(self, tree): l, r = tree.get_tail()[0], tree.get_tail()[2] self.do_check_binary_ops_arithmetic(l, r) def generalize_binary_ops_arithmetic(self, tree): l, r = tree.get_tail()[0], tree.get_tail()[2] l_type, r_type = self.get_type(l), self.get_type(r) return types_mv.generalize_arithmetic(l_type, r_type) def check_unary_ops_arithmetic(self, tree, operator_name): l = tree.get_tail()[1] l_type = self.get_type(l) if l_type.isNotNumber(): raise RuntimeError( "{}:{}:{}: error: wrong type argument to unary {} " "({})".format(self.inputfiles[0], l.startpos['line'], l.startpos['column'], operator_name, str(l_type))) def promote_unary_ops_arithmetic(self, tree): l = tree.get_tail()[1] l_type = self.get_type(l) try: return types_mv.promote_arithmetic(l_type) except RuntimeError: raise RuntimeError( "Pathological situation in promote_unary_ops_arithmetic: " "check_unary_ops_arithmetic has not been executed") # if r_type is provided, r is not used def do_check_assignment(self, l, r, r_type = None): if r_type is not None: l_type = self.get_type(l) else: l_type, r_type = self.get_type(l), self.get_type(r) if SemanticsVisitor.incompatible_types(l_type, r_type): raise RuntimeError("{}:{}:{}: error: cannot assign a value of " "type '{}' to a variable of type '{}'" .format(self.inputfiles[0], l.startpos['line'], l.startpos['column'], str(r_type), str(l_type))) def check_assignment(self, tree): l, r = tree.get_tail()[0], tree.get_tail()[2] self.do_check_assignment(l, r) def check_return(self, tree): l = self.current_funcdecl if len(tree.get_tail()) > 2: r = tree.get_tail()[1] r_type = None else: r = None r_type = types_mv.Void() if l: self.do_check_assignment(l, r, r_type) else: raise RuntimeError( "{}:{}:{}: error: 'return' is used outside of a function" .format(self.inputfiles[0], tree.startpos['line'], tree.startpos['column'])) def check_predicate(self, tree): if isinstance(self.get_type(tree), types_mv.Element): return if self.get_type(tree).isNotNumber(): raise RuntimeError( "{}:{}:{}: error: predicates of type '{}' are not allowed" .format(self.inputfiles[0], tree.startpos['line'], tree.startpos['column'], self.get_type(tree))) def replace_child_binary_op_with_call(self, tree, i=0): child = tree.get_tail()[i] if len(child.get_tail()) > 1: l, op, r = child.get_tail() l_type, r_type = self.get_type(l), self.get_type(r) if type(l_type) != type(r_type): print("Error: " + str(l_type) + " <-> " + str(r_type)) raise RuntimeError( "{}:{}:{}: error: children were not casted".format( self.inputfiles[0], tree.startpos['line'], tree.startpos['column'] )) call_name = SemanticsVisitor.call_name_binary(l_type, op) call_tree = SemanticsVisitor.func_call(call_name, [l, r]) try: self.visit(call_tree) except RuntimeError: call_signature = "{0} function {1}({2}, {2})".format( str(types_mv.Boolean()), call_name, l_type) raise RuntimeError( "{}:{}:{}: error: cannot perform {}: function '{}' is " "not found".format( self.inputfiles[0], tree.startpos['line'], tree.startpos['column'], child.head, call_signature)) tree.replace_child(child, call_tree) self.set_type(tree, self.get_type(tree.get_tail()[i])) def replace_child_unary_op_with_call(self, tree): child = tree.get_tail()[0] if child.head == "keep_sign": tree.replace_child(child, child.get_tail()[1]) else: op, l = child.get_tail() l_type = self.get_type(l) call_name = SemanticsVisitor.call_name_unary(l_type, op) call_tree = SemanticsVisitor.func_call(call_name, [l]) try: self.visit(call_tree) except RuntimeError: call_signature = "{0} function {1}({2})".format( str(types_mv.Boolean()), call_name, l_type) raise RuntimeError( "{}:{}:{}: error: cannot perform {}: function '{}' is " "not found".format( self.inputfiles[0], tree.startpos['line'], tree.startpos['column'], child.head, call_signature)) tree.replace_child(child, call_tree) self.set_type(tree, self.get_type(tree.get_tail()[0])) def cast_binary_ops_arithmetic(self, tree): l, op, r = tree.get_tail() l_type, r_type = self.get_type(l), self.get_type(r) if type(l_type) != type(r_type): # if two different numeric types g_type = types_mv.generalize_arithmetic(l_type, r_type) self.perform_implicit_cast(tree, l, l_type, g_type) self.perform_implicit_cast(tree, r, r_type, g_type) def cast_binary_ops_logical(self, tree): l, op, r = tree.get_tail() l_type, r_type = self.get_type(l), self.get_type(r) self.perform_implicit_cast(tree, l, l_type, types_mv.Boolean()) self.perform_implicit_cast(tree, r, r_type, types_mv.Boolean()) def cast_unary_ops_arithmetic(self, tree): l = tree.get_tail()[1] l_type = self.get_type(l) p_type = self.promote_unary_ops_arithmetic(tree) self.perform_implicit_cast(tree, l, l_type, p_type) @staticmethod def func_call(name, params): zero = {'line': 0, 'column': 0} tree = hp.Tree( "func_call", [ hp.Tree("rvalue", [ hp.Tree("ID", [name], zero, zero) ], zero, zero), # Tokens have no impact on visit_func_call. So leave them out. # hp.Tree("LPAREN", ["("], zero, zero), # hp.Tree("COMMA", [","], zero, zero), # hp.Tree("RPAREN", [")"], zero, zero), ], zero, zero) params = [hp.Tree("expression", [p], zero, zero) for p in params] tree.tail.extend(params) return hp.Tree("expression", [tree], zero, zero) @staticmethod def cast_name(from_type, to_type): from_t = str(from_type)[0].lower() to_t = str(to_type)[0].lower() cast_name = "cast_{}2{}".format(from_t, to_t) return cast_name def raise_implicit_cast_error(self, from_type, to_type, tree): cast_name = SemanticsVisitor.cast_name(from_type, to_type) cast_signature = "{} function {}({})".format( str(to_type), cast_name, str(from_type)) raise RuntimeError( "{}:{}:{}: error: cannot perform implicit cast from '{}'" " to '{}': function '{}' is not found".format( self.inputfiles[0], tree.startpos['line'], tree.startpos['column'], str(to_type), str(from_type), cast_signature)) def perform_implicit_cast(self, tree, child, from_type, to_type): if types_mv.Element in (type(from_type), type(to_type)): return if type(from_type) == type(to_type): return cast_name = SemanticsVisitor.cast_name(from_type, to_type) cast_tree = \ SemanticsVisitor.func_call(cast_name, [child]) try: self.visit(cast_tree) except RuntimeError: self.raise_implicit_cast_error(from_type, to_type, child) tree.replace_child(child, cast_tree) types = { "Integer": "integer", "Float": "float", "Boolean": "bool", "String": "string", "Action": "action", "Element": "element", "Type": "type" } binary_ops = { "OR": "or", "AND": "and", "EQ": "eq", "NEQ": "neq", "LT": "lt", "GT": "gt", "LE": "lte", "GE": "gte", "PLUS": "addition", "MINUS": "subtraction", "STAR": "multiplication", "SLASH": "division" } unary_ops = { "NOT": "not", "MINUS": "neg" } @staticmethod def call_name_binary(operand_type, operator): # String joins should also be possible if str(operand_type) == "String": if operator.head == "PLUS": return "string_join" if operator.head == "EQ": return "value_eq" elif operator.head == "NEQ": return "value_neq" call_name = "{}_{}".format(SemanticsVisitor.types[str(operand_type)], SemanticsVisitor.binary_ops[operator.head]) return call_name @staticmethod def call_name_unary(operand_type, operator): call_name = "{}_{}".format(SemanticsVisitor.types[str(operand_type)], SemanticsVisitor.unary_ops[operator.head]) return call_name def dump(self): return self.tree.get_text(with_implicit=True) # return "No code generation here" # a visit_* method for each non-terminal in the grammar def visit_start(self, tree): self.symbol_table.open_scope() self.inputfiles.append(tree.inputfile) for child in tree.get_tail(): self.inputfiles[0] = child.inputfile self.declare_functions_visitor.visit(child) for child in tree.get_tail(): self.inputfiles[0] = child.inputfile self.visit(child) self.inputfiles.pop() self.symbol_table.close_scope() self.tree = tree def visit_statement(self, tree): self.visit_children(tree) def visit_definition(self, tree): self.visit_vardecl(tree) def visit_vardecl(self, tree): type_spec = tree.get_child("type_specifier") var_id = tree.get_child("ID") var_type = types_mv.string_to_type(type_spec.get_text()) var_name = var_id.get_text() symbol = st.Symbol(var_name, var_type, is_global=self.current_funcdecl is None) try: self.symbol_table.add(symbol) except Exception: raise RuntimeError( "{}:{}:{}: error: redeclaration of '{}'".format( self.inputfiles[0], tree.startpos['line'], tree.startpos['column'], var_name)) self.set_symbol(tree, symbol) def visit_assignment(self, tree): self.visit_children(tree) self.check_assignment(tree) def visit_expression(self, tree): self.visit_children(tree) self.set_type(tree, self.get_type(tree.get_tail()[0])) def visit_binary_operation(self, tree): self.visit_children(tree) self.replace_child_binary_op_with_call(tree) def visit_disjunction(self, tree): self.visit_children(tree) if len(tree.get_tail()) == 1: self.replace_child_binary_op_with_call(tree) else: self.replace_child_binary_op_with_call(tree, 2) self.cast_binary_ops_logical(tree) self.set_type(tree, types_mv.Boolean()) def visit_conjunction(self, tree): self.visit_children(tree) if len(tree.get_tail()) == 1: self.replace_child_binary_op_with_call(tree) else: self.replace_child_binary_op_with_call(tree, 2) self.cast_binary_ops_logical(tree) self.set_type(tree, types_mv.Boolean()) def visit_comparison(self, tree): self.visit_children(tree) if len(tree.get_tail()) == 1: self.replace_child_binary_op_with_call(tree) else: self.replace_child_binary_op_with_call(tree, 2) self.check_binary_ops_arithmetic(tree) self.cast_binary_ops_arithmetic(tree) self.set_type(tree, types_mv.Boolean()) def visit_relation(self, tree): self.visit_children(tree) if len(tree.get_tail()) == 1: self.replace_child_binary_op_with_call(tree) else: self.replace_child_binary_op_with_call(tree, 2) self.check_binary_ops_arithmetic(tree) self.cast_binary_ops_arithmetic(tree) self.set_type(tree, types_mv.Boolean()) def visit_sum(self, tree): self.visit_children(tree) if len(tree.get_tail()) == 1: self.replace_child_binary_op_with_call(tree) else: self.replace_child_binary_op_with_call(tree, 2) self.check_binary_ops_arithmetic(tree) self.cast_binary_ops_arithmetic(tree) # after the cast both parameters have the same (generalized) type: self.set_type(tree, self.get_type(tree.get_tail()[0])) def visit_term(self, tree): self.visit_children(tree) if len(tree.get_tail()) == 1: self.set_type(tree, self.get_type(tree.get_tail()[0])) else: self.check_binary_ops_arithmetic(tree) self.cast_binary_ops_arithmetic(tree) # after the cast both parameters have the same (generalized) type: self.set_type(tree, self.get_type(tree.get_tail()[0])) def visit_factor(self, tree): self.visit_children(tree) if tree.get_child("primary") is not None: self.set_type(tree, self.get_type(tree.get_tail()[0])) else: self.replace_child_unary_op_with_call(tree) def visit_logical_not(self, tree): self.visit_children(tree) l = tree.get_tail()[1] l_type = self.get_type(l) self.perform_implicit_cast(tree, l, l_type, types_mv.Boolean()) self.set_type(tree, self.get_type(tree.get_tail()[1])) def visit_invert_sign(self, tree): self.visit_children(tree) self.check_unary_ops_arithmetic(tree, "minus") self.cast_unary_ops_arithmetic(tree) self.set_type(tree, self.get_type(tree.get_tail()[1])) def visit_keep_sign(self, tree): self.visit_children(tree) self.check_unary_ops_arithmetic(tree, "plus") self.cast_unary_ops_arithmetic(tree) self.set_type(tree, self.get_type(tree.get_tail()[1])) def visit_primary(self, tree): self.visit_children(tree) self.set_type(tree, self.get_type(tree.get_tail()[0])) def visit_parenthesized(self, tree): self.visit_children(tree) self.set_type(tree, self.get_type(tree.get_tail()[1])) def visit_atomvalue(self, tree): self.visit_children(tree) self.set_type(tree, self.get_type(tree.get_tail()[0])) def visit_type_specifier(self, tree): self.set_type(tree, types_mv.Type()) def visit_actionname(self, tree): self.set_type(tree, types_mv.Action()) def visit_string(self, tree): self.set_type(tree, types_mv.String()) def visit_integer(self, tree): self.set_type(tree, types_mv.Integer()) def visit_float(self, tree): self.set_type(tree, types_mv.Float()) # there is no such rule in the grammar, we just avoid code duplicates def visit_id(self, tree): name = tree.get_text() try: symbol = self.symbol_table.get(name) except KeyError: raise RuntimeError("{}:{}:{}: error: '{}' is not declared".format( self.inputfiles[0], tree.startpos['line'], tree.startpos['column'], name)) self.set_type(tree, symbol.type) self.set_symbol(tree, symbol) def visit_rvalue(self, tree): if len(tree.get_tail()) > 1: # Complex: dict_read operation needed child = tree.get_tail()[0] node = tree.get_child("rvalue") expression = tree.get_child("expression") operation = "dict_read" call_tree = SemanticsVisitor.func_call(operation, [node, expression]) self.visit(call_tree) tree.head = call_tree.head tree._tail = call_tree.tail tree.tail = call_tree.tail self.set_type(tree, self.get_type(node)) else: # Simple self.visit_id(tree) def visit_lvalue(self, tree): self.visit_id(tree) def visit_func_call(self, tree): self.visit_children(tree) symbol = self.get_symbol(tree.get_tail()[0]) self.set_type(tree, symbol.type) if not symbol.is_func(): if isinstance(symbol.type, types_mv.Element): sys.stderr.write("{}:{}:{}: warning: calling a variable of type " "'Element'\n".format(self.inputfiles[0], tree.startpos['line'], tree.startpos['column'], symbol.name)) return # allow the call without knowing the declaration raise RuntimeError( "{}:{}:{}: error: '{}' is a variable of type '{}', not a " "function".format(self.inputfiles[0], tree.startpos['line'], tree.startpos['column'], symbol.name, symbol.type)) expressions = tree.get_children("expression") if len(expressions) != len(symbol.params): raise RuntimeError( "{}:{}:{}: error: wrong number of arguments to " "function '{}'".format(self.inputfiles[0], tree.startpos['line'], tree.startpos['column'], symbol.signature())) for i in range(len(expressions)): arg_type = self.get_type(expressions[i]) param_type = symbol.params[i] if SemanticsVisitor.incompatible_types(arg_type, param_type): raise RuntimeError( "{}:{}:{}: error: argument {} has type '{}' instead of " "'{}', calling function '{}'".format( self.inputfiles[0], tree.startpos['line'], tree.startpos['column'], i + 1, str(arg_type), str(param_type), symbol.signature())) if type(arg_type) != type(param_type): self.perform_implicit_cast(tree, expressions[i], arg_type, param_type) if symbol.name in ["input", "output"]: tree.head = symbol.name def visit_input(self, tree): pass # no need to visit it again def visit_output(self, tree): pass # no need to visit it again def visit_dictionary(self, tree): self.set_type(tree, types_mv.Element) def visit_list(self, tree): self.set_type(tree, types_mv.Element) def visit_dict_item(self, tree): pass def visit_ifelse(self, tree): self.visit_children(tree) expressions = tree.get_children("expression") for expression in expressions: self.check_predicate(expression) def visit_while(self, tree): self.visit_children(tree) expression = tree.get_child("expression") self.check_predicate(expression) def visit_block(self, tree): self.symbol_table.open_scope() self.visit_children(tree) self.symbol_table.close_scope() def visit_func_body(self, tree): self.visit_children(tree) def visit_funcdecl(self, tree): # here we only visit the body cause the declaration is already done # by declare_functions_visitor if tree.get_child('func_body') is not None: self.current_funcdecl = tree self.symbol_table.open_scope() self.visit_children(tree) self.symbol_table.close_scope() self.current_funcdecl = None def visit_parameter(self, tree): param_id = tree.get_child("ID") type_spec = tree.get_child("type_specifier") param_type = types_mv.string_to_type(type_spec.get_text()) param_name = param_id.get_text() symbol = st.Symbol(param_name, param_type, is_global=False) try: self.symbol_table.add(symbol) except Exception: raise RuntimeError( "{}:{}:{}: error: redeclaration of '{}'".format( self.inputfiles[0], tree.startpos['line'], tree.startpos['column'], param_name)) self.set_symbol(tree, symbol) def visit_return(self, tree): self.visit_children(tree) self.check_return(tree) def visit_bool(self, tree): self.set_type(tree, types_mv.Boolean())