| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692 |
- 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']))
|