diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 000000000..92721c71d --- /dev/null +++ b/.pylintrc @@ -0,0 +1,3 @@ +[MASTER] +disable=C0114, # missing-module-docstring +disable=C0115, \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..91bab81f6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "python.linting.pylintEnabled": true, + "python.linting.enabled": true, + "prettier.useTabs": true, + "editor.formatOnSave": true +} \ No newline at end of file diff --git a/doc/report.pdf b/doc/report.pdf new file mode 100644 index 000000000..214affc0d Binary files /dev/null and b/doc/report.pdf differ diff --git a/doc/team.yml b/doc/team.yml index c16162532..b96f2e877 100644 --- a/doc/team.yml +++ b/doc/team.yml @@ -1,10 +1,8 @@ members: - - name: Nombre Apellido1 Apellido2 - github: github_id - group: CXXX - - name: Nombre Apellido1 Apellido2 - github: github_id - group: CXXX - - name: Nombre Apellido1 Apellido2 - github: github_id - group: CXXX + - name: Olivia Gonzalez Peña + github: livi98 + group: C411 + - name: Juan Carlos Casteleiro Wong + github: cwjki + group: C411 + diff --git a/requirements.txt b/requirements.txt index 9eb0cad1a..5a914fd88 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ pytest pytest-ordering +ply \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/code_generator/BaseCILToMIPSVisitor.py b/src/code_generator/BaseCILToMIPSVisitor.py new file mode 100644 index 000000000..8962589fb --- /dev/null +++ b/src/code_generator/BaseCILToMIPSVisitor.py @@ -0,0 +1,45 @@ + +class BaseCILToMIPSVisitor: + def __init__(self): + self.mips_code = '' + self.text = '' + self.data = '' + self.mips_operators = { + '+': 'add', + '-': 'sub', + '*': 'mul', + '/': 'div', + '<': 'slt', + '<=': 'sle', + '=': 'seq', + } + self.current_function = None + self.types = None + self.attr_offset = {} + self.method_offset = {} + self.var_offset = {} + self.runtime_errors = {} + self.register_runtime_errors() + + def is_param(self, name): + return name in self.current_function.params + + def register_runtime_errors(self): + self.runtime_errors[ + 'dispatch_void'] = 'Runtime Error: A dispatch (static or dynamic) on void' + self.runtime_errors['case_void'] = 'Runtime Error: A case on void' + self.runtime_errors['case_no_match'] = 'Runtime Error: Execution of a case statement without a matching branch' + self.runtime_errors['div_zero'] = 'Runtime Error: Division by zero' + self.runtime_errors['substr'] = 'Runtime Error: Substring out of range' + self.runtime_errors['heap'] = 'Runtime Error: Heap overflow' + for error in self.runtime_errors: + self.data += f'{error}: .asciiz "{self.runtime_errors[error]}"\n' + self.generate_runtime_error(error) + + def generate_runtime_error(self, error): + self.text += f'{error}_error:\n' + self.text += f'la $a0 {error}\n' + self.text += f'li $v0, 4\n' + self.text += 'syscall\n' + self.text += 'li $v0, 10\n' + self.text += 'syscall\n' diff --git a/src/code_generator/BaseCoolToCilVisitor.py b/src/code_generator/BaseCoolToCilVisitor.py new file mode 100644 index 000000000..206b006c5 --- /dev/null +++ b/src/code_generator/BaseCoolToCilVisitor.py @@ -0,0 +1,325 @@ +import code_generator.cil_ast as cil +from semantic.semantic import VariableInfo + + +class BaseCOOLToCILVisitor: + def __init__(self, context): + self.dottypes = {} + self.dotdata = {} + self.dotcode = [] + self.current_type = None + self.current_method = None + self.current_function = None + self.context = context + self.context.update_graph() + self.context.set_type_tags() + self.context.set_type_max_tags() + self.label_count = 0 + + @property + def params(self): + return self.current_function.params + + @property + def localvars(self): + return self.current_function.localvars + + @property + def instructions(self): + return self.current_function.instructions + + def is_defined_param(self, name): + for p in self.params: + if p.name == name: + return True + return False + + def get_label(self): + self.label_count += 1 + return f'label_{self.label_count}' + + def register_param(self, vinfo): + # 'param_{self.current_function.name[9:]}_{vinfo.name}_{len(self.params)}' + param_node = cil.ParamNode(vinfo.name) + self.params.append(param_node) + return vinfo.name + + def register_local(self, name): + #vinfo.name = f'local_{self.current_function.name[9:]}_{vinfo.name}_{len(self.localvars)}' + local_node = cil.LocalNode(name) + self.localvars.append(local_node) + return name + + def define_internal_local(self, scope, name="internal", cool_var=None, class_type=None): + if class_type != None: + cil_name = f'{class_type}.{name}' + scope.define_cil_local(cool_var, cil_name, None) + self.register_local(cil_name) + else: + cil_name = f'{name}_{len(self.localvars)}' + scope.define_cil_local(cool_var, cil_name, None) + self.register_local(cil_name) + return cil_name + + def register_instruction(self, instruction): + self.instructions.append(instruction) + return instruction + + def to_function_name(self, method_name, type_name): + return f'{type_name}.{method_name}' + + def register_function(self, function_name): + function_node = cil.FunctionNode(function_name, [], [], []) + self.dotcode.append(function_node) + return function_node + + def register_type(self, name): + type_node = cil.TypeNode(name) + self.dottypes[name] = type_node + return type_node + + def register_data(self, value): + vname = f'data_{len(self.dotdata)}' + self.dotdata[vname] = value + return vname + + def define_built_in(self, scope): + for t in ['Object', 'Int', 'String', 'Bool', 'IO']: + builtin_type = self.context.get_type(t) + cil_type = self.register_type(t) + cil_type.attributes = [ + f'{attr}' for attr in builtin_type.attributes] + cil_type.methods = {f'{m}': f'{c}.{m}' for c, + m in builtin_type.get_all_methods()} + if t in ['Int', 'String', 'Bool']: + cil_type.attributes.append('value') + + # ----------------Object--------------------- + # init + self.current_function = self.register_function('Object_init') + self.register_param(VariableInfo('self', None)) + self.register_instruction(cil.ReturnNode(None)) + + # abort + self.current_function = self.register_function( + self.to_function_name('abort', 'Object')) + self.register_param(VariableInfo('self', None)) + msg = self.define_internal_local(scope=scope, name="msg") + key_msg = '' + for s in self.dotdata.keys(): + if self.dotdata[s] == 'Abort called from class ': + key_msg = s + self.register_instruction(cil.LoadStringNode(key_msg, msg)) + self.register_instruction(cil.PrintStringNode(msg)) + type_name = self.define_internal_local(scope=scope, name="type_name") + self.register_instruction(cil.TypeOfNode('self', type_name)) + self.register_instruction(cil.PrintStringNode(type_name)) + eol_local = self.define_internal_local(scope=scope, name="eol") + for s in self.dotdata.keys(): + if self.dotdata[s] == '\n': + eol = s + self.register_instruction(cil.LoadStringNode(eol, eol_local)) + self.register_instruction(cil.PrintStringNode(eol_local)) + self.register_instruction(cil.HaltNode()) + + # type_name + self.current_function = self.register_function( + self.to_function_name('type_name', 'Object')) + self.register_param(VariableInfo('self', None)) + type_name = self.define_internal_local(scope=scope, name="type_name") + self.register_instruction(cil.TypeOfNode('self', type_name)) + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(cil.AllocateNode( + 'String', self.context.get_type('String').tag, instance)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'String_init', [ + cil.ArgNode(type_name), cil.ArgNode(instance)], "String")) + self.register_instruction(cil.ReturnNode(instance)) + + # copy + self.current_function = self.register_function( + self.to_function_name('copy', 'Object')) + self.register_param(VariableInfo('self', None)) + copy = self.define_internal_local(scope=scope, name="copy") + self.register_instruction(cil.CopyNode('self', copy)) + self.register_instruction(cil.ReturnNode(copy)) + + # ----------------IO--------------------- + # init + self.current_function = self.register_function('IO_init') + self.register_param(VariableInfo('self', None)) + self.register_instruction(cil.ReturnNode(None)) + + # out_string + self.current_function = self.register_function( + self.to_function_name('out_string', 'IO')) + self.register_param(VariableInfo('self', None)) + self.register_param(VariableInfo('x', None)) + v = self.define_internal_local(scope=scope, name="v") + self.register_instruction(cil.GetAttrNode(v, 'x', 'value', 'String')) + self.register_instruction(cil.PrintStringNode(v)) + self.register_instruction(cil.ReturnNode('self')) + + # out_int + self.current_function = self.register_function( + self.to_function_name('out_int', 'IO')) + self.register_param(VariableInfo('self', None)) + self.register_param(VariableInfo('x', None)) + v = self.define_internal_local(scope=scope, name="v") + self.register_instruction(cil.GetAttrNode(v, 'x', 'value', 'Int')) + self.register_instruction(cil.PrintIntNode(v)) + self.register_instruction(cil.ReturnNode('self')) + + # in_string + self.current_function = self.register_function( + self.to_function_name('in_string', 'IO')) + self.register_param(VariableInfo('self', None)) + msg = self.define_internal_local(scope=scope, name="read_str") + self.register_instruction(cil.ReadStringNode(msg)) + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(cil.AllocateNode( + 'String', self.context.get_type('String').tag, instance)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'String_init', [ + cil.ArgNode(msg), cil.ArgNode(instance)], "String")) + self.register_instruction(cil.ReturnNode(instance)) + + # in_int + self.current_function = self.register_function( + self.to_function_name('in_int', 'IO')) + self.register_param(VariableInfo('self', None)) + number = self.define_internal_local(scope=scope, name="read_int") + self.register_instruction(cil.ReadIntNode(number)) + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(cil.AllocateNode( + 'Int', self.context.get_type('Int').tag, instance)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'Int_init', [ + cil.ArgNode(number), cil.ArgNode(instance)], "Int")) + self.register_instruction(cil.ReturnNode(instance)) + + # ----------------String--------------------- + # init + self.current_function = self.register_function('String_init') + self.register_param(VariableInfo('self', None)) + self.register_param(VariableInfo('v', None)) + self.register_instruction( + cil.SetAttrNode('self', 'value', 'v', 'String')) + self.register_instruction(cil.ReturnNode(None)) + + # length + self.current_function = self.register_function( + self.to_function_name('length', 'String')) + self.register_param(VariableInfo('self', None)) + length_result = self.define_internal_local(scope=scope, name="length") + self.register_instruction(cil.LengthNode('self', length_result)) + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(cil.AllocateNode( + 'Int', self.context.get_type('Int').tag, instance)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'Int_init', [ + cil.ArgNode(length_result), cil.ArgNode(instance)], "Int")) + self.register_instruction(cil.ReturnNode(instance)) + + # concat + self.current_function = self.register_function( + self.to_function_name('concat', 'String')) + self.register_param(VariableInfo('self', None)) + self.register_param(VariableInfo('s', None)) + + str1 = self.define_internal_local(scope=scope, name="str1") + self.register_instruction(cil.GetAttrNode( + str1, 'self', 'value', 'String')) + len1 = self.define_internal_local(scope=scope, name="len1") + self.register_instruction(cil.CallNode( + len1, 'String.length', [cil.ArgNode('self')], 'String')) + + str2 = self.define_internal_local(scope=scope, name="str2") + self.register_instruction( + cil.GetAttrNode(str2, 's', 'value', 'String')) + len2 = self.define_internal_local(scope=scope, name="len2") + self.register_instruction(cil.CallNode( + len2, 'String.length', [cil.ArgNode('s')], 'String')) + + local_len1 = self.define_internal_local(scope=scope, name="local_len1") + self.register_instruction(cil.GetAttrNode( + local_len1, len1, 'value', 'Int')) + local_len2 = self.define_internal_local(scope=scope, name="local_len2") + self.register_instruction(cil.GetAttrNode( + local_len2, len2, 'value', 'Int')) + + concat_result = self.define_internal_local(scope=scope, name="concat") + self.register_instruction(cil.ConcatNode( + str1, local_len1, str2, local_len2, concat_result)) + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(cil.AllocateNode( + 'String', self.context.get_type('String').tag, instance)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'String_init', [ + cil.ArgNode(concat_result), cil.ArgNode(instance)], "String")) + self.register_instruction(cil.ReturnNode(instance)) + + # substr + self.current_function = self.register_function( + self.to_function_name('substr', 'String')) + self.register_param(VariableInfo('self', None)) + self.register_param(VariableInfo('i', None)) + self.register_param(VariableInfo('l', None)) + i_value = self.define_internal_local(scope=scope, name="i_value") + self.register_instruction( + cil.GetAttrNode(i_value, 'i', 'value', 'Int')) + l_value = self.define_internal_local(scope=scope, name="l_value") + self.register_instruction( + cil.GetAttrNode(l_value, 'l', 'value', 'Int')) + subs_result = self.define_internal_local( + scope=scope, name="subs_result") + self.register_instruction(cil.SubStringNode( + i_value, l_value, 'self', subs_result)) + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(cil.AllocateNode( + 'String', self.context.get_type('String').tag, instance)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'String_init', [ + cil.ArgNode(subs_result), cil.ArgNode(instance)], "String")) + self.register_instruction(cil.ReturnNode(instance)) + + # ----------------Bool--------------------- + # init + self.current_function = self.register_function('Bool_init') + self.register_param(VariableInfo('self', None)) + self.register_param(VariableInfo('v', None)) + self.register_instruction( + cil.SetAttrNode('self', 'value', 'v', 'Bool')) + self.register_instruction(cil.ReturnNode(None)) + + # ----------------Int--------------------- + # init + self.current_function = self.register_function('Int_init') + self.register_param(VariableInfo('self', None)) + self.register_param(VariableInfo('v', None)) + self.register_instruction(cil.SetAttrNode('self', 'value', 'v', 'Int')) + self.register_instruction(cil.ReturnNode(None)) + + def build_string_equals_function(self, scope): + self.current_function = self.register_function('String_equals') + self.register_param(VariableInfo('str1', None)) + self.register_param(VariableInfo('str2', None)) + + str1 = self.define_internal_local(scope=scope, name="str1") + self.register_instruction(cil.GetAttrNode( + str1, 'str1', 'value', 'String')) + + str2 = self.define_internal_local(scope=scope, name="str2") + self.register_instruction(cil.GetAttrNode( + str2, 'str2', 'value', 'String')) + + result = self.define_internal_local( + scope=scope, name="comparison_result") + self.register_instruction(cil.StringEqualsNode(str1, str2, result)) + self.register_instruction(cil.ReturnNode(result)) diff --git a/src/code_generator/CILToMIPSVisitor.py b/src/code_generator/CILToMIPSVisitor.py new file mode 100644 index 000000000..1b9a52cac --- /dev/null +++ b/src/code_generator/CILToMIPSVisitor.py @@ -0,0 +1,486 @@ +from code_generator.BaseCILToMIPSVisitor import BaseCILToMIPSVisitor +import code_generator.cil_ast as cil +from utils import visitor + + +class CILToMIPSVisitor(BaseCILToMIPSVisitor): + @visitor.on('node') + def visit(self, node): + pass + + @visitor.when(cil.ProgramNode) + def visit(self, node): + self.types = node.dottypes + self.data += 'temp_string: .space 2048\n' + self.data += 'void: .word 0\n' + + for node_type in node.dottypes.values(): + self.visit(node_type) + + for node_data in node.dotdata.keys(): + self.data += f'{node_data}: .asciiz "{node.dotdata[node_data]}"\n' + + for node_function in node.dotcode: + self.visit(node_function) + + self.mips_code = '.data\n' + self.data + '.text\n' + self.text + return self.mips_code.strip() + + @visitor.when(cil.TypeNode) + def visit(self, node): + self.data += f'{node.name}_name: .asciiz "{node.name}"\n' + self.data += f'{node.name}_methods:\n' + for method in node.methods.values(): + self.data += f'.word {method}\n' + + idx = 0 + self.attr_offset.__setitem__(node.name, {}) + for attr in node.attributes: + self.attr_offset[node.name][attr] = 4*idx + 16 + idx = idx + 1 + + idx = 0 + self.method_offset.__setitem__(node.name, {}) + for met in node.methods: + self.method_offset[node.name][met] = 4*idx + idx = idx + 1 + + @visitor.when(cil.FunctionNode) + def visit(self, node): + self.current_function = node + self.var_offset.__setitem__(self.current_function.name, {}) + + for idx, var in enumerate(self.current_function.localvars + self.current_function.params): + self.var_offset[self.current_function.name][var.name] = (idx + 1)*4 + + self.text += f'{node.name}:\n' + + self.text += f'addi $sp, $sp, {-4 * len(node.localvars)}\n' + self.text += 'addi $sp, $sp, -4\n' + self.text += 'sw $ra, 0($sp)\n' + + for instruction in node.instructions: + self.visit(instruction) + + self.text += 'lw $ra, 0($sp)\n' + total = 4 * len(node.localvars) + 4 * len(node.params) + 4 + self.text += f'addi $sp, $sp, {total}\n' + self.text += 'jr $ra\n' + + @visitor.when(cil.ParamNode) + def visit(self, node): + pass + + @visitor.when(cil.LocalNode) + def visit(self, node, idx): + pass + + @visitor.when(cil.AssignNode) + def visit(self, node): + offset = self.var_offset[self.current_function.name][node.local_dest] + if node.expr: + if isinstance(node.expr, int): + self.text += f'li $t1, {node.expr}\n' + else: + right_offset = self.var_offset[self.current_function.name][node.expr] + self.text += f'lw $t1, {right_offset}($sp)\n' + else: + self.text += f'la $t1, void\n' + + self.text += f'sw $t1, {offset}($sp)\n' + + @visitor.when(cil.BinaryOperationNode) + def visit(self, node): + mips_comm = self.mips_operators[node.op] + left_offset = self.var_offset[self.current_function.name][node.lvalue] + right_offset = self.var_offset[self.current_function.name][node.rvalue] + self.text += f'lw $a0, {left_offset}($sp)\n' + self.text += f'lw $t1, {right_offset}($sp)\n' + if node.op == '/': + self.text += 'beq $t1, 0, div_zero_error\n' + self.text += f'{mips_comm} $a0, $a0, $t1\n' + result_offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $a0, {result_offset}($sp)\n' + + @visitor.when(cil.UnaryOperationNode) + def visit(self, node): + expr_offset = self.var_offset[self.current_function.name][node.expr] + self.text += f'lw $t1, {expr_offset}($sp)\n' + if node.op == 'not': + self.text += f'xor $a0, $t1, 1\n' + else: + self.text += f'neg $a0, $t1 \n' + + result_offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $a0, {result_offset}($sp)\n' + + @visitor.when(cil.GetAttrNode) + def visit(self, node): + self_offset = self.var_offset[self.current_function.name][node.instance] + self.text += f'lw $t0, {self_offset}($sp)\n' + + attr_offset = self.attr_offset[node.static_type][node.attr] + self.text += f'lw $t1, {attr_offset}($t0)\n' + + result_offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $t1, {result_offset}($sp)\n' + + @visitor.when(cil.SetAttrNode) + def visit(self, node): + self_offset = self.var_offset[self.current_function.name][node.instance] + self.text += f'lw $t0, {self_offset}($sp)\n' + + if node.value: + value_offset = self.var_offset[self.current_function.name][node.value] + self.text += f'lw $t1, {value_offset}($sp)\n' + else: + self.text += f'la $t1, void\n' + + attr_offset = self.attr_offset[node.static_type][node.attr] + self.text += f'sw $t1, {attr_offset}($t0)\n' + + @visitor.when(cil.AllocateNode) + def visit(self, node): + amount = len(self.types[node.type].attributes) + 4 + self.text += f'li $a0, {amount * 4}\n' + self.text += f'li $v0, 9\n' + self.text += f'syscall\n' + self.text += 'bge $v0, $sp heap_error\n' + self.text += f'move $t0, $v0\n' + + # Initialize Object + self.text += f'li $t1, {node.tag}\n' + self.text += f'sw $t1, 0($t0)\n' + self.text += f'la $t1, {node.type}_name\n' + self.text += f'sw $t1, 4($t0)\n' + self.text += f'li $t1, {amount}\n' + self.text += f'sw $t1, 8($t0)\n' + self.text += f'la $t1, {node.type}_methods\n' + self.text += f'sw $t1, 12($t0)\n' + + offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $t0, {offset}($sp)\n' + + @visitor.when(cil.TypeOfNode) + def visit(self, node): + obj_offset = self.var_offset[self.current_function.name][node.instance] + self.text += f'lw $t0, {obj_offset}($sp)\n' + self.text += 'lw $t1, 4($t0)\n' + res_offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $t1, {res_offset}($sp)\n' + + @visitor.when(cil.LabelNode) + def visit(self, node): + self.text += f'{node.label}:\n' + + @visitor.when(cil.GoToNode) + def visit(self, node): + self.text += f'b {node.label}\n' + + @visitor.when(cil.IfGoToNode) + def visit(self, node): + predicate_offset = self.var_offset[self.current_function.name][node.condition] + self.text += f'lw $t0, {predicate_offset}($sp)\n' + self.text += f'lw $a0, 16($t0)\n' + self.text += f'bnez $a0, {node.label}\n' + + @visitor.when(cil.CallNode) + def visit(self, node): + self.text += 'move $t0, $sp\n' + + for arg in node.params: + self.visit(arg) + + self.text += f'jal {node.function}\n' + result_offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $a1, {result_offset}($sp)\n' + + @visitor.when(cil.VCallNode) + def visit(self, node): + self.text += 'move $t0, $sp\n' + + for arg in node.params: + self.visit(arg) + + + value_offset = self.var_offset[self.current_function.name][node.instance] + self.text += f'lw $t1, {value_offset}($t0)\n' + self.text += 'la $t0, void\n' + self.text += 'beq $t1, $t0, dispatch_void_error\n' + + self.text += f'lw $t2, 12($t1)\n' + + method_offset = self.method_offset[node.dynamic_type][node.function] + self.text += f'lw $t3, {method_offset}($t2)\n' + + self.text += 'jal $t3\n' + + result_offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $a1, {result_offset}($sp)\n' + + @visitor.when(cil.ArgNode) + def visit(self, node): + value_offset = self.var_offset[self.current_function.name][node.name] + self.text += f'lw $t1, {value_offset}($t0)\n' + self.text += 'addi $sp, $sp, -4\n' + self.text += 'sw $t1, 0($sp)\n' + + @visitor.when(cil.CaseNode) + def visit(self, node): + offset = self.var_offset[self.current_function.name][node.expr] + self.text += f'lw $t0, {offset}($sp)\n' + self.text += f'lw $t1, 0($t0)\n' + self.text += 'la $a0, void\n' + self.text += f'bne $t1 $a0 {node.first_label}\n' + self.text += 'b case_void_error\n' + + @visitor.when(cil.CaseOptionNode) + def visit(self, node): + self.text += f'blt $t1 {node.tag} {node.next_label}\n' + self.text += f'bgt $t1 {node.max_tag} {node.next_label}\n' + + @visitor.when(cil.ReturnNode) + def visit(self, node): + if node.value: + offset = self.var_offset[self.current_function.name][node.value] + self.text += f'lw $a1, {offset}($sp)\n' + else: + self.text += f'move $a1, $zero\n' + + @visitor.when(cil.LoadStringNode) + def visit(self, node): + self.text += f'la $t0, {node.msg}\n' + offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $t0, {offset}($sp)\n' + + @visitor.when(cil.LoadIntNode) + def visit(self, node): + self.text += f'li $t0, {node.value}\n' + offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $t0, {offset}($sp)\n' + + @visitor.when(cil.LengthNode) + def visit(self, node): + offset = self.var_offset[self.current_function.name][node.variable] + self.text += f'lw $t0, {offset}($sp)\n' + self.text += f'lw $t0, 16($t0)\n' + + self.text += 'li $a0, 0\n' + self.text += 'count_char:\n' + self.text += 'lb $t1, 0($t0)\n' + self.text += 'beqz $t1, finish_chars_count\n' + self.text += 'addi $t0, $t0, 1\n' + self.text += 'addi $a0, $a0, 1\n' + self.text += 'j count_char\n' + self.text += 'finish_chars_count:\n' + + offset = self.var_offset[self.current_function.name][node.result] + self.text += f'sw $a0, {offset}($sp)\n' + + @visitor.when(cil.ConcatNode) + def visit(self, node): + offset_str1 = self.var_offset[self.current_function.name][node.str1] + offset_len1 = self.var_offset[self.current_function.name][node.len1] + + offset_str2 = self.var_offset[self.current_function.name][node.str2] + offset_len2 = self.var_offset[self.current_function.name][node.len2] + + self.text += f'lw $a0, {offset_len1}($sp)\n' + self.text += f'lw $t0, {offset_len2}($sp)\n' + + self.text += 'add $a0, $a0, $t0\n' + self.text += 'addi $a0, $a0, 1\n' + self.text += f'li $v0, 9\n' + self.text += f'syscall\n' + self.text += 'bge $v0, $sp heap_error\n' + self.text += 'move $t3, $v0\n' + + self.text += f'lw $t0, {offset_str1}($sp)\n' + self.text += f'lw $t1, {offset_str2}($sp)\n' + + self.text += 'copy_str1_char:\n' + self.text += 'lb $t2, 0($t0)\n' + self.text += 'sb $t2, 0($v0)\n' + self.text += 'beqz $t2, concat_str2_char\n' + self.text += 'addi $t0, $t0, 1\n' + self.text += 'addi $v0, $v0, 1\n' + self.text += 'j copy_str1_char\n' + + self.text += 'concat_str2_char:\n' + self.text += 'lb $t2, 0($t1)\n' + self.text += 'sb $t2, 0($v0)\n' + self.text += 'beqz $t2, finish_str2_concat\n' + self.text += 'addi $t1, $t1, 1\n' + self.text += 'addi $v0, $v0, 1\n' + self.text += 'j concat_str2_char\n' + self.text += 'finish_str2_concat:\n' + self.text += 'sb $0, ($v0)\n' + + offset = self.var_offset[self.current_function.name][node.result] + self.text += f'sw $t3, {offset}($sp)\n' + + @visitor.when(cil.SubStringNode) + def visit(self, node): + offset_idx = self.var_offset[self.current_function.name][node.i] + offset_len = self.var_offset[self.current_function.name][node.length] + offset_str = self.var_offset[self.current_function.name][node.string] + + self.text += f'lw $a0, {offset_len}($sp)\n' + self.text += 'addi $a0, $a0, 1\n' + self.text += f'li $v0, 9\n' + self.text += f'syscall\n' + self.text += 'bge $v0, $sp heap_error\n' + + self.text += f'lw $t0, {offset_idx}($sp)\n' + self.text += f'lw $t1, {offset_len}($sp)\n' + self.text += f'lw $t4, {offset_str}($sp)\n' + self.text += f'lw $t2, 16($t4)\n' + + self.text += 'bltz $t0, substr_error\n' + + self.text += 'li $a0, 0\n' + self.text += 'jump_str_char:\n' + self.text += f'beq $a0, $t0, finish_index_jump\n' + self.text += 'addi $a0, $a0, 1\n' + self.text += 'addi $t2, $t2, 1\n' + self.text += 'beq $t2, $zero, substr_error\n' + self.text += 'j jump_str_char\n' + self.text += 'finish_index_jump:\n' + self.text += 'li $a0, 0\n' + self.text += 'move $t3, $v0\n' + + self.text += 'copy_substr_char:\n' + self.text += 'beq $a0, $t1 finish_substr_copy\n' + self.text += 'li $t0, 0\n' + self.text += 'lb $t0, 0($t2)\n' + self.text += 'sb $t0, 0($v0)\n' + self.text += 'addi $t2, $t2, 1\n' + self.text += 'beq $t2, $zero, substr_error\n' + self.text += 'addi $v0, $v0, 1\n' + self.text += 'addi $a0, $a0, 1\n' + self.text += 'j copy_substr_char\n' + self.text += 'finish_substr_copy:\n' + self.text += 'sb $0, ($v0)\n' + + offset = self.var_offset[self.current_function.name][node.result] + self.text += f'sw $t3, {offset}($sp)\n' + + @visitor.when(cil.StringEqualsNode) + def visit(self, node): + offset_str1 = self.var_offset[self.current_function.name][node.s1] + offset_str2 = self.var_offset[self.current_function.name][node.s2] + + self.text += f'lw $t1, {offset_str1}($sp)\n' + self.text += f'lw $t2, {offset_str2}($sp)\n' + + self.text += 'compare_str_char:\n' + self.text += 'li $t3, 0\n' + self.text += 'lb $t3, 0($t1)\n' + self.text += 'li $t4, 0\n' + self.text += 'lb $t4, 0($t2)\n' + self.text += 'seq $a0, $t3, $t4\n' + self.text += 'beqz $a0, finish_compare_str\n' + self.text += 'beqz $t3, finish_compare_str\n' + self.text += 'beqz $t4, finish_compare_str\n' + self.text += 'addi $t1, $t1, 1\n' + self.text += 'addi $t2, $t2, 1\n' + self.text += 'j compare_str_char\n' + self.text += 'finish_compare_str:\n' + + offset = self.var_offset[self.current_function.name][node.result] + self.text += f'sw $a0, {offset}($sp)\n' + + @visitor.when(cil.CopyNode) + def visit(self, node): + self_offset = self.var_offset[self.current_function.name][node.type] + self.text += f'lw $t0, {self_offset}($sp)\n' + self.text += f'lw $a0, 8($t0)\n' + self.text += f'mul $a0, $a0, 4\n' + self.text += f'li $v0, 9\n' + self.text += f'syscall\n' + self.text += 'bge $v0, $sp heap_error\n' + self.text += f'move $t1, $v0\n' + + self.text += 'li $a0, 0\n' + self.text += 'lw $t3, 8($t0)\n' + self.text += 'copy_object_word:\n' + self.text += 'lw $t2, 0($t0)\n' + self.text += 'sw $t2, 0($t1)\n' + self.text += 'addi $t0, $t0, 4\n' + self.text += 'addi $t1, $t1, 4\n' + self.text += 'addi $a0, $a0, 1\n' + + self.text += 'blt $a0, $t3, copy_object_word\n' + offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $v0, {offset}($sp)\n' + + @visitor.when(cil.IsVoidNode) + def visit(self, node): + self.text += 'la $t0, void\n' + offset = self.var_offset[self.current_function.name][node.expr] + self.text += f'lw $t1, {offset}($sp)\n' + self.text += 'seq $a0, $t0, $t1\n' + res_offset = self.var_offset[self.current_function.name][node.result_local] + self.text += f'sw $a0, {res_offset}($sp)\n' + + @visitor.when(cil.HaltNode) + def visit(self, node): + self.text += 'li $v0, 10\n' + self.text += 'syscall\n' + + @visitor.when(cil.PrintIntNode) + def visit(self, node): + if isinstance(node.value, int): + self.text += f'li $v0 , 1\n' + self.text += f'li $a0 , {node.value}\n' + self.text += f'syscall\n' + else: + var_offset = self.var_offset[self.current_function.name][node.value] + self.text += f'li $v0 , 1\n' + self.text += f'lw $a0 , {var_offset}($sp)\n' + self.text += f'syscall\n' + + @visitor.when(cil.PrintStringNode) + def visit(self, node): + var_offset = self.var_offset[self.current_function.name][node.value] + self.text += f'lw $a0, {var_offset}($sp)\n' + self.text += f'li $v0, 4\n' + self.text += f'syscall\n' + + @visitor.when(cil.ReadIntNode) + def visit(self, node): + read_offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'li $v0, 5\n' + self.text += f'syscall\n' + self.text += f'sw $v0, {read_offset}($sp)\n' + + @visitor.when(cil.ReadStringNode) + def visit(self, node): + read_offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'la $a0, temp_string\n' + self.text += f'li $a1, 2048\n' + self.text += f'li $v0, 8\n' + self.text += f'syscall\n' + + self.text += 'move $t0, $a0\n' + self.text += 'jump_read_str_char:\n' + self.text += 'li $t1, 0\n' + self.text += 'lb $t1, 0($t0)\n' + self.text += 'beqz $t1, analize_str_end\n' + self.text += 'addi $t0, $t0, 1\n' + self.text += 'j jump_read_str_char\n' + + self.text += 'analize_str_end:\n' + self.text += 'addi $t0, $t0, -1\n' + self.text += 'li $t1, 0\n' + self.text += 'lb $t1, 0($t0)\n' + self.text += 'bne $t1, 10, finish_jump_read_str_char\n' + self.text += 'sb $0, 0($t0)\n' + self.text += 'addi $t0, $t0, -1\n' + self.text += 'lb $t1, 0($t0)\n' + self.text += 'bne $t1, 13, finish_jump_read_str_char\n' + self.text += 'sb $0, 0($t0)\n' + self.text += 'j analize_str_end\n' + self.text += 'finish_jump_read_str_char:\n' + + self.text += f'sw $a0, {read_offset}($sp)\n' diff --git a/src/code_generator/COOLToCILVisitor.py b/src/code_generator/COOLToCILVisitor.py new file mode 100644 index 000000000..1152b4500 --- /dev/null +++ b/src/code_generator/COOLToCILVisitor.py @@ -0,0 +1,720 @@ +import code_generator.cil_ast as cil +from code_generator.BaseCoolToCilVisitor import BaseCOOLToCILVisitor +from semantic.semantic import Scope, VariableInfo +from utils import visitor +from utils.ast import * + + +class COOLToCILVisitor(BaseCOOLToCILVisitor): + @visitor.on('node') + def visit(self, node, scope): + pass + + @visitor.when(ProgramNode) + def visit(self, node, scope=None): + scope = Scope() + self.current_function = self.register_function('main') + instance = self.define_internal_local(scope=scope, name="instance") + result = self.define_internal_local(scope=scope, name="result") + self.register_instruction(cil.AllocateNode( + 'Main', self.context.get_type('Main').tag, instance)) + self.register_instruction(cil.CallNode( + result, 'Main_init', [cil.ArgNode(instance)], "Main")) + self.register_instruction(cil.CallNode(result, self.to_function_name( + 'main', 'Main'), [cil.ArgNode(instance)], "Main")) + self.register_instruction(cil.ReturnNode(None)) + self.current_function = None + + self.register_data('Abort called from class ') + self.register_data('\n') + self.dotdata['empty_str'] = '' + + # Add built-in types in .TYPES section + self.define_built_in(scope) + + # Add string equals function + self.build_string_equals_function(scope) + + for declaration in node.declarations: + self.visit(declaration, scope.create_child()) + + return cil.ProgramNode(self.dottypes, self.dotdata, self.dotcode) + + @visitor.when(ClassDeclarationNode) + def visit(self, node, scope): + self.current_type = self.context.get_type(node.id) + + # Handle all the .TYPE section + cil_type = self.register_type(self.current_type.name) + cil_type.attributes = [f'{attr.name}' for c, + attr in self.current_type.get_all_attributes()] + cil_type.methods = {f'{m}': f'{c}.{m}' for c, + m in self.current_type.get_all_methods()} + + scope.define_cil_local( + "self", self.current_type.name, self.current_type) + + func_declarations = [ + f for f in node.features if isinstance(f, FuncDeclarationNode)] + attr_declarations = [ + a for a in node.features if isinstance(a, AttrDeclarationNode)] + for attr in attr_declarations: + scope.define_cil_local(attr.id, attr.id, node.id) + + # -------------------------Init--------------------------------- + self.current_function = self.register_function(f'{node.id}_init') + self.register_param(VariableInfo('self', None)) + + # Init parents recursively + result = self.define_internal_local(scope=scope, name="result") + self.register_instruction(cil.CallNode(result, f'{node.parent}_init', [ + cil.ArgNode('self')], node.parent)) + self.register_instruction(cil.ReturnNode(None)) + + for attr in attr_declarations: + self.visit(attr, scope) + # --------------------------------------------------------------- + self.current_function = None + + for func in func_declarations: + self.visit(func, scope.create_child()) + + self.current_type = None + + @visitor.when(FuncDeclarationNode) + def visit(self, node, scope): + self.current_method = self.current_type.get_method(node.id) + self.dottypes[self.current_type.name].methods[ + node.id] = f'{self.current_type.name}.{node.id}' + cil_method_name = self.to_function_name( + node.id, self.current_type.name) + self.current_function = self.register_function(cil_method_name) + + self.register_param(VariableInfo('self', self.current_type)) + for pname, ptype, _, _ in node.params: + self.register_param(VariableInfo(pname, ptype)) + + value = self.visit(node.body, scope) + + self.register_instruction(cil.ReturnNode(value)) + self.current_method = None + + @visitor.when(AttrDeclarationNode) + def visit(self, node, scope): + + if not node.expr is None: + expr = self.visit(node.expr, scope) + self.register_instruction(cil.SetAttrNode( + 'self', node.id, expr, self.current_type.name)) + else: + instance = None + + if node.type in ['Int', 'Bool']: + instance = self.define_internal_local( + scope=scope, name="instance") + self.register_instruction(cil.AllocateNode( + node.type, self.context.get_type(node.type).tag, instance)) + value = self.define_internal_local(scope=scope, name="value") + self.register_instruction(cil.LoadIntNode(0, value)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, f'{node.type}_init', [ + cil.ArgNode(value), cil.ArgNode(instance)], node.type)) + elif node.type == 'String': + instance = self.define_internal_local( + scope=scope, name="instance") + self.register_instruction(cil.AllocateNode( + node.type, self.context.get_type(node.type).tag, instance)) + value = self.define_internal_local(scope=scope, name="value") + self.register_instruction( + cil.LoadStringNode('empty_str', value)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, f'{node.type}_init', [ + cil.ArgNode(value), cil.ArgNode(instance)], node.type)) + + self.register_instruction(cil.SetAttrNode( + 'self', node.id, instance, self.current_type.name)) + + @visitor.when(AssignNode) + def visit(self, node, scope): + expr_local = self.visit(node.expr, scope) + result_local = self.define_internal_local(scope=scope, name="result") + cil_node_name = scope.find_cil_local(node.id) + + if self.is_defined_param(node.id): + self.register_instruction(cil.AssignNode(node.id, expr_local)) + elif self.current_type.has_attr(node.id): + cil_type_name = 'self' + self.register_instruction(cil.SetAttrNode( + cil_type_name, node.id, expr_local, self.current_type.name)) + else: + self.register_instruction( + cil.AssignNode(cil_node_name, expr_local)) + return expr_local + + @visitor.when(ArrobaCallNode) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name="result") + expr_value = self.visit(node.obj, scope) + + call_args = [] + for arg in reversed(node.args): + param_local = self.visit(arg, scope) + call_args.append(cil.ArgNode(param_local)) + call_args.append(cil.ArgNode(expr_value)) + + static_instance = self.define_internal_local( + scope=scope, name='static_instance') + self.register_instruction(cil.AllocateNode( + node.type, self.context.get_type(node.type).tag, static_instance)) + + self.register_instruction(cil.VCallNode( + result_local, node.id, call_args, node.type, static_instance)) + return result_local + + @visitor.when(DotCallNode) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name="result") + expr_value = self.visit(node.obj, scope) + + call_args = [] + for arg in reversed(node.args): + param_local = self.visit(arg, scope) + call_args.append(cil.ArgNode(param_local)) + call_args.append(cil.ArgNode(expr_value)) + + dynamic_type = node.obj.computed_type.name + self.register_instruction(cil.VCallNode( + result_local, node.id, call_args, dynamic_type, expr_value)) + + return result_local + + @visitor.when(MemberCallNode) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name="result") + + call_args = [] + for arg in reversed(node.args): + param_local = self.visit(arg, scope) + call_args.append(cil.ArgNode(param_local)) + call_args.append(cil.ArgNode("self")) + + self.register_instruction(cil.VCallNode( + result_local, node.id, call_args, node.static_type.name, "self")) + + return result_local + + @visitor.when(IfThenElseNode) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name="result") + + cond_value = self.visit(node.condition, scope) + + if_then_label = self.get_label() + self.register_instruction(cil.IfGoToNode(cond_value, if_then_label)) + + else_value = self.visit(node.elseBody, scope) + self.register_instruction(cil.AssignNode(result_local, else_value)) + + end_if_label = self.get_label() + self.register_instruction(cil.GoToNode(end_if_label)) + + self.register_instruction(cil.LabelNode(if_then_label)) + then_value = self.visit(node.ifBody, scope) + self.register_instruction(cil.AssignNode(result_local, then_value)) + self.register_instruction(cil.LabelNode(end_if_label)) + + return result_local + + @visitor.when(WhileNode) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name="result") + + loop_init_label = self.get_label() + loop_body_label = self.get_label() + loop_end_label = self.get_label() + self.register_instruction(cil.LabelNode(loop_init_label)) + pred_value = self.visit(node.condition, scope) + self.register_instruction(cil.IfGoToNode(pred_value, loop_body_label)) + self.register_instruction(cil.GoToNode(loop_end_label)) + + self.register_instruction(cil.LabelNode(loop_body_label)) + body_value = self.visit(node.body, scope) + self.register_instruction(cil.GoToNode(loop_init_label)) + self.register_instruction(cil.LabelNode(loop_end_label)) + + self.register_instruction(cil.LoadVoidNode(result_local)) + return result_local + + @visitor.when(BlockNode) + def visit(self, node, scope): + for expr in node.exprs: + result_local = self.visit(expr, scope) + return result_local + + @visitor.when(LetInNode) + def visit(self, node, scope): + let_scope = scope.create_child() + for var in node.letBody: + self.visit(var, let_scope) + + body_value = self.visit(node.inBody, let_scope) + result_local = self.define_internal_local( + scope=scope, name="let_result") + self.register_instruction(cil.AssignNode(result_local, body_value)) + return result_local + + @visitor.when(VarDeclarationNode) + def visit(self, node, scope): + + if not node.expr is None: + expr_value = self.visit(node.expr, scope) + let_var = self.define_internal_local( + scope=scope, name=node.id, cool_var=node.id) + self.register_instruction(cil.AssignNode(let_var, expr_value)) + + else: + instance = None + + if node.type in ['Int', 'Bool']: + instance = self.define_internal_local( + scope=scope, name="instance") + self.register_instruction(cil.AllocateNode( + node.type, self.context.get_type(node.type).tag, instance)) + value = self.define_internal_local(scope=scope, name="value") + self.register_instruction(cil.LoadIntNode(0, value)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, f'{node.type}_init', [ + cil.ArgNode(value), cil.ArgNode(instance)], node.type)) + elif node.type == 'String': + instance = self.define_internal_local( + scope=scope, name="instance") + self.register_instruction(cil.AllocateNode( + node.type, self.context.get_type(node.type).tag, instance)) + value = self.define_internal_local(scope=scope, name="value") + self.register_instruction( + cil.LoadStringNode('empty_str', value)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, f'{node.type}_init', [ + cil.ArgNode(value), cil.ArgNode(instance)], node.type)) + + let_var = self.define_internal_local( + scope=scope, name=node.id, cool_var=node.id) + self.register_instruction(cil.AssignNode(let_var, instance)) + + return let_var + + @visitor.when(CaseNode) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name="result") + case_expr = self.visit(node.expr, scope) + + exit_label = self.get_label() + label = self.get_label() + + self.register_instruction(cil.CaseNode(case_expr, label)) + + tag_lst = [] + option_dict = {} + for option in node.optionList: + tag = self.context.get_type(option.type).tag + tag_lst.append(tag) + option_dict[tag] = option + tag_lst.sort() + + for t in reversed(tag_lst): + option = option_dict[t] + self.register_instruction(cil.LabelNode(label)) + label = self.get_label() + + option_type = self.context.get_type(option.type) + self.register_instruction(cil.CaseOptionNode( + case_expr, option_type.tag, option_type.max_tag, label)) + + option_scope = scope.create_child() + option_id = self.define_internal_local( + scope=option_scope, name=option.id, cool_var=option.id) + self.register_instruction(cil.AssignNode(option_id, case_expr)) + expr_result = self.visit(option.expr, option_scope) + + self.register_instruction( + cil.AssignNode(result_local, expr_result)) + self.register_instruction(cil.GoToNode(exit_label)) + + self.register_instruction(cil.LabelNode(label)) + self.register_instruction(cil.GoToNode('case_no_match_error')) + self.register_instruction(cil.LabelNode(exit_label)) + return result_local + + @visitor.when(PlusNode) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name="result") + op_local = self.define_internal_local(scope=scope, name="op") + left_local = self.define_internal_local(scope=scope, name="left") + right_local = self.define_internal_local(scope=scope, name="right") + + left_value = self.visit(node.lvalue, scope) + right_value = self.visit(node.rvalue, scope) + + self.register_instruction(cil.GetAttrNode( + left_local, left_value, "value", node.lvalue.computed_type.name)) + self.register_instruction(cil.GetAttrNode( + right_local, right_value, "value", node.rvalue.computed_type.name)) + + self.register_instruction(cil.BinaryOperationNode( + op_local, left_local, right_local, "+")) + + # Allocate Int result + self.register_instruction(cil.AllocateNode( + 'Int', self.context.get_type('Int').tag, result_local)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'Int_init', [ + cil.ArgNode(op_local), cil.ArgNode(result_local)], "Int")) + + return result_local + + @visitor.when(MinusNode) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name="result") + op_local = self.define_internal_local(scope=scope, name="op") + left_local = self.define_internal_local(scope=scope, name="left") + right_local = self.define_internal_local(scope=scope, name="right") + + left_value = self.visit(node.lvalue, scope) + right_value = self.visit(node.rvalue, scope) + + self.register_instruction(cil.GetAttrNode( + left_local, left_value, "value", node.lvalue.computed_type.name)) + self.register_instruction(cil.GetAttrNode( + right_local, right_value, "value", node.rvalue.computed_type.name)) + + self.register_instruction(cil.BinaryOperationNode( + op_local, left_local, right_local, "-")) + + # Allocate Int result + self.register_instruction(cil.AllocateNode( + 'Int', self.context.get_type('Int').tag, result_local)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'Int_init', [ + cil.ArgNode(op_local), cil.ArgNode(result_local)], "Int")) + + return result_local + + @visitor.when(StarNode) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name="result") + op_local = self.define_internal_local(scope=scope, name="op") + left_local = self.define_internal_local(scope=scope, name="left") + right_local = self.define_internal_local(scope=scope, name="right") + + left_value = self.visit(node.lvalue, scope) + right_value = self.visit(node.rvalue, scope) + + self.register_instruction(cil.GetAttrNode( + left_local, left_value, "value", node.lvalue.computed_type.name)) + self.register_instruction(cil.GetAttrNode( + right_local, right_value, "value", node.rvalue.computed_type.name)) + + self.register_instruction(cil.BinaryOperationNode( + op_local, left_local, right_local, "*")) + + # Allocate Int result + self.register_instruction(cil.AllocateNode( + 'Int', self.context.get_type('Int').tag, result_local)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'Int_init', [ + cil.ArgNode(op_local), cil.ArgNode(result_local)], "Int")) + + return result_local + + @visitor.when(DivNode) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name="result") + op_local = self.define_internal_local(scope=scope, name="op") + left_local = self.define_internal_local(scope=scope, name="left") + right_local = self.define_internal_local(scope=scope, name="right") + + left_value = self.visit(node.lvalue, scope) + right_value = self.visit(node.rvalue, scope) + + self.register_instruction(cil.GetAttrNode( + left_local, left_value, "value", node.lvalue.computed_type.name)) + self.register_instruction(cil.GetAttrNode( + right_local, right_value, "value", node.rvalue.computed_type.name)) + + self.register_instruction(cil.BinaryOperationNode( + op_local, left_local, right_local, "/")) + + # Allocate Int result + self.register_instruction(cil.AllocateNode( + 'Int', self.context.get_type('Int').tag, result_local)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'Int_init', [ + cil.ArgNode(op_local), cil.ArgNode(result_local)], "Int")) + + return result_local + + @visitor.when(LessNode) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name="result") + op_local = self.define_internal_local(scope=scope, name="op") + left_local = self.define_internal_local(scope=scope, name="left") + right_local = self.define_internal_local(scope=scope, name="right") + + left_value = self.visit(node.lvalue, scope) + right_value = self.visit(node.rvalue, scope) + + self.register_instruction(cil.GetAttrNode( + left_local, left_value, "value", node.lvalue.computed_type.name)) + self.register_instruction(cil.GetAttrNode( + right_local, right_value, "value", node.rvalue.computed_type.name)) + + self.register_instruction(cil.BinaryOperationNode( + op_local, left_local, right_local, "<")) + + # Allocate Bool result + self.register_instruction(cil.AllocateNode( + 'Bool', self.context.get_type('Bool').tag, result_local)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'Bool_init', [ + cil.ArgNode(op_local), cil.ArgNode(result_local)], "Bool")) + + return result_local + + @visitor.when(LessEqNode) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name="result") + op_local = self.define_internal_local(scope=scope, name="op") + left_local = self.define_internal_local(scope=scope, name="left") + right_local = self.define_internal_local(scope=scope, name="right") + + left_value = self.visit(node.lvalue, scope) + right_value = self.visit(node.rvalue, scope) + + self.register_instruction(cil.GetAttrNode( + left_local, left_value, "value", node.lvalue.computed_type.name)) + self.register_instruction(cil.GetAttrNode( + right_local, right_value, "value", node.rvalue.computed_type.name)) + + self.register_instruction(cil.BinaryOperationNode( + op_local, left_local, right_local, "<=")) + + # Allocate Bool result + self.register_instruction(cil.AllocateNode( + 'Bool', self.context.get_type('Bool').tag, result_local)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'Bool_init', [ + cil.ArgNode(op_local), cil.ArgNode(result_local)], "Bool")) + + return result_local + + @visitor.when(EqualNode) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name="result") + op_local = self.define_internal_local(scope=scope, name="op") + left_local = self.define_internal_local(scope=scope, name="left") + right_local = self.define_internal_local(scope=scope, name="right") + + left_value = self.visit(node.lvalue, scope) + right_value = self.visit(node.rvalue, scope) + + if node.lvalue.computed_type.name == 'String': + self.register_instruction(cil.CallNode(op_local, 'String_equals', [ + cil.ArgNode(right_value), cil.ArgNode(left_value)], 'String')) + + # Allocate Bool result + self.register_instruction(cil.AllocateNode( + 'Bool', self.context.get_type('Bool').tag, result_local)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'Bool_init', [ + cil.ArgNode(op_local), cil.ArgNode(result_local)], "Bool")) + + return result_local + + elif node.lvalue.computed_type.name in ['Int', 'Bool']: + self.register_instruction(cil.GetAttrNode( + left_local, left_value, "value", node.lvalue.computed_type.name)) + self.register_instruction(cil.GetAttrNode( + right_local, right_value, "value", node.rvalue.computed_type.name)) + else: + self.register_instruction(cil.AssignNode(left_local, left_value)) + self.register_instruction(cil.AssignNode(right_local, right_value)) + + self.register_instruction(cil.BinaryOperationNode( + op_local, left_local, right_local, "=")) + + # Allocate Bool result + self.register_instruction(cil.AllocateNode( + 'Bool', self.context.get_type('Bool').tag, result_local)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'Bool_init', [ + cil.ArgNode(op_local), cil.ArgNode(result_local)], "Bool")) + + return result_local + + @visitor.when(NegationNode) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name="result") + op_local = self.define_internal_local(scope=scope, name="op") + expr_local = self.define_internal_local(scope=scope) + + expr_value = self.visit(node.expr, scope) + + self.register_instruction(cil.GetAttrNode( + expr_local, expr_value, "value", node.expr.computed_type.name)) + self.register_instruction( + cil.UnaryOperationNode(op_local, expr_local, "~")) + + # Allocate Int result + self.register_instruction(cil.AllocateNode( + 'Int', self.context.get_type('Int').tag, result_local)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'Int_init', [ + cil.ArgNode(op_local), cil.ArgNode(result_local)], "Int")) + + return result_local + + @visitor.when(LogicNegationNode) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name="result") + op_local = self.define_internal_local(scope=scope, name="op") + expr_local = self.define_internal_local(scope=scope) + + expr_value = self.visit(node.expr, scope) + + self.register_instruction(cil.GetAttrNode( + expr_local, expr_value, "value", node.expr.computed_type.name)) + self.register_instruction( + cil.UnaryOperationNode(op_local, expr_local, "not")) + + # Allocate Bool result + self.register_instruction(cil.AllocateNode( + 'Bool', self.context.get_type('Bool').tag, result_local)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'Bool_init', [ + cil.ArgNode(op_local), cil.ArgNode(result_local)], "Bool")) + + return result_local + + @visitor.when(IsVoidNode) + def visit(self, node, scope): + expr_value = self.visit(node.expr, scope) + result_local = self.define_internal_local( + scope=scope, name="isvoid_result") + self.register_instruction(cil.IsVoidNode(result_local, expr_value)) + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(cil.AllocateNode( + 'Bool', self.context.get_type('Bool').tag, instance)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'Bool_init', [ + cil.ArgNode(result_local), cil.ArgNode(instance)], "Bool")) + return instance + + @visitor.when(IdNode) + def visit(self, node, scope): + if self.is_defined_param(node.id): + return node.id + elif self.current_type.has_attr(node.id): + result_local = self.define_internal_local( + scope=scope, name=node.id, class_type=self.current_type.name) + self.register_instruction(cil.GetAttrNode( + result_local, 'self', node.id, self.current_type.name)) + return result_local + else: + return scope.find_cil_local(node.id) + + @visitor.when(NewNode) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name="result") + result_init = self.define_internal_local(scope=scope, name="init") + + if node.id == "SELF_TYPE": + self.register_instruction(cil.AllocateNode( + self.current_type.name, self.current_type.tag, result_local)) + self.register_instruction(cil.CallNode(result_init, f'{self.current_type.name}_init', [ + result_local], self.current_type.name)) + else: + self.register_instruction(cil.AllocateNode( + node.id, self.context.get_type(node.id).tag, result_local)) + self.register_instruction(cil.CallNode(result_init, f'{node.id}_init', [ + cil.ArgNode(result_local)], self.current_type.name)) + + return result_local + + @visitor.when(IntNode) + def visit(self, node, scope): + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(cil.AllocateNode( + 'Int', self.context.get_type('Int').tag, instance)) + value = self.define_internal_local(scope=scope, name="value") + self.register_instruction(cil.LoadIntNode(node.id, value)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'Int_init', [ + cil.ArgNode(value), cil.ArgNode(instance)], "Int")) + return instance + + @visitor.when(StringNode) + def visit(self, node, scope): + str_name = "" + for s in self.dotdata.keys(): + if self.dotdata[s] == node.id: + str_name = s + break + if str_name == "": + str_name = self.register_data(node.id) + + result_local = self.define_internal_local(scope=scope) + self.register_instruction(cil.LoadStringNode(str_name, result_local)) + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(cil.AllocateNode( + 'String', self.context.get_type('String').tag, instance)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'String_init', [ + cil.ArgNode(result_local), cil.ArgNode(instance)], "String")) + return instance + + @visitor.when(BoolNode) + def visit(self, node, scope): + boolean = 0 + if str(node.id) == "true": + boolean = 1 + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(cil.AllocateNode( + 'Bool', self.context.get_type('Bool').tag, instance)) + value = self.define_internal_local(scope=scope, name="value") + self.register_instruction(cil.LoadIntNode(boolean, value)) + result_init = self.define_internal_local( + scope=scope, name="result_init") + self.register_instruction(cil.CallNode(result_init, 'Bool_init', [ + cil.ArgNode(value), cil.ArgNode(instance)], "Bool")) + return instance + + # @visitor.when(VoidNode) + # def visit(self, node, scope): + # print(node.id) + # result_local = self.define_internal_local(scope=scope) + # self.register_instruction(cil.LoadStringNode(node.id, result_local)) + # instance = self.define_internal_local(scope=scope, name="instance") + # self.register_instruction(cil.AllocateNode( + # 'String', self.context.get_type('String').tag, instance)) + # result_init = self.define_internal_local( + # scope=scope, name="result_init") + # self.register_instruction(cil.CallNode(result_init, 'Void_init', [ + # cil.ArgNode(result_local), cil.ArgNode(instance)], "String")) + # return instance diff --git a/src/code_generator/cil_ast.py b/src/code_generator/cil_ast.py new file mode 100644 index 000000000..09494238a --- /dev/null +++ b/src/code_generator/cil_ast.py @@ -0,0 +1,280 @@ +class Node: + pass + + +class ProgramNode(Node): + def __init__(self, dottypes, dotdata, dotcode): + self.dottypes = dottypes + self.dotdata = dotdata + self.dotcode = dotcode + +# .TYPE + + +class TypeNode(Node): + def __init__(self, name): + self.name = name + self.attributes = [] + self.methods = {} + +# .DATA + + +# class DataNode(Node): +# def __init__(self, vname, value): +# self.name = vname +# self.value = value + +# .CODE + + +class FunctionNode(Node): + def __init__(self, name, params=[], localvars=[], instructions=[]): + self.name = name + self.params = params + self.localvars = localvars + self.instructions = instructions + + +class InstructionNode(Node): + def __init__(self): + pass + + +class ParamNode(InstructionNode): + def __init__(self, name): + self.name = name + + +class LocalNode(InstructionNode): + def __init__(self, name): + self.name = name + + +class AssignNode(InstructionNode): + def __init__(self, local_dest, expr): + self.local_dest = local_dest + self.expr = expr + + +class BinaryOperationNode(InstructionNode): + def __init__(self, local_dest, lvalue, rvalue, op): + self.local_dest = local_dest + self.lvalue = lvalue + self.rvalue = rvalue + self.op = op + + +class UnaryOperationNode(InstructionNode): + def __init__(self, local_dest, expr, op): + self.local_dest = local_dest + self.expr = expr + self.op = op + + +# Attr + + +class GetAttrNode(InstructionNode): + def __init__(self, dest, instance, attr, static_type): + self.local_dest = dest + self.instance = instance + self.attr = attr + self.static_type = static_type + + +class SetAttrNode(InstructionNode): + def __init__(self, instance, attr, value, static_type): + self.instance = instance + self.attr = attr + self.value = value + self.static_type = static_type + + +#Arrays and Strings +class GetIndexNode(InstructionNode): + pass + + +class SetIndexNode(InstructionNode): + pass + +# Memory + + +class AllocateNode(InstructionNode): + def __init__(self, typex, tag, dest): + self.type = typex + self.tag = tag + self.local_dest = dest + + +class ArrayNode(InstructionNode): + pass + + +class TypeOfNode(InstructionNode): + def __init__(self, instance, dest): + self.instance = instance + self.local_dest = dest + +# Jumps + + +class LabelNode(InstructionNode): + def __init__(self, label): + self.label = label + + +class GoToNode(InstructionNode): + def __init__(self, label): + self.label = label + + +class IfGoToNode(InstructionNode): + def __init__(self, condition, label): + self.condition = condition + self.label = label + + +# Dynamic Invocation +class CallNode(InstructionNode): + def __init__(self, local_dest, function, params, static_type): + self.function = function + self.params = params + self.static_type = static_type + self.local_dest = local_dest + + +# Static Invocation +class VCallNode(InstructionNode): + def __init__(self, local_dest, function, params, dynamic_type, instance): + self.function = function + self.params = params + self.dynamic_type = dynamic_type + self.local_dest = local_dest + self.instance = instance + + +# Args +class ArgNode(InstructionNode): + def __init__(self, name): + self.name = name + + +# Return +class ReturnNode(InstructionNode): + def __init__(self, value): + self.value = value + + +# IO +class LoadIntNode(InstructionNode): + def __init__(self, value, dest): + self.value = value + self.local_dest = dest + + +class LoadStringNode(InstructionNode): + def __init__(self, msg, dest): + self.msg = msg + self.local_dest = dest + + +class LoadVoidNode(InstructionNode): + def __init__(self, dest): + self.local_dest = dest + + +class LengthNode(InstructionNode): + def __init__(self, variable, result): + self.variable = variable + self.result = result + + +class ConcatNode(InstructionNode): + def __init__(self, str1, len1, str2, len2, result): + self.str1 = str1 + self.len1 = len1 + self.str2 = str2 + self.len2 = len2 + self.result = result + + +class PrefixNode(InstructionNode): + def __init__(self, dest, string, n): + self.local_dest = dest + self.string = string + self.n = n + + +class SubStringNode(InstructionNode): + def __init__(self, i, length, string, result): + self.i = i + self.length = length + self.string = string + self.result = result + + +class StrNode(InstructionNode): + def __init__(self, dest, value): + self.local_dest = dest + self.value = value + + +class ReadStringNode(InstructionNode): + def __init__(self, dest): + self.local_dest = dest + + +class ReadIntNode(InstructionNode): + def __init__(self, dest): + self.local_dest = dest + + +class PrintStringNode(InstructionNode): + def __init__(self, value): + self.value = value + + +class PrintIntNode(InstructionNode): + def __init__(self, value): + self.value = value + + +class IsVoidNode(InstructionNode): + def __init__(self, result_local, expr): + self.result_local = result_local + self.expr = expr + + +class HaltNode(InstructionNode): + def __init__(self): + pass + + +class CopyNode(InstructionNode): + def __init__(self, typex, local_dest): + self.type = typex + self.local_dest = local_dest + + +class StringEqualsNode(InstructionNode): + def __init__(self, s1, s2, result): + self.s1 = s1 + self.s2 = s2 + self.result = result + + +class CaseNode(InstructionNode): + def __init__(self, expr, first_label): + self.expr = expr + self.first_label = first_label + + +class CaseOptionNode(InstructionNode): + def __init__(self, expr, tag, max_tag, next_label): + self.expr = expr + self.tag = tag + self.max_tag = max_tag + self.next_label = next_label diff --git a/src/coolc.sh b/src/coolc.sh index 3088de4f9..1521cd252 100755 --- a/src/coolc.sh +++ b/src/coolc.sh @@ -5,7 +5,12 @@ OUTPUT_FILE=${INPUT_FILE:0: -2}mips # Si su compilador no lo hace ya, aquí puede imprimir la información de contacto echo "LINEA_CON_NOMBRE_Y_VERSION_DEL_COMPILADOR" # TODO: Recuerde cambiar estas -echo "Copyright (c) 2019: Nombre1, Nombre2, Nombre3" # TODO: líneas a los valores correctos +echo "Copyright (c) 2021: Juan Carlos Casteleiro Wong, Olivia González Peña" # TODO: líneas a los valores correctos + +FILE="main.py" + +# Llamar al compilador +python ${FILE} $INPUT_FILE $OUTPUT_FILE # Llamar al compilador -echo "Compiling $INPUT_FILE into $OUTPUT_FILE" +# echo "Compiling $INPUT_FILE into $OUTPUT_FILE" diff --git a/src/cparser/__init__.py b/src/cparser/__init__.py new file mode 100644 index 000000000..aab02ac7f --- /dev/null +++ b/src/cparser/__init__.py @@ -0,0 +1 @@ +from .parser import CoolParser \ No newline at end of file diff --git a/src/cparser/parser.py b/src/cparser/parser.py new file mode 100644 index 000000000..a4facd99d --- /dev/null +++ b/src/cparser/parser.py @@ -0,0 +1,248 @@ +from ply import yacc +from utils.ast import * +from utils.errors import SyntacticError +from utils.utils import tokens + + +class CoolParser: + def __init__(self, lexer): + self.lexer = lexer + self.tokens = tokens + self.parser = yacc.yacc(start='program', module=self) + self.errors = [] + + precedence = ( + ('right', 'ASSIGN'), + ('right', 'NOT'), + ('nonassoc', 'LESSEQ', 'LESS', 'EQUAL'), + ('left', 'PLUS', 'MINUS'), + ('left', 'STAR', 'DIV'), + ('right', 'ISVOID'), + ('left', 'AT'), + ('left', 'DOT') + ) + + def parse(self, program): + return self.parser.parse(program, self.lexer.lexer) + + def p_program(self, p): + 'program : class_list' + p[0] = ProgramNode(p[1]) + + def p_epsilon(self, p): + 'epsilon :' + pass + + def p_class_list(self, p): + '''class_list : def_class SEMICOLON class_list + | def_class SEMICOLON ''' + p[0] = [p[1]] if len(p) == 3 else [p[1]] + p[3] + + def p_def_class(self, p): + '''def_class : CLASS TYPE LBRACE feature_list RBRACE + | CLASS TYPE INHERITS TYPE LBRACE feature_list RBRACE''' + if len(p) == 8: + p[0] = ClassDeclarationNode(p.slice[2], p[6], p.slice[4]) + else: + p[0] = ClassDeclarationNode(p.slice[2], p[4]) + + def p_feature_list(self, p): + '''feature_list : def_attr SEMICOLON feature_list + | def_func SEMICOLON feature_list + | epsilon''' + p[0] = [p[1]] + p[3] if len(p) == 4 else [] + + def p_def_attr(self, p): + '''def_attr : ID COLON TYPE + | ID COLON TYPE ASSIGN expr''' + if len(p) == 4: + p[0] = AttrDeclarationNode(p.slice[1], p.slice[3]) + else: + p[0] = AttrDeclarationNode(p.slice[1], p.slice[3], p[5]) + + def p_def_func(self, p): + '''def_func : ID LPAREN params RPAREN COLON TYPE LBRACE expr RBRACE''' + p[0] = FuncDeclarationNode(p.slice[1], p[3], p.slice[6], p[8]) + + def p_params(self, p): + '''params : param_list + | param_list_empty''' + p[0] = p[1] + + def p_param_list(self, p): + '''param_list : param + | param COMMA param_list''' + p[0] = [p[1]] if len(p) == 2 else [p[1]] + p[3] + + def p_param_list_empty(self, p): + '''param_list_empty : epsilon''' + p[0] = [] + + def p_param(self, p): + '''param : ID COLON TYPE''' + p[0] = (p.slice[1], p.slice[3]) + + def p_expr_flow(self, p): + '''expr : LET let_attrs IN expr + | CASE expr OF case_list ESAC + | IF expr THEN expr ELSE expr FI + | WHILE expr LOOP expr POOL''' + + if p[1].lower() == 'let': + p[0] = LetInNode(p[2], p[4], p.slice[1]) + elif p[1].lower() == 'case': + p[0] = CaseNode(p[2], p[4], p.slice[1]) + elif p[1].lower() == 'if': + p[0] = IfThenElseNode(p[2], p[4], p[6], p.slice[1]) + elif p[1].lower() == 'while': + p[0] = WhileNode(p[2], p[4], p.slice[1]) + + def p_expr_assign(self, p): + '''expr : ID ASSIGN expr''' + p[0] = AssignNode(p.slice[1], p[3]) + + def p_expr_func_call(self, p): + '''expr : expr AT TYPE DOT ID LPAREN args RPAREN + | expr DOT ID LPAREN args RPAREN + | ID LPAREN args RPAREN''' + if len(p) == 9: + if p[7] is None: + p[7] = [] + p[0] = ArrobaCallNode(p[1], p.slice[5], p[7], p.slice[3]) + + elif len(p) == 7: + if p[5] is None: + p[5] = [] + p[0] = DotCallNode(p[1], p.slice[3], p[5]) + + else: + if p[3] is None: + p[3] = [] + p[0] = MemberCallNode(p.slice[1], p[3]) + + def p_expr_operators_binary(self, p): + '''expr : expr PLUS expr + | expr MINUS expr + | expr STAR expr + | expr DIV expr + | expr LESS expr + | expr LESSEQ expr + | expr EQUAL expr''' + if p[2] == '+': + p[0] = PlusNode(p[1], p[3]) + elif p[2] == '-': + p[0] = MinusNode(p[1], p[3]) + elif p[2] == '*': + p[0] = StarNode(p[1], p[3]) + elif p[2] == '/': + p[0] = DivNode(p[1], p[3]) + elif p[2] == '<': + p[0] = LessNode(p[1], p[3]) + elif p[2] == '<=': + p[0] = LessEqNode(p[1], p[3]) + elif p[2] == '=': + p[0] = EqualNode(p[1], p[3]) + + def p_expr_operators_unary(self, p): + '''expr : NOT expr + | ISVOID expr + | LNOT expr''' + if p[1] == '~': + p[0] = NegationNode(p[2], p.slice[1]) + elif p[1].lower() == 'isvoid': + p[0] = IsVoidNode(p[2], p.slice[1]) + elif p[1].lower() == 'not': + p[0] = LogicNegationNode(p[2], p.slice[1]) + + def p_expr_group(self, p): + '''expr : LPAREN expr RPAREN''' + p[0] = p[2] + + def p_expr_atom(self, p): + '''expr : atom''' + p[0] = p[1] + + def p_let_attrs(self, p): + '''let_attrs : def_var + | def_var COMMA let_attrs''' + p[0] = [p[1]] if len(p) == 2 else [p[1]] + p[3] + + def p_def_var(self, p): + '''def_var : ID COLON TYPE + | ID COLON TYPE ASSIGN expr''' + if len(p) == 4: + p[0] = VarDeclarationNode(p.slice[1], p.slice[3]) + else: + p[0] = VarDeclarationNode(p.slice[1], p.slice[3], p[5]) + + def p_case_list(self, p): + '''case_list : case_option SEMICOLON + | case_option SEMICOLON case_list''' + p[0] = [p[1]] if len(p) == 3 else [p[1]] + p[3] + + def p_case_option(self, p): + '''case_option : ID COLON TYPE ARROW expr''' + p[0] = CaseOptionNode(p.slice[1], p.slice[3], p[5]) + + def p_args(self, p): + '''args : arg_list + | arg_list_empty''' + p[0] = p[1] + + def p_arg_list(self, p): + '''arg_list : expr + | expr COMMA arg_list''' + p[0] = [p[1]] if len(p) == 2 else [p[1]] + p[3] + + def p_arg_list_empty(self, p): + '''arg_list_empty : epsilon''' + p[0] = [] + + def p_atom_int(self, p): + '''atom : INT''' + p[0] = IntNode(p.slice[1]) + + def p_atom_id(self, p): + '''atom : ID''' + p[0] = IdNode(p.slice[1]) + + def p_atom_bool(self, p): + '''atom : TRUE + | FALSE''' + p[0] = BoolNode(p.slice[1]) + + def p_atom_string(self, p): + '''atom : STRING''' + p[0] = StringNode(p.slice[1]) + + def p_atom_new(self, p): + '''atom : NEW TYPE''' + p[0] = NewNode(p.slice[2]) + + def p_atom_block(self, p): + '''atom : block''' + p[0] = p[1] + + def p_block(self, p): + '''block : LBRACE block_list RBRACE''' + p[0] = p[2] + + def p_block_list(self, p): + ''' block_list : expr SEMICOLON + | expr SEMICOLON block_list''' + p[0] = BlockNode([p[1]], p.slice[2]) if len( + p) == 3 else BlockNode([p[1]] + p[3].exprs, p.slice[2]) + + def p_error(self, p): + if p: + self.add_error(p) + else: + self.errors.append(SyntacticError('ERROR at or near EOF', 0, 0)) + + def add_error(self, p): + self.errors.append(SyntacticError( + f'ERROR at or near {p.value}', p.lineno, p.column)) + + def print_error(self): + for error in self.errors: + print(error) diff --git a/src/cparser/parsetab.py b/src/cparser/parsetab.py new file mode 100644 index 000000000..f3846f64d --- /dev/null +++ b/src/cparser/parsetab.py @@ -0,0 +1,90 @@ + +# parsetab.py +# This file is automatically generated. Do not edit. +# pylint: disable=W,C,R +_tabversion = '3.10' + +_lr_method = 'LALR' + +_lr_signature = 'programrightASSIGNrightNOTnonassocLESSEQLESSEQUALleftPLUSMINUSleftSTARDIVrightISVOIDleftATleftDOTARROW ASSIGN AT CASE CLASS COLON COMMA DIV DOT ELSE EQUAL ESAC FALSE FI ID IF IN INHERITS INT ISVOID LBRACE LESS LESSEQ LET LNOT LOOP LPAREN MINUS NEW NOT OF PLUS POOL RBRACE RPAREN SEMICOLON STAR STRING THEN TRUE TYPE WHILEprogram : class_listepsilon :class_list : def_class SEMICOLON class_list\n | def_class SEMICOLON def_class : CLASS TYPE LBRACE feature_list RBRACE\n | CLASS TYPE INHERITS TYPE LBRACE feature_list RBRACEfeature_list : def_attr SEMICOLON feature_list\n | def_func SEMICOLON feature_list\n | epsilondef_attr : ID COLON TYPE\n | ID COLON TYPE ASSIGN exprdef_func : ID LPAREN params RPAREN COLON TYPE LBRACE expr RBRACEparams : param_list\n | param_list_emptyparam_list : param\n | param COMMA param_listparam_list_empty : epsilonparam : ID COLON TYPEexpr : LET let_attrs IN expr\n | CASE expr OF case_list ESAC\n | IF expr THEN expr ELSE expr FI\n | WHILE expr LOOP expr POOLexpr : ID ASSIGN exprexpr : expr AT TYPE DOT ID LPAREN args RPAREN\n | expr DOT ID LPAREN args RPAREN\n | ID LPAREN args RPARENexpr : expr PLUS expr\n | expr MINUS expr\n | expr STAR expr\n | expr DIV expr\n | expr LESS expr\n | expr LESSEQ expr\n | expr EQUAL exprexpr : NOT expr\n | ISVOID expr\n | LNOT exprexpr : LPAREN expr RPARENexpr : atomlet_attrs : def_var\n | def_var COMMA let_attrsdef_var : ID COLON TYPE\n | ID COLON TYPE ASSIGN exprcase_list : case_option SEMICOLON\n | case_option SEMICOLON case_listcase_option : ID COLON TYPE ARROW exprargs : arg_list\n | arg_list_emptyarg_list : expr\n | expr COMMA arg_listarg_list_empty : epsilonatom : INTatom : IDatom : TRUE\n | FALSEatom : STRINGatom : NEW TYPEatom : blockblock : LBRACE block_list RBRACE block_list : expr SEMICOLON\n | expr SEMICOLON block_list' + +_lr_action_items = {'CLASS':([0,5,],[4,4,]),'$end':([1,2,5,7,],[0,-1,-4,-3,]),'SEMICOLON':([3,11,12,16,24,36,37,38,47,48,49,50,51,53,76,77,78,79,81,83,91,92,93,94,95,96,97,104,105,108,112,116,126,130,131,133,140,141,142,],[5,17,18,-5,-10,-6,-52,-11,-38,-51,-53,-54,-55,-57,-34,-35,-36,-56,106,-23,-27,-28,-29,-30,-31,-32,-33,-37,-58,-26,-19,127,-20,-22,-12,-25,-21,-24,-45,]),'TYPE':([4,9,19,33,52,56,60,100,128,],[6,15,24,55,79,82,89,114,136,]),'LBRACE':([6,15,32,40,41,42,43,44,45,46,54,58,59,62,63,64,65,66,67,68,82,98,102,103,106,107,109,111,125,129,132,139,],[8,21,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,107,54,54,54,54,54,54,54,54,54,54,54,]),'INHERITS':([6,],[9,]),'ID':([8,17,18,20,21,32,35,39,40,41,42,43,44,45,46,54,58,59,61,62,63,64,65,66,67,68,98,99,101,102,103,106,107,109,110,111,125,127,129,132,139,],[14,14,14,25,14,37,25,71,37,37,37,37,37,37,37,37,37,37,90,37,37,37,37,37,37,37,37,71,117,37,37,37,37,37,123,37,37,117,37,37,37,]),'RBRACE':([8,10,13,17,18,21,22,23,31,37,47,48,49,50,51,53,76,77,78,79,80,83,91,92,93,94,95,96,97,104,105,106,108,112,120,121,126,130,133,140,141,],[-2,16,-9,-2,-2,-2,-7,-8,36,-52,-38,-51,-53,-54,-55,-57,-34,-35,-36,-56,105,-23,-27,-28,-29,-30,-31,-32,-33,-37,-58,-59,-26,-19,-60,131,-20,-22,-25,-21,-24,]),'COLON':([14,25,34,71,117,],[19,33,56,100,128,]),'LPAREN':([14,32,37,40,41,42,43,44,45,46,54,58,59,62,63,64,65,66,67,68,90,98,102,103,106,107,109,111,123,125,129,132,139,],[20,43,59,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,111,43,43,43,43,43,43,43,132,43,43,43,43,]),'RPAREN':([20,26,27,28,29,30,37,47,48,49,50,51,53,55,57,59,75,76,77,78,79,83,84,85,86,87,88,91,92,93,94,95,96,97,104,105,108,111,112,122,124,126,130,132,133,138,140,141,],[-2,34,-13,-14,-15,-17,-52,-38,-51,-53,-54,-55,-57,-18,-16,-2,104,-34,-35,-36,-56,-23,108,-46,-47,-48,-50,-27,-28,-29,-30,-31,-32,-33,-37,-58,-26,-2,-19,-49,133,-20,-22,-2,-25,141,-21,-24,]),'ASSIGN':([24,37,114,],[32,58,125,]),'COMMA':([29,37,47,48,49,50,51,53,55,70,76,77,78,79,83,87,91,92,93,94,95,96,97,104,105,108,112,114,126,130,133,134,140,141,],[35,-52,-38,-51,-53,-54,-55,-57,-18,99,-34,-35,-36,-56,-23,109,-27,-28,-29,-30,-31,-32,-33,-37,-58,-26,-19,-41,-20,-22,-25,-42,-21,-24,]),'LET':([32,40,41,42,43,44,45,46,54,58,59,62,63,64,65,66,67,68,98,102,103,106,107,109,111,125,129,132,139,],[39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,]),'CASE':([32,40,41,42,43,44,45,46,54,58,59,62,63,64,65,66,67,68,98,102,103,106,107,109,111,125,129,132,139,],[40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,]),'IF':([32,40,41,42,43,44,45,46,54,58,59,62,63,64,65,66,67,68,98,102,103,106,107,109,111,125,129,132,139,],[41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,]),'WHILE':([32,40,41,42,43,44,45,46,54,58,59,62,63,64,65,66,67,68,98,102,103,106,107,109,111,125,129,132,139,],[42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,]),'NOT':([32,40,41,42,43,44,45,46,54,58,59,62,63,64,65,66,67,68,98,102,103,106,107,109,111,125,129,132,139,],[44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,]),'ISVOID':([32,40,41,42,43,44,45,46,54,58,59,62,63,64,65,66,67,68,98,102,103,106,107,109,111,125,129,132,139,],[45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,]),'LNOT':([32,40,41,42,43,44,45,46,54,58,59,62,63,64,65,66,67,68,98,102,103,106,107,109,111,125,129,132,139,],[46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,]),'INT':([32,40,41,42,43,44,45,46,54,58,59,62,63,64,65,66,67,68,98,102,103,106,107,109,111,125,129,132,139,],[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,]),'TRUE':([32,40,41,42,43,44,45,46,54,58,59,62,63,64,65,66,67,68,98,102,103,106,107,109,111,125,129,132,139,],[49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,]),'FALSE':([32,40,41,42,43,44,45,46,54,58,59,62,63,64,65,66,67,68,98,102,103,106,107,109,111,125,129,132,139,],[50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,]),'STRING':([32,40,41,42,43,44,45,46,54,58,59,62,63,64,65,66,67,68,98,102,103,106,107,109,111,125,129,132,139,],[51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,]),'NEW':([32,40,41,42,43,44,45,46,54,58,59,62,63,64,65,66,67,68,98,102,103,106,107,109,111,125,129,132,139,],[52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,]),'AT':([37,38,47,48,49,50,51,53,72,73,74,75,76,77,78,79,81,83,87,91,92,93,94,95,96,97,104,105,108,112,118,119,121,126,130,133,134,137,140,141,142,],[-52,60,-38,-51,-53,-54,-55,-57,60,60,60,60,60,60,60,-56,60,60,60,60,60,60,60,60,60,60,-37,-58,-26,60,60,60,60,-20,-22,-25,60,60,-21,-24,60,]),'DOT':([37,38,47,48,49,50,51,53,72,73,74,75,76,77,78,79,81,83,87,89,91,92,93,94,95,96,97,104,105,108,112,118,119,121,126,130,133,134,137,140,141,142,],[-52,61,-38,-51,-53,-54,-55,-57,61,61,61,61,61,61,61,-56,61,61,61,110,61,61,61,61,61,61,61,-37,-58,-26,61,61,61,61,-20,-22,-25,61,61,-21,-24,61,]),'PLUS':([37,38,47,48,49,50,51,53,72,73,74,75,76,77,78,79,81,83,87,91,92,93,94,95,96,97,104,105,108,112,118,119,121,126,130,133,134,137,140,141,142,],[-52,62,-38,-51,-53,-54,-55,-57,62,62,62,62,62,-35,62,-56,62,62,62,-27,-28,-29,-30,62,62,62,-37,-58,-26,62,62,62,62,-20,-22,-25,62,62,-21,-24,62,]),'MINUS':([37,38,47,48,49,50,51,53,72,73,74,75,76,77,78,79,81,83,87,91,92,93,94,95,96,97,104,105,108,112,118,119,121,126,130,133,134,137,140,141,142,],[-52,63,-38,-51,-53,-54,-55,-57,63,63,63,63,63,-35,63,-56,63,63,63,-27,-28,-29,-30,63,63,63,-37,-58,-26,63,63,63,63,-20,-22,-25,63,63,-21,-24,63,]),'STAR':([37,38,47,48,49,50,51,53,72,73,74,75,76,77,78,79,81,83,87,91,92,93,94,95,96,97,104,105,108,112,118,119,121,126,130,133,134,137,140,141,142,],[-52,64,-38,-51,-53,-54,-55,-57,64,64,64,64,64,-35,64,-56,64,64,64,64,64,-29,-30,64,64,64,-37,-58,-26,64,64,64,64,-20,-22,-25,64,64,-21,-24,64,]),'DIV':([37,38,47,48,49,50,51,53,72,73,74,75,76,77,78,79,81,83,87,91,92,93,94,95,96,97,104,105,108,112,118,119,121,126,130,133,134,137,140,141,142,],[-52,65,-38,-51,-53,-54,-55,-57,65,65,65,65,65,-35,65,-56,65,65,65,65,65,-29,-30,65,65,65,-37,-58,-26,65,65,65,65,-20,-22,-25,65,65,-21,-24,65,]),'LESS':([37,38,47,48,49,50,51,53,72,73,74,75,76,77,78,79,81,83,87,91,92,93,94,95,96,97,104,105,108,112,118,119,121,126,130,133,134,137,140,141,142,],[-52,66,-38,-51,-53,-54,-55,-57,66,66,66,66,66,-35,66,-56,66,66,66,-27,-28,-29,-30,None,None,None,-37,-58,-26,66,66,66,66,-20,-22,-25,66,66,-21,-24,66,]),'LESSEQ':([37,38,47,48,49,50,51,53,72,73,74,75,76,77,78,79,81,83,87,91,92,93,94,95,96,97,104,105,108,112,118,119,121,126,130,133,134,137,140,141,142,],[-52,67,-38,-51,-53,-54,-55,-57,67,67,67,67,67,-35,67,-56,67,67,67,-27,-28,-29,-30,None,None,None,-37,-58,-26,67,67,67,67,-20,-22,-25,67,67,-21,-24,67,]),'EQUAL':([37,38,47,48,49,50,51,53,72,73,74,75,76,77,78,79,81,83,87,91,92,93,94,95,96,97,104,105,108,112,118,119,121,126,130,133,134,137,140,141,142,],[-52,68,-38,-51,-53,-54,-55,-57,68,68,68,68,68,-35,68,-56,68,68,68,-27,-28,-29,-30,None,None,None,-37,-58,-26,68,68,68,68,-20,-22,-25,68,68,-21,-24,68,]),'OF':([37,47,48,49,50,51,53,72,76,77,78,79,83,91,92,93,94,95,96,97,104,105,108,112,126,130,133,140,141,],[-52,-38,-51,-53,-54,-55,-57,101,-34,-35,-36,-56,-23,-27,-28,-29,-30,-31,-32,-33,-37,-58,-26,-19,-20,-22,-25,-21,-24,]),'THEN':([37,47,48,49,50,51,53,73,76,77,78,79,83,91,92,93,94,95,96,97,104,105,108,112,126,130,133,140,141,],[-52,-38,-51,-53,-54,-55,-57,102,-34,-35,-36,-56,-23,-27,-28,-29,-30,-31,-32,-33,-37,-58,-26,-19,-20,-22,-25,-21,-24,]),'LOOP':([37,47,48,49,50,51,53,74,76,77,78,79,83,91,92,93,94,95,96,97,104,105,108,112,126,130,133,140,141,],[-52,-38,-51,-53,-54,-55,-57,103,-34,-35,-36,-56,-23,-27,-28,-29,-30,-31,-32,-33,-37,-58,-26,-19,-20,-22,-25,-21,-24,]),'ELSE':([37,47,48,49,50,51,53,76,77,78,79,83,91,92,93,94,95,96,97,104,105,108,112,118,126,130,133,140,141,],[-52,-38,-51,-53,-54,-55,-57,-34,-35,-36,-56,-23,-27,-28,-29,-30,-31,-32,-33,-37,-58,-26,-19,129,-20,-22,-25,-21,-24,]),'POOL':([37,47,48,49,50,51,53,76,77,78,79,83,91,92,93,94,95,96,97,104,105,108,112,119,126,130,133,140,141,],[-52,-38,-51,-53,-54,-55,-57,-34,-35,-36,-56,-23,-27,-28,-29,-30,-31,-32,-33,-37,-58,-26,-19,130,-20,-22,-25,-21,-24,]),'IN':([37,47,48,49,50,51,53,69,70,76,77,78,79,83,91,92,93,94,95,96,97,104,105,108,112,113,114,126,130,133,134,140,141,],[-52,-38,-51,-53,-54,-55,-57,98,-39,-34,-35,-36,-56,-23,-27,-28,-29,-30,-31,-32,-33,-37,-58,-26,-19,-40,-41,-20,-22,-25,-42,-21,-24,]),'FI':([37,47,48,49,50,51,53,76,77,78,79,83,91,92,93,94,95,96,97,104,105,108,112,126,130,133,137,140,141,],[-52,-38,-51,-53,-54,-55,-57,-34,-35,-36,-56,-23,-27,-28,-29,-30,-31,-32,-33,-37,-58,-26,-19,-20,-22,-25,140,-21,-24,]),'ESAC':([115,127,135,],[126,-43,-44,]),'ARROW':([136,],[139,]),} + +_lr_action = {} +for _k, _v in _lr_action_items.items(): + for _x,_y in zip(_v[0],_v[1]): + if not _x in _lr_action: _lr_action[_x] = {} + _lr_action[_x][_k] = _y +del _lr_action_items + +_lr_goto_items = {'program':([0,],[1,]),'class_list':([0,5,],[2,7,]),'def_class':([0,5,],[3,3,]),'feature_list':([8,17,18,21,],[10,22,23,31,]),'def_attr':([8,17,18,21,],[11,11,11,11,]),'def_func':([8,17,18,21,],[12,12,12,12,]),'epsilon':([8,17,18,20,21,59,111,132,],[13,13,13,30,13,88,88,88,]),'params':([20,],[26,]),'param_list':([20,35,],[27,57,]),'param_list_empty':([20,],[28,]),'param':([20,35,],[29,29,]),'expr':([32,40,41,42,43,44,45,46,54,58,59,62,63,64,65,66,67,68,98,102,103,106,107,109,111,125,129,132,139,],[38,72,73,74,75,76,77,78,81,83,87,91,92,93,94,95,96,97,112,118,119,81,121,87,87,134,137,87,142,]),'atom':([32,40,41,42,43,44,45,46,54,58,59,62,63,64,65,66,67,68,98,102,103,106,107,109,111,125,129,132,139,],[47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,]),'block':([32,40,41,42,43,44,45,46,54,58,59,62,63,64,65,66,67,68,98,102,103,106,107,109,111,125,129,132,139,],[53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,]),'let_attrs':([39,99,],[69,113,]),'def_var':([39,99,],[70,70,]),'block_list':([54,106,],[80,120,]),'args':([59,111,132,],[84,124,138,]),'arg_list':([59,109,111,132,],[85,122,85,85,]),'arg_list_empty':([59,111,132,],[86,86,86,]),'case_list':([101,127,],[115,135,]),'case_option':([101,127,],[116,116,]),} + +_lr_goto = {} +for _k, _v in _lr_goto_items.items(): + for _x, _y in zip(_v[0], _v[1]): + if not _x in _lr_goto: _lr_goto[_x] = {} + _lr_goto[_x][_k] = _y +del _lr_goto_items +_lr_productions = [ + ("S' -> program","S'",1,None,None,None), + ('program -> class_list','program',1,'p_program','parser.py',39), + ('epsilon -> ','epsilon',0,'p_epsilon','parser.py',43), + ('class_list -> def_class SEMICOLON class_list','class_list',3,'p_class_list','parser.py',47), + ('class_list -> def_class SEMICOLON','class_list',2,'p_class_list','parser.py',48), + ('def_class -> CLASS TYPE LBRACE feature_list RBRACE','def_class',5,'p_def_class','parser.py',52), + ('def_class -> CLASS TYPE INHERITS TYPE LBRACE feature_list RBRACE','def_class',7,'p_def_class','parser.py',53), + ('feature_list -> def_attr SEMICOLON feature_list','feature_list',3,'p_feature_list','parser.py',60), + ('feature_list -> def_func SEMICOLON feature_list','feature_list',3,'p_feature_list','parser.py',61), + ('feature_list -> epsilon','feature_list',1,'p_feature_list','parser.py',62), + ('def_attr -> ID COLON TYPE','def_attr',3,'p_def_attr','parser.py',66), + ('def_attr -> ID COLON TYPE ASSIGN expr','def_attr',5,'p_def_attr','parser.py',67), + ('def_func -> ID LPAREN params RPAREN COLON TYPE LBRACE expr RBRACE','def_func',9,'p_def_func','parser.py',74), + ('params -> param_list','params',1,'p_params','parser.py',78), + ('params -> param_list_empty','params',1,'p_params','parser.py',79), + ('param_list -> param','param_list',1,'p_param_list','parser.py',83), + ('param_list -> param COMMA param_list','param_list',3,'p_param_list','parser.py',84), + ('param_list_empty -> epsilon','param_list_empty',1,'p_param_list_empty','parser.py',88), + ('param -> ID COLON TYPE','param',3,'p_param','parser.py',92), + ('expr -> LET let_attrs IN expr','expr',4,'p_expr_flow','parser.py',96), + ('expr -> CASE expr OF case_list ESAC','expr',5,'p_expr_flow','parser.py',97), + ('expr -> IF expr THEN expr ELSE expr FI','expr',7,'p_expr_flow','parser.py',98), + ('expr -> WHILE expr LOOP expr POOL','expr',5,'p_expr_flow','parser.py',99), + ('expr -> ID ASSIGN expr','expr',3,'p_expr_assign','parser.py',111), + ('expr -> expr AT TYPE DOT ID LPAREN args RPAREN','expr',8,'p_expr_func_call','parser.py',115), + ('expr -> expr DOT ID LPAREN args RPAREN','expr',6,'p_expr_func_call','parser.py',116), + ('expr -> ID LPAREN args RPAREN','expr',4,'p_expr_func_call','parser.py',117), + ('expr -> expr PLUS expr','expr',3,'p_expr_operators_binary','parser.py',134), + ('expr -> expr MINUS expr','expr',3,'p_expr_operators_binary','parser.py',135), + ('expr -> expr STAR expr','expr',3,'p_expr_operators_binary','parser.py',136), + ('expr -> expr DIV expr','expr',3,'p_expr_operators_binary','parser.py',137), + ('expr -> expr LESS expr','expr',3,'p_expr_operators_binary','parser.py',138), + ('expr -> expr LESSEQ expr','expr',3,'p_expr_operators_binary','parser.py',139), + ('expr -> expr EQUAL expr','expr',3,'p_expr_operators_binary','parser.py',140), + ('expr -> NOT expr','expr',2,'p_expr_operators_unary','parser.py',157), + ('expr -> ISVOID expr','expr',2,'p_expr_operators_unary','parser.py',158), + ('expr -> LNOT expr','expr',2,'p_expr_operators_unary','parser.py',159), + ('expr -> LPAREN expr RPAREN','expr',3,'p_expr_group','parser.py',168), + ('expr -> atom','expr',1,'p_expr_atom','parser.py',172), + ('let_attrs -> def_var','let_attrs',1,'p_let_attrs','parser.py',176), + ('let_attrs -> def_var COMMA let_attrs','let_attrs',3,'p_let_attrs','parser.py',177), + ('def_var -> ID COLON TYPE','def_var',3,'p_def_var','parser.py',181), + ('def_var -> ID COLON TYPE ASSIGN expr','def_var',5,'p_def_var','parser.py',182), + ('case_list -> case_option SEMICOLON','case_list',2,'p_case_list','parser.py',189), + ('case_list -> case_option SEMICOLON case_list','case_list',3,'p_case_list','parser.py',190), + ('case_option -> ID COLON TYPE ARROW expr','case_option',5,'p_case_option','parser.py',194), + ('args -> arg_list','args',1,'p_args','parser.py',198), + ('args -> arg_list_empty','args',1,'p_args','parser.py',199), + ('arg_list -> expr','arg_list',1,'p_arg_list','parser.py',203), + ('arg_list -> expr COMMA arg_list','arg_list',3,'p_arg_list','parser.py',204), + ('arg_list_empty -> epsilon','arg_list_empty',1,'p_arg_list_empty','parser.py',208), + ('atom -> INT','atom',1,'p_atom_int','parser.py',212), + ('atom -> ID','atom',1,'p_atom_id','parser.py',216), + ('atom -> TRUE','atom',1,'p_atom_bool','parser.py',220), + ('atom -> FALSE','atom',1,'p_atom_bool','parser.py',221), + ('atom -> STRING','atom',1,'p_atom_string','parser.py',225), + ('atom -> NEW TYPE','atom',2,'p_atom_new','parser.py',229), + ('atom -> block','atom',1,'p_atom_block','parser.py',233), + ('block -> LBRACE block_list RBRACE','block',3,'p_block','parser.py',237), + ('block_list -> expr SEMICOLON','block_list',2,'p_block_list','parser.py',241), + ('block_list -> expr SEMICOLON block_list','block_list',3,'p_block_list','parser.py',242), +] diff --git a/src/lexer/__init__.py b/src/lexer/__init__.py new file mode 100644 index 000000000..4cc27177f --- /dev/null +++ b/src/lexer/__init__.py @@ -0,0 +1 @@ +from .lexer import CoolLexer \ No newline at end of file diff --git a/src/lexer/lexer.py b/src/lexer/lexer.py new file mode 100644 index 000000000..bb626369e --- /dev/null +++ b/src/lexer/lexer.py @@ -0,0 +1,281 @@ +from ply import lex + +from utils.errors import LexicographicError +from utils.utils import Token, tokens, reserved + + +class CoolLexer: + + states = ( + ('comments', 'exclusive'), + ('strings', 'exclusive') + ) + + def __init__(self, **kwargs): + self.errors = [] + self.reserved = reserved + self.tokens = tokens + self.lexer = lex.lex(module=self, **kwargs) + self.lexer.lineno = 1 + self.lexer.linestart = 0 + self.text = None + + # Comments + + def t_comment(self, t): + r'--.*($|\n)' + t.lexer.lineno += 1 + t.lexer.linestart = t.lexer.lexpos + + def t_comments(self, t): + r'\(\*' + t.lexer.level = 1 + t.lexer.begin('comments') + + def t_comments_open(self, t): + r'\(\*' + t.lexer.level += 1 + + def t_comments_close(self, t): + r'\*\)' + t.lexer.level -= 1 + if t.lexer.level == 0: + t.lexer.begin('INITIAL') + + def t_comments_newline(self, t): + r'\n+' + t.lexer.lineno += len(t.value) + t.lexer.linestart = t.lexer.lexpos + + def t_comments_error(self, t): + t.lexer.skip(1) + + def t_comments_eof(self, t): + self.compute_column(t) + if t.lexer.level > 0: + self.errors.append(LexicographicError( + "EOF in comment", t.lineno, t.column)) + + t_comments_ignore = ' \t\f\r\t\v' + + # Strings + t_strings_ignore = '' + + def t_strings(self, t): + r'\"' + t.lexer.string_start = t.lexer.lexpos + t.lexer.string = '' + t.lexer.backslash = False + t.lexer.begin('strings') + + def t_strings_end(self, t): + r'\"' + self.compute_column(t) + + if t.lexer.backslash: + t.lexer.string += '"' + t.lexer.backslash = False + else: + t.value = t.lexer.string + t.type = 'STRING' + t.lexer.begin('INITIAL') + return t + + def t_strings_newline(self, t): + r'\n' + t.lexer.lineno += 1 + self.compute_column(t) + + t.lexer.linestart = t.lexer.lexpos + + if not t.lexer.backslash: + self.errors.append(LexicographicError( + 'Undeterminated string constant', t.lineno, t.column)) + t.lexer.begin('INITIAL') + + def t_strings_nill(self, t): + r'\0' + self.compute_column(t) + self.errors.append(LexicographicError( + 'Null caracter in string', t.lineno, t.column)) + + def t_strings_consume(self, t): + r'[^\n]' + + if t.lexer.backslash: + if t.value == 'b': + t.lexer.string += '\b' + elif t.value == 't': + t.lexer.string += '\t' + elif t.value == 'f': + t.lexer.string += '\f' + elif t.value == 'n': + t.lexer.string += '\n' + elif t.value == '\\': + t.lexer.string += '\\' + else: + t.lexer.string += t.value + + t.lexer.backslash = False + else: + if t.value != '\\': + t.lexer.string += t.value + else: + t.lexer.backslash = True + + def t_strings_error(self, t): + pass + + def t_strings_eof(self, t): + self.compute_column(t) + self.errors.append(LexicographicError( + 'EOF in string constant', t.lineno, t.column)) + + t_ignore = ' \t\f\r\t\v' + + def compute_column(self, t): + t.column = t.lexpos - t.lexer.linestart + 1 + + def t_LPAREN(self, t): + r'\(' + self.compute_column(t) + return t + + def t_RPAREN(self, t): + r'\)' + self.compute_column(t) + return t + + def t_LBRACE(self, t): + r'\{' + self.compute_column(t) + return t + + def t_RBRACE(self, t): + r'\}' + self.compute_column(t) + return t + + def t_COLON(self, t): + r':' + self.compute_column(t) + return t + + def t_SEMICOLON(self, t): + r';' + self.compute_column(t) + return t + + def t_COMMA(self, t): + r',' + self.compute_column(t) + return t + + def t_DOT(self, t): + r'\.' + self.compute_column(t) + return t + + def t_AT(self, t): + r'@' + self.compute_column(t) + return t + + def t_ASSIGN(self, t): + r'<-' + self.compute_column(t) + return t + + def t_PLUS(self, t): + r'\+' + self.compute_column(t) + return t + + def t_MINUS(self, t): + r'-' + self.compute_column(t) + return t + + def t_STAR(self, t): + r'\*' + self.compute_column(t) + return t + + def t_DIV(self, t): + r'/' + self.compute_column(t) + return t + + def t_ARROW(self, t): + r'=>' + self.compute_column(t) + return t + + def t_EQUAL(self, t): + r'=' + self.compute_column(t) + return t + + def t_LESSEQ(self, t): + r'<=' + self.compute_column(t) + return t + + def t_LESS(self, t): + r'<' + self.compute_column(t) + return t + + def t_NOT(self, t): + r'~' + self.compute_column(t) + return t + + def t_INT(self, t): + r'\d+' + t.value = int(t.value) + self.compute_column(t) + return t + + def t_ID(self, t): + r'[a-z][a-zA-Z_0-9]*' + t.type = self.reserved.get(t.value.lower(), 'ID') + self.compute_column(t) + return t + + def t_TYPE(self, t): + r'[A-Z][a-zA-Z_0-9]*' + t.type = self.reserved.get(t.value.lower(), 'TYPE') + self.compute_column(t) + return t + + def t_newline(self, t): + r'\n+' + t.lexer.lineno += len(t.value) + t.lexer.linestart = t.lexer.lexpos + + def t_error(self, t): + self.compute_column(t) + self.errors.append(LexicographicError( + f'ERROR \"{t.value[0]}\"', t.lineno, t.column)) + t.lexer.skip(1) + + def tokenize(self, text): + self.text = text + self.lexer.input(text) + tokens = [] + for token in self.lexer: + tokens.append(Token(token.value, token.type, + token.lineno, token.column)) + self.lexer.lineno = 1 + self.lexer.linestart = 0 + return tokens + + def run(self, text): + tokens = self.tokenize(text) + if self.errors: + for error in self.errors: + print(error) + raise Exception() + + return tokens diff --git a/src/main.py b/src/main.py new file mode 100644 index 000000000..8604aba19 --- /dev/null +++ b/src/main.py @@ -0,0 +1,66 @@ +import sys + +from lexer import CoolLexer +from cparser import CoolParser +from semantic.visitors.typeBuilder import TypeBuilder +from semantic.visitors.typeChecker import TypeChecker +from semantic.visitors.typeCollector import TypeCollector +from semantic.visitors.varCollector import VarCollector +from code_generator.COOLToCILVisitor import COOLToCILVisitor +from code_generator.CILToMIPSVisitor import CILToMIPSVisitor + + +def main(_input, _output): + + with open(_input) as file: + text = file.read() + + # Lexer + lexer = CoolLexer() + tokens = lexer.run(text) + + # Parser + parser = CoolParser(lexer) + ast = parser.parse(text) + if parser.errors: + parser.print_error() + raise Exception() + + # Semantic + semanticErrors = [] + typeCollector = TypeCollector(semanticErrors) + typeCollector.visit(ast) + + context = typeCollector.context + typeBuilder = TypeBuilder(context, semanticErrors) + typeBuilder.visit(ast) + + varCollector = VarCollector(context, semanticErrors) + scope = varCollector.visit(ast) + + typeChecker = TypeChecker(context, semanticErrors) + typeChecker.visit(ast, scope) + + if semanticErrors: + for error in semanticErrors: + print(error) + raise Exception() + + # Code Generation + coolToCIL = COOLToCILVisitor(context) + cilAST = coolToCIL.visit(ast, scope) + + cilToMIPS = CILToMIPSVisitor() + mips_code = cilToMIPS.visit(cilAST) + + with open(_output, 'w+') as f: + f.write(mips_code) + +if __name__ == "__main__": + + in_path = '/home/cwjki/Projects/cool-compiler-2021/tests/codegen/arith.cl' + out_path = '/home/cwjki/Projects/cool-compiler-2021/src/codeMips.mips' + _input = sys.argv[1] if len(sys.argv) > 1 else in_path + _output = sys.argv[2] if len(sys.argv) > 2 else out_path + + main(_input, _output) diff --git a/src/semantic/__init__.py b/src/semantic/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/semantic/semantic.py b/src/semantic/semantic.py new file mode 100644 index 000000000..c2dcc8d75 --- /dev/null +++ b/src/semantic/semantic.py @@ -0,0 +1,517 @@ +from utils.errors import SemanticError +from collections import OrderedDict +import itertools as itt + + +class Attribute: + def __init__(self, name, typex, index, tok=None): + self.name = name + self.type = typex + self.index = index + self.expr = None + + def __str__(self): + return f'[attrib] {self.name} : {self.type.name};' + + def __repr__(self): + return str(self) + + +class Method: + def __init__(self, name, param_names, params_types, return_type): + self.name = name + self.param_names = param_names + self.param_types = params_types + self.return_type = return_type + + def __str__(self): + params = ', '.join(f'{n}:{t.name}' for n, t in zip( + self.param_names, self.param_types)) + return f'[method] {self.name}({params}): {self.return_type.name};' + + def __eq__(self, other): + return other.name == self.name and \ + other.return_type == self.return_type and \ + other.param_types == self.param_types + + +class MethodError(Method): + def __init__(self, name, param_names, param_types, return_types): + super().__init__(name, param_names, param_types, return_types) + + def __str__(self): + return f'[method] {self.name} ERROR' + + +class Type: + def __init__(self, name: str, pos, parent=True): + if name == 'ObjectType': + return ObjectType(pos) + self.name = name + self.attributes = {} + self.methods = {} + if parent: + self.parent = ObjectType(pos) + else: + self.parent = None + self.pos = pos + + def set_parent(self, parent): + if type(self.parent) != ObjectType and self.parent is not None: + error_msg = f'Parent type is already set for {self.name}.' + raise SemanticError(error_msg, *self.pos) + self.parent = parent + + def get_attribute(self, name: str, pos=None): + try: + return self.attributes[name] + except KeyError: + if self.parent is None: + error_msg = f'Attribute {name} is not defined in {self.name}.' + raise SemanticError(error_msg, *pos) + try: + return self.parent.get_attribute(name, pos) + except: + error_msg = f'Attribute {name} is not defined in {self.name}.' + raise SemanticError(error_msg, *pos) + + def define_attribute(self, name: str, typex, pos): + try: + self.attributes[name] + except KeyError: + try: + self.get_attribute(name, pos) + except: + self.attributes[name] = attribute = Attribute( + name, typex, len(self.attributes)) + return attribute + else: + error_msg = f'Attribute {name} is an attribute of an inherit class.' + raise SemanticError(error_msg, *pos) + else: + error_msg = f'Attribute {name} is multiply defined in class.' + raise SemanticError(error_msg, *pos) + + def get_method(self, name: str, pos=None): + try: + return self.methods[name] + except KeyError: + error_msg = f'Method {name} is not defined in {self.name}.' + if self.parent is None: + raise SemanticError(error_msg, *pos) + try: + return self.parent.get_method(name, pos) + except: + raise SemanticError(error_msg, *pos) + + def define_method(self, name: str, param_names: list, param_types: list, return_type, pos=(0, 0)): + if name in self.methods: + error_msg = f'Method {name} is multiply defined' + raise SemanticError(error_msg, *pos) + + method = self.methods[name] = Method( + name, param_names, param_types, return_type) + return method + + def has_attr(self, name: str): + try: + attr_name = self.get_attribute(name) + except: + return False + else: + return True + + def change_type(self, method, nparm, newtype): + idx = method.param_names.index(nparm) + method.param_types[idx] = newtype + + def all_attributes(self, clean=True): + plain = OrderedDict() if self.parent is None else self.parent.all_attributes(False) + for attr in self.attributes.values(): + plain[attr.name] = (attr, self) + return plain.values() if clean else plain + + def all_methods(self, clean=True): + plain = OrderedDict() if self.parent is None else self.parent.all_methods(False) + for method in self.methods.values(): + plain[method.name] = (method, self) + return plain.values() if clean else plain + + # code generator + def get_all_attributes(self): + all_attributes = self.parent and self.parent.get_all_attributes() or [] + all_attributes += [(self.name, attr) + for attr in self.attributes.values()] + return all_attributes + + # code generator + def get_all_methods(self): + all_methods = self.parent and self.parent.get_all_methods() or [] + all_methods += [(self.name, method) for method in self.methods] + return all_methods + + def conforms_to(self, other): + return other.bypass() or self == other or self.parent is not None and self.parent.conforms_to(other) + + def bypass(self): + return False + + def __str__(self): + output = f'type {self.name}' + parent = '' if self.parent is None else f' : {self.parent.name}' + output += parent + output += ' {' + output += '\n\t' if self.attributes or self.methods else '' + output += '\n\t'.join(str(x) for x in self.attributes.values()) + output += '\n\t' if self.attributes else '' + output += '\n\t'.join(str(x) for x in self.methods.values()) + output += '\n' if self.methods else '' + output += '}\n' + return output + + def __repr__(self): + return str(self) + + +class ErrorType(Type): + def __init__(self, pos=(0, 0)): + Type.__init__(self, '', pos) + + def conforms_to(self, other): + return True + + def bypass(self): + return True + + def __eq__(self, other): + return isinstance(other, ErrorType) + + def __ne__(self, other): + return not isinstance(other, ErrorType) + + +class VoidType(Type): + def __init__(self, pos=(0, 0)): + Type.__init__(self, 'Void', pos) + + def conforms_to(self, other): + return True + + def bypass(self): + return True + + def __eq__(self, other): + return isinstance(other, VoidType) + + +class BoolType(Type): + def __init__(self, pos=(0, 0)): + self.name = 'Bool' + self.attributes = {} + self.methods = {} + self.parent = None + self.pos = pos + # self.init_methods() + + def init_methods(self): + self.define_method('abort', [], [], self) + self.define_method('type_name', [], [], StringType()) + self.define_method('copy', [], [], SelfType()) + + def conforms_to(self, other): + return other.name == 'Object' or other.name == self.name + + def __eq__(self, other): + return other.name == self.name or isinstance(other, BoolType) + + def __ne__(self, other): + return other.name != self.name and not isinstance(other, BoolType) + + +class SelfType(Type): + def __init__(self, pos=(0, 0)): + self.name = 'SELF_TYPE' + self.attributes = {} + self.methods = {} + self.parent = None + self.pos = pos + + def __eq__(self, other): + return other.name == self.name or isinstance(other, SelfType) + + def __ne__(self, other): + return other.name != self.name and not isinstance(other, SelfType) + + +class IntType(Type): + def __init__(self, pos=(0, 0)): + self.name = 'Int' + self.attributes = {} + self.methods = {} + self.parent = None + self.pos = pos + # self.init_methods() + + def init_methods(self): + self.define_method('abort', [], [], self) + self.define_method('type_name', [], [], Type('String', (0, 0), False)) + self.define_method('copy', [], [], SelfType()) + + def conforms_to(self, other): + return other.name == 'Object' or other.name == self.name + + def __eq__(self, other): + return other.name == self.name or isinstance(other, IntType) + + def __ne__(self, other): + return other.name != self.name and not isinstance(other, IntType) + + +class StringType(Type): + def __init__(self, pos=(0, 0)): + self.name = 'String' + self.attributes = {} + self.methods = {} + self.parent = None + self.pos = pos + self.init_methods() + + def init_methods(self): + # self.define_method('abort', [], [], self) + # self.define_method('type_name', [], [], self) + # self.define_method('copy', [], [], SelfType()) + self.define_method('length', [], [], IntType()) + self.define_method('concat', ['s'], [self], self) + self.define_method('substr', ['i', 'l'], [IntType(), IntType()], self) + + def conforms_to(self, other): + return other.name == 'Object' or other.name == self.name + + def __eq__(self, other): + return other.name == self.name or isinstance(other, StringType) + + def __ne__(self, other): + return other.name != self.name and not isinstance(other, StringType) + + +class ObjectType(Type): + def __init__(self, pos=(0, 0)): + self.name = 'Object' + self.attributes = {} + self.methods = {} + self.parent = None + self.pos = pos + self.init_methods() + + def init_methods(self): + self.define_method('abort', [], [], self) + self.define_method('type_name', [], [], StringType()) + self.define_method('copy', [], [], SelfType()) + + def __eq__(self, other): + return other.name == self.name or isinstance(other, ObjectType) + + def __ne__(self, other): + return other.name != self.name and not isinstance(other, ObjectType) + + +class IOType(Type): + def __init__(self, pos=(0, 0)): + self.name = 'IO' + self.attributes = {} + self.methods = {} + self.parent = ObjectType(pos) + self.pos = pos + self.init_methods() + + def init_methods(self): + self.define_method('out_string', ['x'], [StringType()], SelfType()) + self.define_method('out_int', ['x'], [IntType()], SelfType()) + self.define_method('in_string', [], [], StringType()) + self.define_method('in_int', [], [], IntType()) + + def __eq__(self, other): + return other.name == self.name or isinstance(other, IOType) + + def __ne__(self, other): + return other.name != self.name and not isinstance(other, IOType) + + +class AutoType(Type): + def __init__(self): + Type.__init__(self, 'AUTO_TYPE') + + def __eq__(self, other): + return other.name == self.name or isinstance(other, AutoType) + + def __ne__(self, other): + return other.name != self.name and not isinstance(other, AutoType) + + +class Context: + def __init__(self): + self.types = {} + self.graph = {} + self.create_graph() + + def create_graph(self): + self.graph['Object'] = ['IO', 'String', 'Bool', 'Int'] + self.graph['IO'] = [] + self.graph['String'] = [] + self.graph['Int'] = [] + self.graph['Bool'] = [] + + def update_graph(self): + for tname, _ in self.types.items(): + if not tname in ['Object', 'IO', 'String', 'Bool', 'Int', 'SELF_TYPE']: + self._update_graph(tname) + + def _update_graph(self, name): + parentName = self.types[name].parent.name + + if not self.graph.__contains__(name): + self.graph[name] = [] + if self.graph.__contains__(parentName): + self.graph[parentName].append(name) + else: + self.graph[parentName] = [name] + + def create_type(self, name, pos): + if name in self.types: + error_text = 'Classes may not be redefined.' + raise SemanticError(error_text, *pos) + typex = self.types[name] = Type(name, pos) + + return typex + + def get_type(self, name: str, pos=None): + try: + return self.types[name] + except KeyError: + error_text = f'Type {name} is not defined.' + raise SemanticError(error_text, *pos) + + def set_type_tags(self, node='Object', tag=0): + self.types[node].tag = tag + for i, t in enumerate(self.graph[node]): + self.set_type_tags(t, tag + i + 1) + + def set_type_max_tags(self, node='Object'): + if not self.graph[node]: + self.types[node].max_tag = self.types[node].tag + else: + for t in self.graph[node]: + self.set_type_max_tags(t) + maximum = 0 + for t in self.graph[node]: + maximum = max(maximum, self.types[t].max_tag) + self.types[node].max_tag = maximum + + def __str__(self): + return '{\n\t' + '\n\t'.join(y for x in self.types.values() for y in str(x).split('\n')) + '\n}' + + def __repr__(self): + return str(self) + + +class VariableInfo: + def __init__(self, name, vtype, index=None): + self.name = name + self.type = vtype + self.index = index + + def __str__(self): + return f'{self.name} : {self.type.name}' + + def __repr__(self): + return str(self) + + +class Scope: + def __init__(self, parent=None): + self.locals = [] + self.attributes = [] + self.parent = parent + self.children = [] + self.expr_dict = {} + self.functions = {} + self.cil_locals = {} + self.index = 0 if parent is None else len(parent) + + def __len__(self): + return len(self.locals) + + def __repr__(self): + return str(self) + + def create_child(self): + child = Scope(self) + self.children.append(child) + return child + + def define_variable(self, vname, vtype): + info = VariableInfo(vname, vtype) + if info not in self.locals: + self.locals.append(info) + return info + + def find_variable(self, vname, index=None): + locals = self.attributes + self.locals + locals = locals if index is None else itt.islice(locals, index) + try: + return next(x for x in locals if x.name == vname) + except StopIteration: + return self.parent.find_variable(vname, index) if self.parent is not None else None + + def find_local(self, vname, index=None): + locals = self.locals if index is None else itt.islice( + self.locals, index) + try: + return next(x for x in locals if x.name == vname) + except StopIteration: + return self.parent.find_local(vname, self.index) if self.parent is not None else None + + def define_cil_local(self, vname, cil_name, vtype=None): + self.define_variable(vname, vtype) + self.cil_locals[vname] = cil_name + + def get_cil_local(self, vname): + if self.cil_locals.__contains__(vname): + return self.cil_locals[vname] + return None + + def find_cil_local(self, vname, index=None): + locals = self.cil_locals.items() if index is None else itt.islice( + self.cil_locals.items(), index) + try: + return next(cil_name for name, cil_name in locals if name == vname) + except StopIteration: + return self.parent.find_cil_local(vname, self.index) if (self.parent is not None) else None + + def find_attribute(self, vname, index=None): + locals = self.attributes if index is None else itt.islice( + self.attributes, index) + try: + return next(x for x in locals if x.name == vname) + except StopIteration: + return self.parent.find_attribute(vname, index) if self.parent is not None else None + + def get_class_scope(self): + if self.parent == None or self.parent.parent == None: + return self + return self.parent.get_class_scope() + + def is_defined(self, vname): + return self.find_variable(vname) is not None + + def is_defined_cil_local(self, vname): + return self.find_cil_local(vname) is not None + + def is_local(self, vname): + return any(True for x in self.locals if x.name == vname) + + def remove_local(self, vname): + self.locals = [local for local in self.locals if local.name != vname] + + def define_attribute(self, attr): + self.attributes.append(attr) diff --git a/src/semantic/visitors/__init__.py b/src/semantic/visitors/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/semantic/visitors/typeBuilder.py b/src/semantic/visitors/typeBuilder.py new file mode 100644 index 000000000..dbcd9d3a8 --- /dev/null +++ b/src/semantic/visitors/typeBuilder.py @@ -0,0 +1,118 @@ +from semantic.semantic import ErrorType +from utils.errors import SemanticError, TypexError +from utils import visitor +from utils.ast import * + + +class TypeBuilder: + def __init__(self, context, errors): + self.context = context + self.currentType = None + self.errors = errors + + @visitor.on('node') + def visit(self, node): + pass + + @visitor.when(ProgramNode) + def visit(self, programNode): + for declaration in programNode.declarations: + self.visit(declaration) + + @visitor.when(ClassDeclarationNode) + def visit(self, classDeclarationNode): + try: + self.currentType = self.context.get_type( + classDeclarationNode.id, (classDeclarationNode.line, classDeclarationNode.col)) + except SemanticError as error: + self.currentType = ErrorType() + self.errors.append(error) + + if classDeclarationNode.parent is not None: + if classDeclarationNode.parent in ['String', 'Int', 'Bool']: + errorText = f'Class {classDeclarationNode.id} cannot inherit class {classDeclarationNode.parent}.' + self.errors.append(SemanticError( + errorText, classDeclarationNode.line, classDeclarationNode.col)) + try: + parent = self.context.get_type( + classDeclarationNode.parent, (classDeclarationNode.parentLine, classDeclarationNode.parentCol)) + except: + errorText = f'Class {classDeclarationNode.id} inherits from an undefined class {classDeclarationNode.parent}' + self.errors.append(TypexError( + errorText, classDeclarationNode.line, classDeclarationNode.col)) + parent = None + try: + current = parent + while current is not None: + if current.name == self.currentType.name: + errorText = f'Class {self.currentType.name}, or an ancestor of {self.currentType.name}, is involved in an inheritance cycle.' + raise SemanticError( + errorText, classDeclarationNode.line, classDeclarationNode.col) + current = current.parent + except SemanticError as error: + parent = ErrorType() + self.errors.append(error) + + self.currentType.set_parent(parent) + + for feature in classDeclarationNode.features: + self.visit(feature) + + @visitor.when(FuncDeclarationNode) + def visit(self, funcDeclarationNode): + argsNames = [] + argsTypes = [] + for name, typex, line, col in funcDeclarationNode.params: + if name in argsNames: + errorText = f'Formal parameter {name} is multiply defined.' + self.errors.append(SemanticError(errorText, line, col)) + + argsNames.append(name) + + try: + argType = self.context.get_type(typex, (line, col)) + except SemanticError: + errorText = f'Class {typex} of formal parameter {typex} is undefined.' + self.errors.append(TypexError(errorText, line, col)) + argType = ErrorType() + + argsTypes.append(argType) + + try: + returnType = self.context.get_type( + funcDeclarationNode.type, (funcDeclarationNode.typeLine, funcDeclarationNode.typeCol)) + except SemanticError: + errorText = f'Undefined return type {funcDeclarationNode.type} in method {funcDeclarationNode.id}.' + self.errors.append(TypexError( + errorText, funcDeclarationNode.typeLine, funcDeclarationNode.typeCol)) + returnType = ErrorType( + (funcDeclarationNode.typeLine, funcDeclarationNode.typeCol)) + + try: + self.currentType.define_method(funcDeclarationNode.id, argsNames, argsTypes, + returnType, (funcDeclarationNode.line, funcDeclarationNode.col)) + except SemanticError as error: + self.errors.append(error) + + @visitor.when(AttrDeclarationNode) + def visit(self, attrDeclarationNode): + try: + attrType = self.context.get_type( + attrDeclarationNode.type, (attrDeclarationNode.line, attrDeclarationNode.col)) + except SemanticError: + errorText = f'Class {attrDeclarationNode.type} of attribute {attrDeclarationNode.id} is undefined.' + self.errors.append(TypexError( + errorText, attrDeclarationNode.typeLine, attrDeclarationNode.typeCol)) + attrType = ErrorType( + (attrDeclarationNode.line, attrDeclarationNode.col)) + + if attrDeclarationNode.id == 'self': + errorText = f"'self' cannot be the name of an attribute." + self.errors.append(SemanticError( + errorText, attrDeclarationNode.line, attrDeclarationNode.col)) + + try: + self.currentType.define_attribute( + attrDeclarationNode.id, attrType, (attrDeclarationNode.line, attrDeclarationNode.col)) + except SemanticError as error: + self.errors.append(error) diff --git a/src/semantic/visitors/typeChecker.py b/src/semantic/visitors/typeChecker.py new file mode 100644 index 000000000..069fc090f --- /dev/null +++ b/src/semantic/visitors/typeChecker.py @@ -0,0 +1,470 @@ +from semantic.semantic import AutoType, BoolType, ErrorType, IntType, MethodError, ObjectType, StringType, VariableInfo, VoidType +from utils.errors import AttributexError, SemanticError, TypexError +from utils import visitor +from utils.utils import get_type, get_common_basetype +from utils.ast import * + + +class TypeChecker: + def __init__(self, context, errors): + self.context = context + self.currentType = None + self.currentMethod = None + self.currentIndex = None + self.errors = errors + + @visitor.on('node') + def visit(self, node, scope): + pass + + @visitor.when(ProgramNode) + def visit(self, programNode, scope): + for i in range(0, len(programNode.declarations)): + self.visit(programNode.declarations[i], scope.children[i]) + + @visitor.when(ClassDeclarationNode) + def visit(self, classDeclarationNode, scope): + self.currentType = self.context.get_type( + classDeclarationNode.id, (classDeclarationNode.line, classDeclarationNode.col)) + + funcDeclarations = [] + for feature in classDeclarationNode.features: + if isinstance(feature, AttrDeclarationNode): + self.visit(feature, scope) + else: + funcDeclarations.append(feature) + + for funcDeclaration in funcDeclarations: + self.visit(funcDeclaration, scope.functions[funcDeclaration.id]) + + @visitor.when(FuncDeclarationNode) + def visit(self, funcDeclarationNode, scope): + parent = self.currentType.parent + self.currentMethod = self.currentType.get_method( + funcDeclarationNode.id, (funcDeclarationNode.line, funcDeclarationNode.col)) + + method = self.currentMethod + if parent is not None: + try: + oldMethod = parent.get_method( + funcDeclarationNode.id, (funcDeclarationNode.line, funcDeclarationNode.col)) + if oldMethod.return_type.name != method.return_type.name: + errorText = f'In redefined method {funcDeclarationNode.id}, return type {method.return_type.name} is different from original return type {oldMethod.return_type.name}.' + self.errors.append(SemanticError( + errorText, funcDeclarationNode.typeLine, funcDeclarationNode.typeCol)) + if len(method.param_names) != len(oldMethod.param_names): + errorText = f'Incompatible number of formal parameters in redefined method {funcDeclarationNode.id}.' + self.errors.append(SemanticError( + errorText, funcDeclarationNode.line, funcDeclarationNode.col)) + for (name, ptype, pline, pcol), type1, type2 in zip(funcDeclarationNode.params, method.param_types, oldMethod.param_types): + if type1.name != type2.name: + errorText = f'In redefined method {name}, parameter type {type1.name} is different from original type {type2.name}.' + self.errors.append( + SemanticError(errorText, pline, pcol)) + except: + pass + + result = self.visit(funcDeclarationNode.body, scope) + returnType = get_type(method.return_type, self.currentType) + + if not result.conforms_to(returnType): + errorText = f'Inferred return type {result.name} of method test does not conform to declared return type {returnType.name}.' + self.errors.append(TypexError( + errorText, funcDeclarationNode.typeLine, funcDeclarationNode.typeCol)) + + @visitor.when(AttrDeclarationNode) + def visit(self, attrDeclarationNode, scope): + attr = self.currentType.get_attribute( + attrDeclarationNode.id, (attrDeclarationNode.line, attrDeclarationNode.col)) + attrType = get_type(attr.type, self.currentType) + self.currentIndex = attr.index + typex = self.visit(attrDeclarationNode.expr, scope) + self.currentIndex = None + + if not typex.conforms_to(attrType): + errorText = f'Inferred type {typex.name} of initialization of attribute {attr.name} does not conform to declared type {attrType.name}.' + self.errors.append(TypexError( + errorText, attrDeclarationNode.line, attrDeclarationNode.col)) + return ErrorType() + + return typex + + @visitor.when(VarDeclarationNode) + def visit(self, varDeclarationNode, scope): + varType = self._get_type( + varDeclarationNode.type, (varDeclarationNode.line, varDeclarationNode.col)) + varType = get_type(varType, self.currentType) + + if varDeclarationNode.expr == None: + return varType + else: + typex = self.visit(varDeclarationNode.expr, scope) + if not typex.conforms_to(varType): + errorText = f'Inferred type {typex.name} of initialization of {varDeclarationNode.id} does not conform to identifier\'s declared type {varType.name}.' + self.errors.append(TypexError( + errorText, varDeclarationNode.typeLine, varDeclarationNode.typeCol)) + return typex + + @visitor.when(AssignNode) + def visit(self, assignNode, scope): + varInfo = self.find_variable(scope, assignNode.id) + varType = get_type(varInfo.type, self.currentType) + typex = self.visit(assignNode.expr, scope) + + if not typex.conforms_to(varType): + errorText = f'Inferred type {typex.name} of initialization of {assignNode.id} does not conform to identifier\'s declared type {varType.name}.' + self.errors.append(TypexError( + errorText, assignNode.line, assignNode.col)) + + assignNode.computed_type = typex + return typex + + @visitor.when(ArrobaCallNode) + def visit(self, arrobaCallNode, scope): + objType = self.visit(arrobaCallNode.obj, scope) + typex = self._get_type( + arrobaCallNode.type, (arrobaCallNode.typeLine, arrobaCallNode.typeCol)) + + if not objType.conforms_to(typex): + errorText = f'Expression type {typex.name} does not conform to declared static dispatch type {objType.name}.' + self.errors.append(TypexError( + errorText, arrobaCallNode.typeLine, arrobaCallNode.typeCol)) + return ErrorType() + + method = self._get_method( + typex, arrobaCallNode.id, (arrobaCallNode.line, arrobaCallNode.col)) + if not isinstance(method, MethodError): + # check the args + argTypes = [self.visit(arg, scope) for arg in arrobaCallNode.args] + + if len(argTypes) > len(method.param_types): + errorText = f'Method {method.name} called with wrong number of arguments.' + self.errors.append(SemanticError( + errorText, arrobaCallNode.line, arrobaCallNode.col)) + elif len(argTypes) < len(method.param_types): + for arg, argInfo in zip(method.param_names[len(argTypes):], arrobaCallNode.args[len(argTypes):]): + errorText = f'Method {method.name} called with wrong number of arguments.' + self.errors.append(SemanticError(errorText, *argInfo.pos)) + + for argType, paramType, paramName in zip(argTypes, method.param_types, method.param_names): + if not argType.conforms_to(paramType): + errorText = f'In call of method {method.name}, type {argType.name} of parameter {paramName} does not conform to declared type {paramType.name}.' + self.errors.append(TypexError( + errorText, arrobaCallNode.line, arrobaCallNode.col)) + + arrobaCallNode.computed_type = get_type(method.return_type, TypexError) + return arrobaCallNode.computed_type + + @visitor.when(DotCallNode) + def visit(self, dotCallNode, scope): + objType = self.visit(dotCallNode.obj, scope) + method = self._get_method( + objType, dotCallNode.id, (dotCallNode.line, dotCallNode.col)) + if not isinstance(method, MethodError): + # check the args + argTypes = [self.visit(arg, scope) for arg in dotCallNode.args] + + if len(argTypes) > len(method.param_types): + errorText = f'Method {method.name} called with wrong number of arguments.' + self.errors.append(SemanticError( + errorText, dotCallNode.line, dotCallNode.col)) + elif len(argTypes) < len(method.param_types): + for arg, argInfo in zip(method.param_names[len(argTypes):], dotCallNode.args[len(argTypes):]): + errorText = f'Method {method.name} called with wrong number of arguments.' + self.errors.append(SemanticError(errorText, *argInfo.pos)) + + for argType, paramType, paramName in zip(argTypes, method.param_types, method.param_names): + if not argType.conforms_to(paramType): + errorText = f'In call of method {method.name}, type {argType.name} of parameter {paramName} does not conform to declared type {paramType.name}.' + self.errors.append(TypexError( + errorText, dotCallNode.line, dotCallNode.col)) + + dotCallNode.computed_type = get_type(method.return_type, objType) + return dotCallNode.computed_type + + @visitor.when(MemberCallNode) + def visit(self, memberCallNode, scope): + typex = self.currentType + method = self._get_method( + typex, memberCallNode.id, (memberCallNode.line, memberCallNode.col)) + if not isinstance(method, MethodError): + # check the args + argTypes = [self.visit(arg, scope) for arg in memberCallNode.args] + + if len(argTypes) > len(method.param_types): + errorText = f'Method {method.name} called with wrong number of arguments.' + self.errors.append(SemanticError( + errorText, memberCallNode.line, memberCallNode.col)) + elif len(argTypes) < len(method.param_types): + for arg, argInfo in zip(method.param_names[len(argTypes):], memberCallNode.args[len(argTypes):]): + errorText = f'Method {method.name} called with wrong number of arguments.' + self.errors.append(SemanticError(errorText, *argInfo.pos)) + + for argType, paramType, paramName in zip(argTypes, method.param_types, method.param_names): + if not argType.conforms_to(paramType): + errorText = f'In call of method {method.name}, type {argType.name} of parameter {paramName} does not conform to declared type {paramType.name}.' + self.errors.append(TypexError( + errorText, memberCallNode.line, memberCallNode.col)) + + memberCallNode.static_type = typex + memberCallNode.computed_type = get_type(method.return_type, typex) + return memberCallNode.computed_type + + @visitor.when(IfThenElseNode) + def visit(self, ifThenElseNode, scope): + conditionType = self.visit(ifThenElseNode.condition, scope) + if conditionType.name != 'Bool': + errorText = f'Predicate of \'if\' does not have type Bool.' + self.errors.append(TypexError( + errorText, ifThenElseNode.line, ifThenElseNode.col)) + + ifBodyType = self.visit(ifThenElseNode.ifBody, scope) + elseBodyType = self.visit(ifThenElseNode.elseBody, scope) + + ifThenElseNode.computed_type = get_common_basetype( + [ifBodyType, elseBodyType]) + return ifThenElseNode.computed_type + + @visitor.when(WhileNode) + def visit(self, whileNode, scope): + conditionType = self.visit(whileNode.condition, scope) + if conditionType.name != 'Bool': + errorText = 'Loop condition does not have type Bool.' + self.errors.append(TypexError( + errorText, whileNode.line, whileNode.col)) + self.visit(whileNode.body, scope) + + whileNode.computed_type = ObjectType() + return ObjectType() + + @visitor.when(BlockNode) + def visit(self, blockNode, scope): + typex = None + for expr in blockNode.exprs: + typex = self.visit(expr, scope) + + blockNode.computed_type = typex + return typex + + @visitor.when(LetInNode) + def visit(self, letInNode, scope): + childScope = scope.expr_dict[letInNode] + for letDeclaration in letInNode.letBody: + self.visit(letDeclaration, childScope) + + typex = self.visit(letInNode.inBody, childScope) + letInNode.computed_type = typex + return typex + + @visitor.when(CaseNode) + def visit(self, caseNode, scope): + exprType = self.visit(caseNode.expr, scope) + newScope = scope.expr_dict[caseNode] + types = [] + checkDuplicate = [] + for option, optionScope in zip(caseNode.optionList, newScope.children): + optionType = self.visit(option, optionScope) + types.append(optionType) + if option.type in checkDuplicate: + errorText = f'Duplicate branch {option.type} in case statement.' + self.errors.append(SemanticError( + errorText, option.typeLine, option.typeCol)) + checkDuplicate.append(option.type) + + caseNode.computed_type = get_common_basetype(types) + return caseNode.computed_type + + @visitor.when(CaseOptionNode) + def visit(self, caseOptionNode, scope): + optionType = self.visit(caseOptionNode.expr, scope) + caseOptionNode.computed_type = optionType + return optionType + + @visitor.when(PlusNode) + def visit(self, plusNode, scope): + leftType = self.visit(plusNode.lvalue, scope) + rightType = self.visit(plusNode.rvalue, scope) + if leftType != IntType() or rightType != IntType(): + errorText = f'non-Int arguments: {leftType.name} + {rightType.name} .' + self.errors.append(TypexError( + errorText, plusNode.line, plusNode.col)) + return ErrorType() + + plusNode.computed_type = IntType() + return IntType() + + @visitor.when(MinusNode) + def visit(self, minusNode, scope): + leftType = self.visit(minusNode.lvalue, scope) + rightType = self.visit(minusNode.rvalue, scope) + if leftType != IntType() or rightType != IntType(): + errorText = f'non-Int arguments: {leftType.name} - {rightType.name} .' + self.errors.append(TypexError( + errorText, minusNode.line, minusNode.col)) + return ErrorType() + + minusNode.computed_type = IntType() + return IntType() + + @visitor.when(StarNode) + def visit(self, starNode, scope): + leftType = self.visit(starNode.lvalue, scope) + rightType = self.visit(starNode.rvalue, scope) + if leftType != IntType() or rightType != IntType(): + errorText = f'non-Int arguments: {leftType.name} * {rightType.name} .' + self.errors.append(TypexError( + errorText, starNode.line, starNode.col)) + return ErrorType() + + starNode.computed_type = IntType() + return IntType() + + @visitor.when(DivNode) + def visit(self, divNode, scope): + leftType = self.visit(divNode.lvalue, scope) + rightType = self.visit(divNode.rvalue, scope) + if leftType != IntType() or rightType != IntType(): + errorText = f'non-Int arguments: {leftType.name} / {rightType.name} .' + self.errors.append(TypexError( + errorText, divNode.line, divNode.col)) + return ErrorType() + + divNode.computed_type = IntType() + return IntType() + + @visitor.when(LessNode) + def visit(self, lessNode, scope): + leftType = self.visit(lessNode.lvalue, scope) + rightType = self.visit(lessNode.rvalue, scope) + if leftType != IntType() or rightType != IntType(): + errorText = f'non-Int arguments: {leftType.name} < {rightType.name} .' + self.errors.append(TypexError( + errorText, lessNode.line, lessNode.col)) + return ErrorType() + + lessNode.computed_type = BoolType() + return BoolType() + + @visitor.when(LessEqNode) + def visit(self, lessEq, scope): + leftType = self.visit(lessEq.lvalue, scope) + rightType = self.visit(lessEq.rvalue, scope) + if leftType != IntType() or rightType != IntType(): + errorText = f'non-Int arguments: {leftType.name} <= {rightType.name} .' + self.errors.append(TypexError( + errorText, lessEq.line, lessEq.col)) + return ErrorType() + + lessEq.computed_type = BoolType() + return BoolType() + + @visitor.when(EqualNode) + def visit(self, equalNode, scope): + leftType = self.visit(equalNode.lvalue, scope) + rightType = self.visit(equalNode.rvalue, scope) + if (leftType != rightType) and (leftType in [IntType(), StringType(), BoolType()] or rightType in [IntType(), StringType(), BoolType()]): + errorText = 'Illegal comparison with a basic type.' + self.errors.append(TypexError( + errorText, equalNode.line, equalNode.col)) + return ErrorType() + + equalNode.computed_type = BoolType() + return BoolType() + + @visitor.when(NegationNode) + def visit(self, negationNode, scope): + exprType = self.visit(negationNode.expr, scope) + if exprType != IntType(): + errorText = f'Argument of \'~\' has type {exprType.name} instead of {IntType().name}.' + self.errors.append(TypexError( + errorText, negationNode.line, negationNode.col)) + return ErrorType() + + negationNode.computed_type = IntType() + return IntType() + + @visitor.when(LogicNegationNode) + def visit(self, logicNegationNode, scope): + exprType = self.visit(logicNegationNode.expr, scope) + if exprType != BoolType(): + errorText = f'Argument of \'not\' has type {exprType.name} instead of {BoolType().name}.' + self.errors.append(TypexError( + errorText, logicNegationNode.line, logicNegationNode.col)) + return ErrorType() + + logicNegationNode.computed_type = BoolType() + return BoolType() + + @visitor.when(IsVoidNode) + def visit(self, isVoidNode, scope): + self.visit(isVoidNode.expr, scope) + isVoidNode.computed_type = BoolType() + return BoolType() + + @visitor.when(NewNode) + def visit(self, newNode, scope): + try: + typex = self.context.get_type( + newNode.id, (newNode.line, newNode.col)) + except: + typex = ErrorType() + errorText = f'\'new\' used with undefined class {newNode.id}.' + self.errors.append(TypexError( + errorText, newNode.line, newNode.col)) + + typex = get_type(typex, self.currentType) + newNode.computed_type = typex + return typex + + @visitor.when(IdNode) + def visit(self, idNode, scope): + varType = self.find_variable(scope, idNode.id).type + typex = get_type(varType, self.currentType) + idNode.computed_type = typex + return typex + + @visitor.when(IntNode) + def visit(self, intNode, scope): + intNode.computed_type = IntType((intNode.line, intNode.col)) + return intNode.computed_type + + @visitor.when(BoolNode) + def visit(self, boolNode, scope): + boolNode.computed_type = BoolType((boolNode.line, boolNode.col)) + return boolNode.computed_type + + @visitor.when(StringNode) + def visit(self, stringNode, scope): + stringNode.computed_type = StringType( + (stringNode.line, stringNode.col)) + return stringNode.computed_type + + @visitor.when(VoidNode) + def visit(self, voidNode, scope): + voidNode.computed_type = VoidType((voidNode.line, voidNode.col)) + return voidNode.computed_type + + def _get_type(self, ntype, pos): + try: + return self.context.get_type(ntype, pos) + except SemanticError as e: + self.errors.append(e) + return ErrorType() + + def _get_method(self, typex, name, pos): + typex = self.context.get_type(typex.name) + try: + return typex.get_method(name, pos) + except SemanticError: + if type(typex) != ErrorType and type(typex) != AutoType: + errorText = f'Dispatch to undefined method {name}.' + self.errors.append(AttributexError(errorText, *pos)) + return MethodError(name, [], [], ErrorType()) + + def find_variable(self, scope, lex): + var_info = scope.find_local(lex) + if var_info is None: + var_info = scope.find_attribute(lex) + if lex in self.currentType.attributes and var_info is None: + return VariableInfo(lex, VoidType()) + return var_info diff --git a/src/semantic/visitors/typeCollector.py b/src/semantic/visitors/typeCollector.py new file mode 100644 index 000000000..e356ad1df --- /dev/null +++ b/src/semantic/visitors/typeCollector.py @@ -0,0 +1,47 @@ +from utils.errors import SemanticError +from utils import visitor +from utils.ast import ProgramNode, ClassDeclarationNode +from semantic.semantic import Context, IntType, StringType, BoolType, ObjectType, SelfType, IOType + + +class TypeCollector(object): + def __init__(self, errors): + self.context = None + self.errors = errors + + @visitor.on('node') + def visit(self, node): + pass + + @visitor.when(ProgramNode) + def visit(self, programNode): + self.context = Context() + self.context.types['Int'] = IntType() + self.context.types['String'] = StringType() + self.context.types['Bool'] = BoolType() + self.context.types['Object'] = ObjectType() + self.context.types['SELF_TYPE'] = SelfType() + self.context.types['IO'] = IOType() + + self.context.types['IO'].set_parent(self.context.types['Object']) + self.context.types['String'].set_parent(self.context.types['Object']) + self.context.types['Int'].set_parent(self.context.types['Object']) + self.context.types['Bool'].set_parent(self.context.types['Object']) + + for classDeclarationNode in programNode.declarations: + self.visit(classDeclarationNode) + + @visitor.when(ClassDeclarationNode) + def visit(self, classDeclarationNode): + if classDeclarationNode.id in ['Int', 'String', 'Bool', 'Object', 'SELF_TYPE', 'IO']: + errorText = f'Redefinition of basic class {classDeclarationNode.id}' + self.errors.append(SemanticError( + errorText, classDeclarationNode.line, classDeclarationNode.col)) + try: + self.context.create_type( + classDeclarationNode.id, (classDeclarationNode.line, classDeclarationNode.col)) + except SemanticError as error: + self.errors.append(error) + + if not classDeclarationNode.parent: + classDeclarationNode.parent = 'Object' diff --git a/src/semantic/visitors/varCollector.py b/src/semantic/visitors/varCollector.py new file mode 100644 index 000000000..3ddbe0bfe --- /dev/null +++ b/src/semantic/visitors/varCollector.py @@ -0,0 +1,222 @@ +from utils import visitor +from utils.ast import ArrobaCallNode, AssignNode, AttrDeclarationNode, BlockNode, BoolNode, CaseNode, CaseOptionNode, ClassDeclarationNode, DotCallNode, FuncDeclarationNode, IdNode, IfThenElseNode, IntNode, IsVoidNode, LetInNode, MemberCallNode, ProgramNode, StringNode, VarDeclarationNode, VoidNode, WhileNode +from semantic.semantic import BoolType, ErrorType, IntType, Scope, StringType +from utils.errors import NamexError, SemanticError, TypexError + + +class VarCollector: + def __init__(self, context, errors): + self.context = context + self.errors = errors + self.currentType = None + self.currentMethod = None + self.currentIndex = None + + @visitor.on('node') + def visit(self, node, scope): + pass + + @visitor.when(ProgramNode) + def visit(self, programNode, scope=None): + scope = Scope() + for declaration in programNode.declarations: + self.visit(declaration, scope.create_child()) + return scope + + @visitor.when(ClassDeclarationNode) + def visit(self, classDeclarationNode, scope): + self.currentType = self._get_type( + classDeclarationNode.id, (classDeclarationNode.line, classDeclarationNode.col)) + scope.define_variable('self', self.currentType) + + for feature in classDeclarationNode.features: + if isinstance(feature, AttrDeclarationNode): + self.visit(feature, scope) + + for attr, _ in self.currentType.all_attributes(): + if scope.find_attribute(attr.name) is None: + scope.define_attribute(attr) + + for feature in classDeclarationNode.features: + if isinstance(feature, FuncDeclarationNode): + self.visit(feature, scope) + + @visitor.when(FuncDeclarationNode) + def visit(self, funcDeclarationNode, scope): + parent = self.currentType.parent + pnames = [param[0] for param in funcDeclarationNode.params] + ptypes = [param[1] for param in funcDeclarationNode.params] + + self.currentMethod = self.currentType.get_method( + funcDeclarationNode.id, (funcDeclarationNode.line, funcDeclarationNode.col)) + + newScope = scope.create_child() + scope.functions[funcDeclarationNode.id] = newScope + + for pname, ptype, pline, pcol in funcDeclarationNode.params: + if pname == 'self': + errorText = "'self' cannot be the name of a formal parameter." + self.errors.append(SemanticError(errorText, pline, pcol)) + newScope.define_variable( + pname, self._get_type(ptype, (pline, pcol))) + + self.visit(funcDeclarationNode.body, newScope) + + @visitor.when(AttrDeclarationNode) + def visit(self, attrDeclarationNode, scope): + attr = self.currentType.get_attribute( + attrDeclarationNode.id, (attrDeclarationNode.line, attrDeclarationNode.col)) + if attrDeclarationNode.expr is None: + self.define_default_value(attr.type, attrDeclarationNode) + else: + self.visit(attrDeclarationNode.expr, scope) + attr.expr = attrDeclarationNode.expr + scope.define_attribute(attr) + + def _get_type(self, ntype, pos): + try: + return self.context.get_type(ntype, pos) + except SemanticError as e: + self.errors.append(e) + return ErrorType() + + @visitor.when(VarDeclarationNode) + def visit(self, varDeclarationNode, scope): + if varDeclarationNode.id == 'self': + errorText = '\'self\' cannot be bound in a \'let\' expression.' + self.errors.append(SemanticError( + errorText, varDeclarationNode.line, varDeclarationNode.col)) + return + + try: + vType = self.context.get_type( + varDeclarationNode.type, (varDeclarationNode.line, varDeclarationNode.col)) + except: + errorText = f'Class {varDeclarationNode.type} of let-bound identifier {varDeclarationNode.id} is undefined.' + self.errors.append(TypexError( + errorText, varDeclarationNode.typeLine, varDeclarationNode.typeCol)) + vType = ErrorType() + + vType = self._get_type( + varDeclarationNode.type, (varDeclarationNode.typeLine, varDeclarationNode.typeCol)) + varInfo = scope.define_variable(varDeclarationNode.id, vType) + + if varDeclarationNode.expr is not None: + self.visit(varDeclarationNode.expr, scope) + else: + self.define_default_value(vType, varDeclarationNode) + + @visitor.when(AssignNode) + def visit(self, assignNode, scope): + if assignNode.id == 'self': + errorText = 'Cannot assign to \'self\'.' + self.errors.append(SemanticError( + errorText, assignNode.line, assignNode.col)) + return + + vInfo = scope.find_variable(assignNode.id) + if vInfo is None: + varInfo = scope.find_attribute(assignNode.id) + if varInfo is None: + errorText = f'Undeclared identifier {assignNode.id}.' + self.errors.append(NamexError( + errorText, assignNode.line, assignNode.col)) + vType = ErrorType() + scope.define_variable(assignNode.id, vType) + + self.visit(assignNode.expr, scope) + + @visitor.when(ArrobaCallNode) + def visit(self, arrobaCallNode, scope): + self.visit(arrobaCallNode.obj, scope) + for arg in arrobaCallNode.args: + self.visit(arg, scope) + + @visitor.when(DotCallNode) + def visit(self, dotCallNode, scope): + self.visit(dotCallNode.obj, scope) + for arg in dotCallNode.args: + self.visit(arg, scope) + + @visitor.when(MemberCallNode) + def visit(self, memberCallNode, scope): + for arg in memberCallNode.args: + self.visit(arg, scope) + + @visitor.when(IfThenElseNode) + def visit(self, ifThenElseNode, scope): + self.visit(ifThenElseNode.condition, scope) + self.visit(ifThenElseNode.ifBody, scope) + self.visit(ifThenElseNode.elseBody, scope) + + @visitor.when(WhileNode) + def visit(self, whileNode, scope): + self.visit(whileNode.condition, scope) + self.visit(whileNode.body, scope) + + @visitor.when(BlockNode) + def visit(self, blockNode, scope): + for expr in blockNode.exprs: + self.visit(expr, scope) + + @visitor.when(LetInNode) + def visit(self, letInNode, scope): + newScope = scope.create_child() + scope.expr_dict[letInNode] = newScope + for letDeclaration in letInNode.letBody: + self.visit(letDeclaration, newScope) + + self.visit(letInNode.inBody, newScope) + + @visitor.when(CaseNode) + def visit(self, caseNode, scope): + self.visit(caseNode.expr, scope) + newScope = scope.create_child() + scope.expr_dict[caseNode] = newScope + + for option in caseNode.optionList: + self.visit(option, newScope.create_child()) + + @visitor.when(CaseOptionNode) + def visit(self, caseOptionNode, scope): + try: + typex = self.context.get_type( + caseOptionNode.type, (caseOptionNode.line, caseOptionNode.col)) + except: + errorText = f'Class {caseOptionNode.type} of case branch is undefined.' + self.errors.append(TypexError( + errorText, caseOptionNode.line, caseOptionNode.col)) + typex = ErrorType() + + scope.define_variable(caseOptionNode.id, typex) + self.visit(caseOptionNode.expr, scope) + + @visitor.when(IsVoidNode) + def visit(self, isVoidNode, scope): + self.visit(isVoidNode.expr, scope) + + @visitor.when(IdNode) + def visit(self, idNode, scope): + try: + return self.currentType.get_attribute(idNode.id, (idNode.line, idNode.col)).type + except: + if not scope.is_defined(idNode.id): + errorText = f'Undeclared identifier {idNode.id}.' + self.errors.append(NamexError( + errorText, idNode.line, idNode.col)) + vInfo = scope.define_variable( + idNode.id, ErrorType((idNode.line, idNode.col))) + else: + vInfo = scope.find_variable(idNode.id) + + return vInfo.type + + def define_default_value(self, typex, node): + if typex == IntType(): + node.expr = IntNode(0) + elif typex == StringType(): + node.expr = StringNode("") + elif typex == BoolType(): + node.expr = BoolNode('false') + else: + node.expr = VoidNode(node.id) diff --git a/src/utils/__init__.py b/src/utils/__init__.py new file mode 100644 index 000000000..1a7eeac05 --- /dev/null +++ b/src/utils/__init__.py @@ -0,0 +1,2 @@ +from .utils import find_column, Token +from .errors import LexicographicError, SemanticError, SyntacticError \ No newline at end of file diff --git a/src/utils/ast.py b/src/utils/ast.py new file mode 100644 index 000000000..009e830b8 --- /dev/null +++ b/src/utils/ast.py @@ -0,0 +1,260 @@ + +class Node(): + pass + + +class ProgramNode(Node): + def __init__(self, declarations): + self.declarations = declarations + + +class DeclarationNode(Node): + pass + + +class ClassDeclarationNode(DeclarationNode): + def __init__(self, idx, features, parent=None): + self.id = idx.value + self.line = idx.lineno + self.col = idx.column + self.features = features + if parent: + self.parent = parent.value + self.parentLine = parent.lineno + self.parentCol = parent.column + else: + self.parent = None + self.parentLine = 0 + self.parentCol = 0 + + +class FuncDeclarationNode(DeclarationNode): + def __init__(self, idx, params, return_type, body): + self.id = idx.value + self.line = idx.lineno + self.col = idx.column + self.params = [(pname.value, ptype.value, ptype.lineno, + ptype.column) for pname, ptype in params] + self.type = return_type.value + self.typeLine = return_type.lineno + self.typeCol = return_type.column + self.body = body + + +class AttrDeclarationNode(DeclarationNode): + def __init__(self, idx, typex, expr=None): + self.id = idx.value + self.line = idx.lineno + self.col = idx.column + self.type = typex.value + self.typeLine = typex.lineno + self.typeCol = typex.column + self.expr = expr + + +class VarDeclarationNode(DeclarationNode): + def __init__(self, idx, typex, expr=None): + self.id = idx.value + self.line = idx.lineno + self.col = idx.column + self.type = typex.value + self.typeLine = typex.lineno + self.typeCol = typex.column + self.expr = expr + + +class ExpressionNode(Node): + pass + + +class AssignNode(ExpressionNode): + def __init__(self, idx, expr): + self.id = idx.value + self.line = idx.lineno + self.col = idx.column + self.expr = expr + + +class ArrobaCallNode(ExpressionNode): + def __init__(self, obj, idx, args, typex): + self.obj = obj + self.id = idx.value + self.line = idx.lineno + self.col = idx.column + self.args = args + self.type = typex.value + self.typeLine = typex.lineno + self.typeCol = typex.column + + +class DotCallNode(ExpressionNode): + def __init__(self, obj, idx, args): + self.obj = obj + self.id = idx.value + self.line = idx.lineno + self.col = idx.column + self.args = args + + +class MemberCallNode(ExpressionNode): + def __init__(self, idx, args): + self.id = idx.value + self.line = idx.lineno + self.col = idx.column + self.args = args + self.instance = SelfNode('self', self.line, self.col) + + +class IfThenElseNode(ExpressionNode): + def __init__(self, condition, ifBody, elseBody, token): + self.condition = condition + self.ifBody = ifBody + self.elseBody = elseBody + self.line = token.lineno + self.col = token.column + + +class WhileNode(ExpressionNode): + def __init__(self, condition, body, token): + self.condition = condition + self.body = body + self.line = token.lineno + self.col = token.column + + +class BlockNode(ExpressionNode): + def __init__(self, exprs, token): + self.exprs = exprs + self.line = token.lineno + self.col = token.column + + +class LetInNode(ExpressionNode): + def __init__(self, letBody, inBody, token): + self.letBody = letBody + self.inBody = inBody + self.line = token.lineno + self.col = token.column + + +class CaseNode(ExpressionNode): + def __init__(self, expr, optionList, token): + self.expr = expr + self.optionList = optionList + self.line = token.lineno + self.col = token.column + + +class CaseOptionNode(ExpressionNode): + def __init__(self, idx, typex, expr): + self.id = idx.value + self.line = idx.lineno + self.col = idx.column + self.type = typex.value + self.typeLine = typex.lineno + self.typeCol = typex.column + self.expr = expr + + # ---------------- Binary Nodes ------------------ + + +class BinaryNode(ExpressionNode): + def __init__(self, lvalue, rvalue): + self.lvalue = lvalue + self.rvalue = rvalue + self.line = lvalue.line + self.col = lvalue.col + + +class PlusNode(BinaryNode): + pass + + +class MinusNode(BinaryNode): + pass + + +class StarNode(BinaryNode): + pass + + +class DivNode(BinaryNode): + pass + + +class LessNode(BinaryNode): + pass + + +class LessEqNode(BinaryNode): + pass + + +class EqualNode(BinaryNode): + pass + + +# ---------------- Unary Nodes ------------------ + +class UnaryNode(ExpressionNode): + def __init__(self, expr, token): + self.expr = expr + self.line = token.lineno + self.col = token.column + + +class NegationNode(UnaryNode): + pass + + +class LogicNegationNode(UnaryNode): + pass + + +class IsVoidNode(UnaryNode): + pass + + +# ---------------- Atomic Nodes ------------------ + +class AtomicNode(ExpressionNode): + def __init__(self, token): + try: + self.id = token.value + self.line = token.lineno + self.col = token.column + except: + self.id = token + self.line = 0 + self.col = 0 + + +class NewNode(AtomicNode): + pass + + +class IdNode(AtomicNode): + pass + + +class IntNode(AtomicNode): + pass + + +class BoolNode(AtomicNode): + pass + + +class StringNode(AtomicNode): + pass + + +class VoidNode(AtomicNode): + pass + + +class SelfNode(): + def __init__(self, name, line, col): + self.name = name + self.line = line + self.col = col diff --git a/src/utils/errors.py b/src/utils/errors.py new file mode 100644 index 000000000..6df03f6fd --- /dev/null +++ b/src/utils/errors.py @@ -0,0 +1,42 @@ +class CoolError(Exception): + def __init__(self, error_type, text, line, column): + self.type = error_type + self.text = text + self.line = line + self.column = column + + def __str__(self): + return f'{self.line, self.column} - {self.type}: {self.text}' + + def __repr__(self): + return str(self) + + +class LexicographicError(CoolError): + def __init__(self, text, line, column): + super().__init__('LexicographicError', text, line, column) + + +class SyntacticError(CoolError): + def __init__(self, text, line, column): + super().__init__('SyntacticError', text, line, column) + + +class SemanticError(CoolError): + def __init__(self, text, line, column): + super().__init__('SemanticError', text, line, column) + + +class TypexError(CoolError): + def __init__(self, text, line, column): + super().__init__('TypeError', text, line, column) + + +class NamexError(CoolError): + def __init__(self, text, line, column): + super().__init__('NameError', text, line, column) + + +class AttributexError(CoolError): + def __init__(self, text, line, column): + super().__init__('AttributeError', text, line, column) diff --git a/src/utils/utils.py b/src/utils/utils.py new file mode 100644 index 000000000..57794461c --- /dev/null +++ b/src/utils/utils.py @@ -0,0 +1,99 @@ +import itertools +from semantic.semantic import SelfType + + +def find_column(text, pos): + line_start = text.rfind('\n', 0, pos) + 1 + return (pos - line_start) + 1 + + +class Token: + def __init__(self, lex, token_type, row, column): + self.lex = lex + self.type = token_type + self.lineno = row + self.column = column + + def __str__(self): + return f'{self.type}: {self.lex} ({self.lineno}, {self.column})' + + def __repr__(self): + return str(self) + + +reserved = { + 'class': 'CLASS', + 'else': 'ELSE', + 'fi': 'FI', + 'if': 'IF', + 'in': 'IN', + 'inherits': 'INHERITS', + 'isvoid': 'ISVOID', + 'let': 'LET', + 'loop': 'LOOP', + 'pool': 'POOL', + 'then': 'THEN', + 'while': 'WHILE', + 'case': 'CASE', + 'esac': 'ESAC', + 'new': 'NEW', + 'of': 'OF', + 'not': 'LNOT', + 'true': 'TRUE', + 'false': 'FALSE' +} + +tokens = [ + 'ID', + 'TYPE', + 'LPAREN', + 'RPAREN', + 'LBRACE', + 'RBRACE', + 'COLON', + 'SEMICOLON', + 'COMMA', + 'DOT', + 'AT', + 'ASSIGN', + 'PLUS', + 'MINUS', + 'STAR', + 'DIV', + 'EQUAL', + 'LESS', + 'LESSEQ', + 'ARROW', + 'INT', + 'STRING', + 'NOT' +] + list(reserved.values()) + + +def path_to_objet(typex): + path = [] + c_type = typex + + while c_type: + path.append(c_type) + + c_type = c_type.parent + + path.reverse() + return path + + +def get_common_basetype(types): + paths = [path_to_objet(typex) for typex in types] + tuples = zip(*paths) + + for i, t in enumerate(tuples): + gr = itertools.groupby(t) + if len(list(gr)) > 1: + return paths[0][i-1] + + return paths[0][-1] + + +def get_type(typex, current_type): + return current_type if typex == SelfType() else typex diff --git a/src/utils/visitor.py b/src/utils/visitor.py new file mode 100644 index 000000000..d4f2e4d7c --- /dev/null +++ b/src/utils/visitor.py @@ -0,0 +1,82 @@ +# The MIT License (MIT) +# +# Copyright (c) 2013 Curtis Schlak +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import inspect + +__all__ = ['on', 'when'] + + +def on(param_name): + def f(fn): + dispatcher = Dispatcher(param_name, fn) + return dispatcher + return f + + +def when(param_type): + def f(fn): + frame = inspect.currentframe().f_back + func_name = fn.func_name if 'func_name' in dir(fn) else fn.__name__ + dispatcher = frame.f_locals[func_name] + if not isinstance(dispatcher, Dispatcher): + dispatcher = dispatcher.dispatcher + dispatcher.add_target(param_type, fn) + + def ff(*args, **kw): + return dispatcher(*args, **kw) + ff.dispatcher = dispatcher + return ff + return f + + +class Dispatcher(object): + def __init__(self, param_name, fn): + frame = inspect.currentframe().f_back.f_back + top_level = frame.f_locals == frame.f_globals + self.param_index = self.__argspec(fn).args.index(param_name) + self.param_name = param_name + self.targets = {} + + def __call__(self, *args, **kw): + typ = args[self.param_index].__class__ + d = self.targets.get(typ) + if d is not None: + return d(*args, **kw) + else: + issub = issubclass + t = self.targets + ks = t.keys() + ans = [t[k](*args, **kw) for k in ks if issub(typ, k)] + if len(ans) == 1: + return ans.pop() + return ans + + def add_target(self, typ, target): + self.targets[typ] = target + + @staticmethod + def __argspec(fn): + # Support for Python 3 type hints requires inspect.getfullargspec + if hasattr(inspect, 'getfullargspec'): + return inspect.getfullargspec(fn) + else: + return inspect.getargspec(fn)