import hutn_compiler.hutnparser as hp import hutn_compiler.symbol_table as st import sys from hutn_compiler.declare_functions_visitor import DeclareFunctionsVisitor from hutn_compiler.hutnparser import Tree from hutn_compiler.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 = [] # Count whether we are in a while or not self.while_counter = 0 # 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 "Void" in (type(l_type), type(r_type)): return True if "Element" in (type(l_type), type(r_type)): return False if l_type not in ["Integer", "Float", "Boolean"] or r_type not in ["Integer", "Float", "Boolean"]: 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(tree)[0], Tree.get_tail(tree)[2] self.do_check_binary_ops_arithmetic(l, r) def generalize_binary_ops_arithmetic(self, tree): l, r = Tree.get_tail(tree)[0], Tree.get_tail(tree)[2] l_type, r_type = self.get_type(l), self.get_type(r) if l_type == "Float" or r_type == "Float": return "Float" elif l_type == "Integer" or r_type == "Integer": return "Integer" else: return "Boolean" def check_unary_ops_arithmetic(self, tree, operator_name): l = Tree.get_tail(tree)[1] l_type = self.get_type(l) if l_type not in ["Boolean", "Integer", "Float"]: 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(tree)[1] l_type = self.get_type(l) try: if l_type == "Boolean": return "Integer" elif l_type == "Integer" or l_type == "Float": return l_type else: raise RuntimeError("You cannot promote {} to a numeric type".format(str(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(tree)[0], Tree.get_tail(tree)[2] self.do_check_assignment(l, r) def check_return(self, tree): l = self.current_funcdecl if len(Tree.get_tail(tree)) > 2: r = Tree.get_tail(tree)[1] r_type = None else: r = None r_type = "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 self.get_type(tree) == "Element": return if self.get_type(tree) not in ["Boolean", "Integer", "Float"]: 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): if i == -1: child = tree else: child = Tree.get_tail(tree)[i] if len(Tree.get_tail(child)) > 1: try: l, op, r = Tree.get_tail(child) except: # Something went wrong... this code is severely broken return 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 = self.func_call(call_name, [l, r], tree) try: self.visit(call_tree) except RuntimeError: call_signature = "Boolean function {1}({2}, {2})".format(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)) if i == -1: tree['head'] = call_tree['head'] tree['tail'] = call_tree['tail'] tree['_tail'] = None else: Tree.replace_child(tree, child, call_tree) self.set_type(tree, self.get_type(Tree.get_tail(tree)[i])) def replace_child_unary_op_with_call(self, tree): child = Tree.get_tail(tree)[0] if child['head'] == "keep_sign": Tree.replace_child(tree, child, Tree.get_tail(child)[1]) else: op, l = Tree.get_tail(child) l_type = self.get_type(l) call_name = SemanticsVisitor.call_name_unary(l_type, op) call_tree = self.func_call(call_name, [l], tree) try: self.visit(call_tree) except RuntimeError: call_signature = "Boolean function {1}({2})".format(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(tree, child, call_tree) self.set_type(tree, self.get_type(Tree.get_tail(tree)[0])) def cast_binary_ops_arithmetic(self, tree): l, op, r = Tree.get_tail(tree) l_type, r_type = self.get_type(l), self.get_type(r) if type(l_type) != type(r_type): # if two different numeric types if l_type == "Float" or r_type == "Float": g_type = "Float" elif l_type == "Integer" or r_type == "Integer": g_type = "Integer" else: g_type = "Boolean" 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(tree) l_type, r_type = self.get_type(l), self.get_type(r) self.perform_implicit_cast(tree, l, l_type, "Boolean") self.perform_implicit_cast(tree, r, r_type, "Boolean") def cast_unary_ops_arithmetic(self, tree): l = Tree.get_tail(tree)[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) def func_call(self, name, params, old_tree): startpos = old_tree['startpos'] endpos = old_tree['endpos'] inputfile = old_tree['inputfile'] tree = {"head": "func_call", "tail": [ {"head": "rvalue", "tail": [ {"head": "ID", "tail": [name], "startpos": startpos, "endpos": endpos, "inputfile": inputfile}], "startpos": startpos, "endpos": endpos, "inputfile": inputfile}], "startpos": startpos, "endpos": endpos, "inputfile": inputfile} for p in params: self.replace_child_binary_op_with_call(p, -1) params = [{"head": "expression", "tail": [p], "startpos": startpos, "endpos": endpos, "inputfile": inputfile} for p in params] tree['tail'].extend(params) return {"head": "expression", "tail": [tree], "startpos": startpos, "endpos": endpos, "inputfile": inputfile} @staticmethod def cast_name(from_type, to_type): to_t = str(to_type)[0].lower() cast_name = "cast_" + {"f": "float", "i": "integer", "b": "boolean", "s": "string"}[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 "Element" in (from_type, to_type): return if from_type == to_type: return cast_name = SemanticsVisitor.cast_name(from_type, to_type) cast_tree = self.func_call(cast_name, [child], tree) try: self.visit(cast_tree) except RuntimeError: self.raise_implicit_cast_error(from_type, to_type, child) Tree.replace_child(tree, 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 Tree.get_text(self.tree, 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(tree): self.inputfiles[0] = child['inputfile'] self.declare_functions_visitor.visit(child) for child in Tree.get_tail(tree): 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(tree, "type_specifier") var_id = Tree.get_child(tree, "ID") var_type = Tree.get_text(type_spec) var_name = Tree.get_text(var_id) symbol = {"name": var_name, "type": var_type, "is_global": self.current_funcdecl is None, "node": None, "params": 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(tree)[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(tree)) == 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, "Boolean") def visit_conjunction(self, tree): self.visit_children(tree) if len(Tree.get_tail(tree)) == 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, "Boolean") def visit_comparison(self, tree): self.visit_children(tree) if len(Tree.get_tail(tree)) == 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, "Boolean") def visit_relation(self, tree): self.visit_children(tree) if len(Tree.get_tail(tree)) == 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, "Boolean") def visit_sum(self, tree): self.visit_children(tree) if len(Tree.get_tail(tree)) == 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(tree)[0])) def visit_term(self, tree): self.visit_children(tree) if len(Tree.get_tail(tree)) == 1: self.set_type(tree, self.get_type(Tree.get_tail(tree)[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(tree)[0])) def visit_factor(self, tree): self.visit_children(tree) if Tree.get_child(tree, "primary") is not None: self.set_type(tree, self.get_type(Tree.get_tail(tree)[0])) else: self.replace_child_unary_op_with_call(tree) def visit_logical_not(self, tree): self.visit_children(tree) l = Tree.get_tail(tree)[1] l_type = self.get_type(l) self.perform_implicit_cast(tree, l, l_type, "Boolean") self.set_type(tree, self.get_type(Tree.get_tail(tree)[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(tree)[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(tree)[1])) def visit_primary(self, tree): self.visit_children(tree) self.set_type(tree, self.get_type(Tree.get_tail(tree)[0])) def visit_parenthesized(self, tree): self.visit_children(tree) self.set_type(tree, self.get_type(Tree.get_tail(tree)[1])) def visit_atomvalue(self, tree): self.visit_children(tree) self.set_type(tree, self.get_type(Tree.get_tail(tree)[0])) def visit_type_specifier(self, tree): self.set_type(tree, "Type") def visit_actionname(self, tree): self.set_type(tree, "Action") def visit_string(self, tree): self.set_type(tree, "String") def visit_integer(self, tree): self.set_type(tree, "Integer") def visit_float(self, tree): self.set_type(tree, "Float") # there is no such rule in the grammar, we just avoid code duplicates def visit_id(self, tree): name = Tree.get_text(tree) #TODO this is set to the function returnvalue, even if we use the function pointer... 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(tree)) > 1: # Complex: dict_read operation needed child = Tree.get_tail(tree)[0] node = Tree.get_child(tree, "rvalue") expression = Tree.get_child(tree, "expression") operation = "dict_read" call_tree = self.func_call(operation, [node, expression], tree) 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(tree)[0]) self.set_type(tree, symbol['type']) if not st.Symbol.is_func(symbol): if symbol['type'] == "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(tree, "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'], st.Symbol.signature(symbol))) """ 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'] == "__input": tree['head'] = "input" elif symbol['name'] == "__output": tree['head'] = "output" 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, "Element") def visit_list(self, tree): self.set_type(tree, "Element") def visit_dict_item(self, tree): pass def visit_ifelse(self, tree): self.visit_children(tree) expressions = Tree.get_children(tree, "expression") for expression in expressions: self.check_predicate(expression) def visit_while(self, tree): self.while_counter += 1 self.visit_children(tree) self.while_counter -= 1 expression = Tree.get_child(tree, "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(tree, '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(tree, "ID") type_spec = Tree.get_child(tree, "type_specifier") param_type = Tree.get_text(type_spec) param_name = Tree.get_text(param_id) symbol = {"name": param_name, "type": param_type, "is_global": False, "node": None, "params": None} 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, "Boolean") def visit_break(self, tree): if self.while_counter == 0: raise RuntimeError( "{}:{}:{}: error: break outside of while".format( self.inputfiles[0], tree['startpos']['line'], tree['startpos']['column'])) def visit_continue(self, tree): if self.while_counter == 0: raise RuntimeError( "{}:{}:{}: error: continue outside of while".format( self.inputfiles[0], tree['startpos']['line'], tree['startpos']['column']))