def visit_type(self, target: ast.AST) -> Optional[IType]: """ Gets the type by its identifier :param target: ast node to be evaluated :return: the type of the value inside the node. None by default """ target_type = self.visit(target) # Type:str or IType if isinstance(target_type, str) and not isinstance(target, ast.Str): symbol = self.get_symbol(target_type) if symbol is None: # the symbol doesn't exists self._log_error( CompilerError.UnresolvedReference(target.lineno, target.col_offset, target_type)) target_type = symbol if target_type is None and not isinstance(target, ast.NameConstant): # the value type is invalid return None if not isinstance(target_type, (ast.AST, IType, IExpression)) and isinstance( target_type, ISymbol): self._log_error( CompilerError.UnresolvedReference(line=target.lineno, col=target.col_offset, symbol_id=str(target_type))) return self.get_type(target_type)
def get_values_type(self, value: ast.AST) -> Iterable[Optional[IType]]: """ Verifies if it is a multiple assignments statement :param value: the python ast subscription node """ value_type: Optional[IType] = None if isinstance(value, (ast.Subscript, ast.Attribute, ast.Tuple)): # index is another subscription value_type = self.visit(value) elif isinstance(value, ast.Name): # index is an identifier value_type = self.get_symbol(value.id) types: Iterable[Optional[IType]] = value_type if isinstance( value_type, Iterable) else [value_type] for tpe in types: if not isinstance(tpe, IType): # type hint not using identifiers or using identifiers that are not types index = self.visit(value) self._log_error( CompilerError.UnresolvedReference(value.lineno, value.col_offset, index)) return types
def validate_type_variable_assign(self, node: ast.AST, value: Any, target: Any = None) -> bool: value_type: IType = self.get_type(value) if isinstance(value, ast.AST): value = self.visit(value) if target is not None: target_type = self.get_type(target) elif not isinstance(node, ast.Name): target_type = self.get_type(node) else: var: ISymbol = self.get_symbol(node.id) if not isinstance(var, Variable): self._log_error( CompilerError.UnresolvedReference( node.lineno, node.col_offset, symbol_id=node.id )) return False if var.type is None: # it is an declaration with assignment and the value is neither literal nor another variable var.set_type(value_type) target_type = var.type if not target_type.is_type_of(value_type) and value != target_type.default_value: self._log_error( CompilerError.MismatchedTypes( node.lineno, node.col_offset, actual_type_id=value_type.identifier, expected_type_id=target_type.identifier )) return False return True
def visit_Call(self, call: ast.Call) -> Optional[IType]: """ Visitor of a function call node :param call: the python ast function call node :return: the result type of the called function. None if the function is not found """ func_id = self.visit(call.func) func_symbol = self.get_symbol(func_id) # if func_symbol is None, the called function may be a function written after in the code # that's why it shouldn't log a compiler error here if func_symbol is None: return None if not isinstance(func_symbol, Callable): # verifiy if it is a builtin method with its name shadowed func = Builtin.get_symbol(func_id) func_symbol = func if func is not None else func_symbol func_symbol = Builtin.Exception if func_symbol is Type.exception else func_symbol if not isinstance(func_symbol, Callable): # the symbol doesn't exists self._log_error( CompilerError.UnresolvedReference(call.func.lineno, call.func.col_offset, func_id)) elif isinstance(func_symbol, IBuiltinMethod): if func_symbol.body is not None: self._builtin_functions_to_visit[func_id] = func_symbol elif func_symbol is Builtin.NewEvent: new_event = self.create_new_event(call) self.__include_callable(new_event.identifier, new_event) self._current_event = new_event return self.get_type(call.func)
def visit_Compare(self, compare: ast.Compare) -> Optional[IType]: """ Verifies if the types of the operands are valid to the compare operations If the operations are valid, changes de Python operator by the Boa operator in the syntax tree :param compare: the python ast compare operation node :return: the type of the result of the operation if the operation is valid. Otherwise, returns None :rtype: IType or None """ if len(compare.comparators) != len(compare.ops): self._log_error( CompilerError.IncorrectNumberOfOperands( compare.lineno, compare.col_offset, len(compare.ops), len(compare.comparators) + 1 # num comparators + compare.left ) ) line = compare.lineno col = compare.col_offset try: return_type = None l_operand = self.visit_value(compare.left) for index, op in enumerate(compare.ops): operator: Operator = self.get_operator(op) r_operand = self.visit_value(compare.comparators[index]) if not isinstance(operator, Operator): # the operator is invalid or it was not implemented yet self._log_error( CompilerError.UnresolvedReference(line, col, type(op).__name__) ) operation: IOperation = self.get_bin_op(operator, r_operand, l_operand) if operation is None: self._log_error( CompilerError.NotSupportedOperation(line, col, operator) ) elif not operation.is_supported: # TODO: is, is not and eq were not implemented yet self._log_error( CompilerError.NotSupportedOperation(line, col, operator) ) else: compare.ops[index] = operation return_type = operation.result line = compare.comparators[index].lineno col = compare.comparators[index].col_offset l_operand = r_operand return return_type except CompilerError.MismatchedTypes as raised_error: raised_error.line = line raised_error.col = col # raises the exception with the line/col info self._log_error(raised_error)
def visit_type(self, target: ast.AST) -> Optional[IType]: """ Gets the type by its identifier :param target: ast node to be evaluated :return: the type of the value inside the node. None by default """ target_type = self.visit(target) # Type:str or IType if isinstance(target_type, str) and not isinstance(target, ast.Str): symbol = self.get_symbol(target_type) if symbol is None: # the symbol doesn't exists self._log_error( CompilerError.UnresolvedReference(target.lineno, target.col_offset, target_type)) target_type = symbol if target_type is None and not isinstance(target, ast.NameConstant): # the value type is invalid return None if not isinstance(target_type, (ast.AST, IType, IExpression)) and isinstance( target_type, ISymbol): self._log_error( CompilerError.UnresolvedReference(line=target.lineno, col=target.col_offset, symbol_id=str(target_type))) if isinstance(target_type, ClassType): args = [] for arg in target.args: result = self.visit(arg) if (isinstance(result, str) and not isinstance(arg, (ast.Str, ast.Constant)) and result in self._current_scope.symbols): result = self.get_type(self._current_scope.symbols[result]) args.append(result) init = target_type.constructor_method() if hasattr(init, 'build'): init = init.build(args) target_type = init.return_type return self.get_type(target_type)
def _log_unresolved_import(self, origin_node: ast.AST, import_id: str): if self._log: self._log_error( CompilerError.UnresolvedReference( line=origin_node.lineno, col=origin_node.col_offset, symbol_id=import_id ) )
def visit_Module(self, module: ast.Module): """ Visitor of the module node Fills module symbol table :param module: """ mod: Module = Module() self._current_module = mod self._scope_stack.append(SymbolScope()) global_stmts = [] function_stmts = [] for stmt in module.body: if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)): function_stmts.append(stmt) elif not (isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Constant)): # don't evaluate constant expression - for example: string for documentation if self.visit(stmt) is not Builtin.Event: global_stmts.append(stmt) module.body = global_stmts + function_stmts for var_id, var in mod.variables.items(): # all static fields must be initialized if not mod.is_variable_assigned(var_id): self._log_error( CompilerError.UnresolvedReference( line=var.origin.lineno if var.origin is not None else 0, col=var.origin.col_offset if var.origin is not None else 0, symbol_id=var_id)) for stmt in function_stmts: result = self.visit(stmt) # don't evaluate the metadata function in the following analysers if result is Builtin.Metadata: module.body.remove(stmt) # TODO: include the body of the builtin methods to the ast # TODO: get module name self.modules['main'] = mod module_scope = self._scope_stack.pop() for symbol_id, symbol in module_scope.symbols.items(): if symbol_id in self._global_assigned_variables: mod.include_symbol(symbol_id, symbol) mod.assign_variable(symbol_id) self._global_assigned_variables.clear() self._current_module = None
def visit_Return(self, ret: ast.Return): """ Visitor of the function return node If the return is a name, verifies if the symbol is defined :param ret: the python ast return node """ if isinstance(ret.value, ast.Name): symbol_id = self.visit(ret.value) symbol = self.get_symbol(symbol_id) if symbol is None: # the symbol doesn't exists self._log_error( CompilerError.UnresolvedReference(ret.value.lineno, ret.value.col_offset, symbol_id) )
def visit_BoolOp(self, bool_op: ast.BoolOp) -> Optional[IType]: """ Verifies if the types of the operands are valid to the boolean operations If the operations are valid, changes de Python operator by the Boa operator in the syntax tree :param bool_op: the python ast boolean operation node :return: the type of the result of the operation if the operation is valid. Otherwise, returns None :rtype: IType or None """ lineno: int = bool_op.lineno col_offset: int = bool_op.col_offset try: return_type: IType = None bool_operation: IOperation = None operator: Operator = self.get_operator(bool_op.op) if not isinstance(operator, Operator): # the operator is invalid or it was not implemented yet self._log_error( CompilerError.UnresolvedReference(lineno, col_offset, type(operator).__name__) ) l_operand = self.visit(bool_op.values[0]) for index, operand in enumerate(bool_op.values[1:]): r_operand = self.visit(operand) operation: IOperation = self.get_bin_op(operator, r_operand, l_operand) if operation is None: self._log_error( CompilerError.NotSupportedOperation(lineno, col_offset, operator) ) elif bool_operation is None: return_type = operation.result bool_operation = operation lineno = operand.lineno col_offset = operand.col_offset l_operand = r_operand bool_op.op = bool_operation return return_type except CompilerError.MismatchedTypes as raised_error: raised_error.line = lineno raised_error.col = col_offset # raises the exception with the line/col info self._log_error(raised_error)
def visit_Expr(self, expr: ast.Expr): """ Visitor of the atom expression node :param expr: """ value = self.visit(expr.value) if isinstance(expr.value, ast.Name): if ( # it is not a symbol of the method scope (self._current_method is not None and value not in self._current_method.symbols) # nor it is a symbol of the module scope or (self._current_module is not None and value not in self._current_module.symbols) # nor it is a symbol of the global scope or value not in self.symbols ): self._log_error( CompilerError.UnresolvedReference(expr.value.lineno, expr.value.col_offset, value) )
def visit_Call(self, call: ast.Call): """ Verifies if the number of arguments is correct :param call: the python ast function call node :return: the result type of the called function """ if isinstance(call.func, ast.Name): callable_id: str = call.func.id callable_target = self.get_symbol(callable_id) else: callable_id, callable_target = self.get_callable_and_update_args(call) # type: str, ISymbol callable_target = self.validate_builtin_callable(callable_id, callable_target) if not isinstance(callable_target, Callable): # the symbol doesn't exists or is not a function self._log_error( CompilerError.UnresolvedReference(call.func.lineno, call.func.col_offset, callable_id) ) else: if callable_target is Builtin.NewEvent: return callable_target.return_type # TODO: change when kwargs is implemented if len(call.keywords) > 0: raise NotImplementedError if self.validate_callable_arguments(call, callable_target): args = [self.get_type(param) for param in call.args] if isinstance(callable_target, IBuiltinMethod): # if the arguments are not generic, build the specified method callable_target: IBuiltinMethod = callable_target.build(args) if not callable_target.is_supported: self._log_error( CompilerError.NotSupportedOperation(call.lineno, call.col_offset, callable_id) ) return callable_target.return_type self.validate_passed_arguments(call, args, callable_id, callable_target) self.update_callable_after_validation(call, callable_id, callable_target) return self.get_type(callable_target)
def validate_binary_operation(self, node: ast.AST, left_op: ast.AST, right_op: ast.AST) -> Optional[IOperation]: """ Validates a ast node that represents a binary operation :param node: ast node that represents a binary operation :param left_op: ast node that represents the left operand of the operation :param right_op: ast node that represents the right operand of the operation :return: the corresponding :class:`BinaryOperation` if is valid. None otherwise. """ if not hasattr(node, 'op'): return operator: Operator = self.get_operator(node.op) l_operand = self.visit(left_op) r_operand = self.visit(right_op) if not isinstance(operator, Operator): # the operator is invalid or it was not implemented yet self._log_error( CompilerError.UnresolvedReference(node.lineno, node.col_offset, type(node.op).__name__) ) try: operation: IOperation = self.get_bin_op(operator, r_operand, l_operand) if operation is None: self._log_error( CompilerError.NotSupportedOperation(node.lineno, node.col_offset, operator) ) elif not operation.is_supported: # TODO: concat and power not implemented yet # number float division is not supported by Neo VM self._log_error( CompilerError.NotSupportedOperation(node.lineno, node.col_offset, operator) ) else: return operation except CompilerError.MismatchedTypes as raised_error: raised_error.line = node.lineno raised_error.col = node.col_offset # raises the exception with the line/col info self._log_error(raised_error)
def visit_UnaryOp(self, un_op: ast.UnaryOp) -> Optional[IType]: """ Verifies if the type of the operand is valid to the operation If the operation is valid, changes de Python operator by the Boa operator in the syntax tree :param un_op: the python ast unary operation node :return: the type of the result of the operation if the operation is valid. Otherwise, returns None :rtype: IType or None """ operator: Operator = self.get_operator(un_op.op) operand = self.visit(un_op.operand) if not isinstance(operator, Operator): # the operator is invalid or it was not implemented yet self._log_error( CompilerError.UnresolvedReference(un_op.lineno, un_op.col_offset, type(un_op.op).__name__) ) try: operation: UnaryOperation = self.get_un_op(operator, operand) if operation is None: self._log_error( CompilerError.NotSupportedOperation(un_op.lineno, un_op.col_offset, operator) ) elif not operation.is_supported: self._log_error( CompilerError.NotSupportedOperation(un_op.lineno, un_op.col_offset, operator) ) else: un_op.op = operation return operation.result except CompilerError.MismatchedTypes as raised_error: raised_error.line = un_op.lineno raised_error.col = un_op.col_offset # raises the exception with the line/col info self._log_error(raised_error)