Index: third_party/cython/src/Cython/Compiler/ExprNodes.py |
diff --git a/third_party/cython/src/Cython/Compiler/ExprNodes.py b/third_party/cython/src/Cython/Compiler/ExprNodes.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..74e1004fd4b4a812a6b841ab26c78bff86bbd6ff |
--- /dev/null |
+++ b/third_party/cython/src/Cython/Compiler/ExprNodes.py |
@@ -0,0 +1,11289 @@ |
+# |
+# Parse tree nodes for expressions |
+# |
+ |
+import cython |
+cython.declare(error=object, warning=object, warn_once=object, InternalError=object, |
+ CompileError=object, UtilityCode=object, TempitaUtilityCode=object, |
+ StringEncoding=object, operator=object, |
+ Naming=object, Nodes=object, PyrexTypes=object, py_object_type=object, |
+ list_type=object, tuple_type=object, set_type=object, dict_type=object, |
+ unicode_type=object, str_type=object, bytes_type=object, type_type=object, |
+ Builtin=object, Symtab=object, Utils=object, find_coercion_error=object, |
+ debug_disposal_code=object, debug_temp_alloc=object, debug_coercion=object, |
+ bytearray_type=object, slice_type=object) |
+ |
+import sys |
+import copy |
+import operator |
+ |
+from Errors import error, warning, warn_once, InternalError, CompileError |
+from Errors import hold_errors, release_errors, held_errors, report_error |
+from Code import UtilityCode, TempitaUtilityCode |
+import StringEncoding |
+import Naming |
+import Nodes |
+from Nodes import Node |
+import PyrexTypes |
+from PyrexTypes import py_object_type, c_long_type, typecast, error_type, \ |
+ unspecified_type |
+import TypeSlots |
+from Builtin import list_type, tuple_type, set_type, dict_type, type_type, \ |
+ unicode_type, str_type, bytes_type, bytearray_type, basestring_type, slice_type |
+import Builtin |
+import Symtab |
+from Cython import Utils |
+from Annotate import AnnotationItem |
+from Cython.Compiler import Future |
+from Cython.Debugging import print_call_chain |
+from DebugFlags import debug_disposal_code, debug_temp_alloc, \ |
+ debug_coercion |
+ |
+try: |
+ from __builtin__ import basestring |
+except ImportError: |
+ basestring = str # Python 3 |
+ |
+try: |
+ from builtins import bytes |
+except ImportError: |
+ bytes = str # Python 2 |
+ |
+ |
+class NotConstant(object): |
+ _obj = None |
+ |
+ def __new__(cls): |
+ if NotConstant._obj is None: |
+ NotConstant._obj = super(NotConstant, cls).__new__(cls) |
+ |
+ return NotConstant._obj |
+ |
+ def __repr__(self): |
+ return "<NOT CONSTANT>" |
+ |
+not_a_constant = NotConstant() |
+constant_value_not_set = object() |
+ |
+# error messages when coercing from key[0] to key[1] |
+coercion_error_dict = { |
+ # string related errors |
+ (Builtin.unicode_type, Builtin.bytes_type) : "Cannot convert Unicode string to 'bytes' implicitly, encoding required.", |
+ (Builtin.unicode_type, Builtin.str_type) : "Cannot convert Unicode string to 'str' implicitly. This is not portable and requires explicit encoding.", |
+ (Builtin.unicode_type, PyrexTypes.c_char_ptr_type) : "Unicode objects only support coercion to Py_UNICODE*.", |
+ (Builtin.unicode_type, PyrexTypes.c_uchar_ptr_type) : "Unicode objects only support coercion to Py_UNICODE*.", |
+ (Builtin.bytes_type, Builtin.unicode_type) : "Cannot convert 'bytes' object to unicode implicitly, decoding required", |
+ (Builtin.bytes_type, Builtin.str_type) : "Cannot convert 'bytes' object to str implicitly. This is not portable to Py3.", |
+ (Builtin.bytes_type, Builtin.basestring_type) : "Cannot convert 'bytes' object to basestring implicitly. This is not portable to Py3.", |
+ (Builtin.bytes_type, PyrexTypes.c_py_unicode_ptr_type) : "Cannot convert 'bytes' object to Py_UNICODE*, use 'unicode'.", |
+ (Builtin.basestring_type, Builtin.bytes_type) : "Cannot convert 'basestring' object to bytes implicitly. This is not portable.", |
+ (Builtin.str_type, Builtin.unicode_type) : "str objects do not support coercion to unicode, use a unicode string literal instead (u'')", |
+ (Builtin.str_type, Builtin.bytes_type) : "Cannot convert 'str' to 'bytes' implicitly. This is not portable.", |
+ (Builtin.str_type, PyrexTypes.c_char_ptr_type) : "'str' objects do not support coercion to C types (use 'bytes'?).", |
+ (Builtin.str_type, PyrexTypes.c_uchar_ptr_type) : "'str' objects do not support coercion to C types (use 'bytes'?).", |
+ (Builtin.str_type, PyrexTypes.c_py_unicode_ptr_type) : "'str' objects do not support coercion to C types (use 'unicode'?).", |
+ (PyrexTypes.c_char_ptr_type, Builtin.unicode_type) : "Cannot convert 'char*' to unicode implicitly, decoding required", |
+ (PyrexTypes.c_uchar_ptr_type, Builtin.unicode_type) : "Cannot convert 'char*' to unicode implicitly, decoding required", |
+} |
+ |
+def find_coercion_error(type_tuple, default, env): |
+ err = coercion_error_dict.get(type_tuple) |
+ if err is None: |
+ return default |
+ elif ((PyrexTypes.c_char_ptr_type in type_tuple or PyrexTypes.c_uchar_ptr_type in type_tuple) |
+ and env.directives['c_string_encoding']): |
+ if type_tuple[1].is_pyobject: |
+ return default |
+ elif env.directives['c_string_encoding'] in ('ascii', 'default'): |
+ return default |
+ else: |
+ return "'%s' objects do not support coercion to C types with non-ascii or non-default c_string_encoding" % type_tuple[0].name |
+ else: |
+ return err |
+ |
+ |
+def default_str_type(env): |
+ return { |
+ 'bytes': bytes_type, |
+ 'bytearray': bytearray_type, |
+ 'str': str_type, |
+ 'unicode': unicode_type |
+ }.get(env.directives['c_string_type']) |
+ |
+ |
+def check_negative_indices(*nodes): |
+ """ |
+ Raise a warning on nodes that are known to have negative numeric values. |
+ Used to find (potential) bugs inside of "wraparound=False" sections. |
+ """ |
+ for node in nodes: |
+ if (node is None |
+ or not isinstance(node.constant_result, (int, float, long))): |
+ continue |
+ if node.constant_result < 0: |
+ warning(node.pos, |
+ "the result of using negative indices inside of " |
+ "code sections marked as 'wraparound=False' is " |
+ "undefined", level=1) |
+ |
+ |
+def infer_sequence_item_type(env, seq_node, index_node=None, seq_type=None): |
+ if not seq_node.is_sequence_constructor: |
+ if seq_type is None: |
+ seq_type = seq_node.infer_type(env) |
+ if seq_type is tuple_type: |
+ # tuples are immutable => we can safely follow assignments |
+ if seq_node.cf_state and len(seq_node.cf_state) == 1: |
+ try: |
+ seq_node = seq_node.cf_state[0].rhs |
+ except AttributeError: |
+ pass |
+ if seq_node is not None and seq_node.is_sequence_constructor: |
+ if index_node is not None and index_node.has_constant_result(): |
+ try: |
+ item = seq_node.args[index_node.constant_result] |
+ except (ValueError, TypeError, IndexError): |
+ pass |
+ else: |
+ return item.infer_type(env) |
+ # if we're lucky, all items have the same type |
+ item_types = set([item.infer_type(env) for item in seq_node.args]) |
+ if len(item_types) == 1: |
+ return item_types.pop() |
+ return None |
+ |
+ |
+class ExprNode(Node): |
+ # subexprs [string] Class var holding names of subexpr node attrs |
+ # type PyrexType Type of the result |
+ # result_code string Code fragment |
+ # result_ctype string C type of result_code if different from type |
+ # is_temp boolean Result is in a temporary variable |
+ # is_sequence_constructor |
+ # boolean Is a list or tuple constructor expression |
+ # is_starred boolean Is a starred expression (e.g. '*a') |
+ # saved_subexpr_nodes |
+ # [ExprNode or [ExprNode or None] or None] |
+ # Cached result of subexpr_nodes() |
+ # use_managed_ref boolean use ref-counted temps/assignments/etc. |
+ # result_is_used boolean indicates that the result will be dropped and the |
+ # result_code/temp_result can safely be set to None |
+ |
+ result_ctype = None |
+ type = None |
+ temp_code = None |
+ old_temp = None # error checker for multiple frees etc. |
+ use_managed_ref = True # can be set by optimisation transforms |
+ result_is_used = True |
+ |
+ # The Analyse Expressions phase for expressions is split |
+ # into two sub-phases: |
+ # |
+ # Analyse Types |
+ # Determines the result type of the expression based |
+ # on the types of its sub-expressions, and inserts |
+ # coercion nodes into the expression tree where needed. |
+ # Marks nodes which will need to have temporary variables |
+ # allocated. |
+ # |
+ # Allocate Temps |
+ # Allocates temporary variables where needed, and fills |
+ # in the result_code field of each node. |
+ # |
+ # ExprNode provides some convenience routines which |
+ # perform both of the above phases. These should only |
+ # be called from statement nodes, and only when no |
+ # coercion nodes need to be added around the expression |
+ # being analysed. In that case, the above two phases |
+ # should be invoked separately. |
+ # |
+ # Framework code in ExprNode provides much of the common |
+ # processing for the various phases. It makes use of the |
+ # 'subexprs' class attribute of ExprNodes, which should |
+ # contain a list of the names of attributes which can |
+ # hold sub-nodes or sequences of sub-nodes. |
+ # |
+ # The framework makes use of a number of abstract methods. |
+ # Their responsibilities are as follows. |
+ # |
+ # Declaration Analysis phase |
+ # |
+ # analyse_target_declaration |
+ # Called during the Analyse Declarations phase to analyse |
+ # the LHS of an assignment or argument of a del statement. |
+ # Nodes which cannot be the LHS of an assignment need not |
+ # implement it. |
+ # |
+ # Expression Analysis phase |
+ # |
+ # analyse_types |
+ # - Call analyse_types on all sub-expressions. |
+ # - Check operand types, and wrap coercion nodes around |
+ # sub-expressions where needed. |
+ # - Set the type of this node. |
+ # - If a temporary variable will be required for the |
+ # result, set the is_temp flag of this node. |
+ # |
+ # analyse_target_types |
+ # Called during the Analyse Types phase to analyse |
+ # the LHS of an assignment or argument of a del |
+ # statement. Similar responsibilities to analyse_types. |
+ # |
+ # target_code |
+ # Called by the default implementation of allocate_target_temps. |
+ # Should return a C lvalue for assigning to the node. The default |
+ # implementation calls calculate_result_code. |
+ # |
+ # check_const |
+ # - Check that this node and its subnodes form a |
+ # legal constant expression. If so, do nothing, |
+ # otherwise call not_const. |
+ # |
+ # The default implementation of check_const |
+ # assumes that the expression is not constant. |
+ # |
+ # check_const_addr |
+ # - Same as check_const, except check that the |
+ # expression is a C lvalue whose address is |
+ # constant. Otherwise, call addr_not_const. |
+ # |
+ # The default implementation of calc_const_addr |
+ # assumes that the expression is not a constant |
+ # lvalue. |
+ # |
+ # Code Generation phase |
+ # |
+ # generate_evaluation_code |
+ # - Call generate_evaluation_code for sub-expressions. |
+ # - Perform the functions of generate_result_code |
+ # (see below). |
+ # - If result is temporary, call generate_disposal_code |
+ # on all sub-expressions. |
+ # |
+ # A default implementation of generate_evaluation_code |
+ # is provided which uses the following abstract methods: |
+ # |
+ # generate_result_code |
+ # - Generate any C statements necessary to calculate |
+ # the result of this node from the results of its |
+ # sub-expressions. |
+ # |
+ # calculate_result_code |
+ # - Should return a C code fragment evaluating to the |
+ # result. This is only called when the result is not |
+ # a temporary. |
+ # |
+ # generate_assignment_code |
+ # Called on the LHS of an assignment. |
+ # - Call generate_evaluation_code for sub-expressions. |
+ # - Generate code to perform the assignment. |
+ # - If the assignment absorbed a reference, call |
+ # generate_post_assignment_code on the RHS, |
+ # otherwise call generate_disposal_code on it. |
+ # |
+ # generate_deletion_code |
+ # Called on an argument of a del statement. |
+ # - Call generate_evaluation_code for sub-expressions. |
+ # - Generate code to perform the deletion. |
+ # - Call generate_disposal_code on all sub-expressions. |
+ # |
+ # |
+ |
+ is_sequence_constructor = 0 |
+ is_string_literal = 0 |
+ is_attribute = 0 |
+ is_subscript = 0 |
+ |
+ saved_subexpr_nodes = None |
+ is_temp = 0 |
+ is_target = 0 |
+ is_starred = 0 |
+ |
+ constant_result = constant_value_not_set |
+ |
+ # whether this node with a memoryview type should be broadcast |
+ memslice_broadcast = False |
+ |
+ child_attrs = property(fget=operator.attrgetter('subexprs')) |
+ |
+ def not_implemented(self, method_name): |
+ print_call_chain(method_name, "not implemented") ### |
+ raise InternalError( |
+ "%s.%s not implemented" % |
+ (self.__class__.__name__, method_name)) |
+ |
+ def is_lvalue(self): |
+ return 0 |
+ |
+ def is_addressable(self): |
+ return self.is_lvalue() and not self.type.is_memoryviewslice |
+ |
+ def is_ephemeral(self): |
+ # An ephemeral node is one whose result is in |
+ # a Python temporary and we suspect there are no |
+ # other references to it. Certain operations are |
+ # disallowed on such values, since they are |
+ # likely to result in a dangling pointer. |
+ return self.type.is_pyobject and self.is_temp |
+ |
+ def subexpr_nodes(self): |
+ # Extract a list of subexpression nodes based |
+ # on the contents of the subexprs class attribute. |
+ nodes = [] |
+ for name in self.subexprs: |
+ item = getattr(self, name) |
+ if item is not None: |
+ if type(item) is list: |
+ nodes.extend(item) |
+ else: |
+ nodes.append(item) |
+ return nodes |
+ |
+ def result(self): |
+ if self.is_temp: |
+ return self.temp_code |
+ else: |
+ return self.calculate_result_code() |
+ |
+ def result_as(self, type = None): |
+ # Return the result code cast to the specified C type. |
+ if (self.is_temp and self.type.is_pyobject and |
+ type != py_object_type): |
+ # Allocated temporaries are always PyObject *, which may not |
+ # reflect the actual type (e.g. an extension type) |
+ return typecast(type, py_object_type, self.result()) |
+ return typecast(type, self.ctype(), self.result()) |
+ |
+ def py_result(self): |
+ # Return the result code cast to PyObject *. |
+ return self.result_as(py_object_type) |
+ |
+ def ctype(self): |
+ # Return the native C type of the result (i.e. the |
+ # C type of the result_code expression). |
+ return self.result_ctype or self.type |
+ |
+ def get_constant_c_result_code(self): |
+ # Return the constant value of this node as a result code |
+ # string, or None if the node is not constant. This method |
+ # can be called when the constant result code is required |
+ # before the code generation phase. |
+ # |
+ # The return value is a string that can represent a simple C |
+ # value, a constant C name or a constant C expression. If the |
+ # node type depends on Python code, this must return None. |
+ return None |
+ |
+ def calculate_constant_result(self): |
+ # Calculate the constant compile time result value of this |
+ # expression and store it in ``self.constant_result``. Does |
+ # nothing by default, thus leaving ``self.constant_result`` |
+ # unknown. If valid, the result can be an arbitrary Python |
+ # value. |
+ # |
+ # This must only be called when it is assured that all |
+ # sub-expressions have a valid constant_result value. The |
+ # ConstantFolding transform will do this. |
+ pass |
+ |
+ def has_constant_result(self): |
+ return self.constant_result is not constant_value_not_set and \ |
+ self.constant_result is not not_a_constant |
+ |
+ def compile_time_value(self, denv): |
+ # Return value of compile-time expression, or report error. |
+ error(self.pos, "Invalid compile-time expression") |
+ |
+ def compile_time_value_error(self, e): |
+ error(self.pos, "Error in compile-time expression: %s: %s" % ( |
+ e.__class__.__name__, e)) |
+ |
+ # ------------- Declaration Analysis ---------------- |
+ |
+ def analyse_target_declaration(self, env): |
+ error(self.pos, "Cannot assign to or delete this") |
+ |
+ # ------------- Expression Analysis ---------------- |
+ |
+ def analyse_const_expression(self, env): |
+ # Called during the analyse_declarations phase of a |
+ # constant expression. Analyses the expression's type, |
+ # checks whether it is a legal const expression, |
+ # and determines its value. |
+ node = self.analyse_types(env) |
+ node.check_const() |
+ return node |
+ |
+ def analyse_expressions(self, env): |
+ # Convenience routine performing both the Type |
+ # Analysis and Temp Allocation phases for a whole |
+ # expression. |
+ return self.analyse_types(env) |
+ |
+ def analyse_target_expression(self, env, rhs): |
+ # Convenience routine performing both the Type |
+ # Analysis and Temp Allocation phases for the LHS of |
+ # an assignment. |
+ return self.analyse_target_types(env) |
+ |
+ def analyse_boolean_expression(self, env): |
+ # Analyse expression and coerce to a boolean. |
+ node = self.analyse_types(env) |
+ bool = node.coerce_to_boolean(env) |
+ return bool |
+ |
+ def analyse_temp_boolean_expression(self, env): |
+ # Analyse boolean expression and coerce result into |
+ # a temporary. This is used when a branch is to be |
+ # performed on the result and we won't have an |
+ # opportunity to ensure disposal code is executed |
+ # afterwards. By forcing the result into a temporary, |
+ # we ensure that all disposal has been done by the |
+ # time we get the result. |
+ node = self.analyse_types(env) |
+ return node.coerce_to_boolean(env).coerce_to_simple(env) |
+ |
+ # --------------- Type Inference ----------------- |
+ |
+ def type_dependencies(self, env): |
+ # Returns the list of entries whose types must be determined |
+ # before the type of self can be inferred. |
+ if hasattr(self, 'type') and self.type is not None: |
+ return () |
+ return sum([node.type_dependencies(env) for node in self.subexpr_nodes()], ()) |
+ |
+ def infer_type(self, env): |
+ # Attempt to deduce the type of self. |
+ # Differs from analyse_types as it avoids unnecessary |
+ # analysis of subexpressions, but can assume everything |
+ # in self.type_dependencies() has been resolved. |
+ if hasattr(self, 'type') and self.type is not None: |
+ return self.type |
+ elif hasattr(self, 'entry') and self.entry is not None: |
+ return self.entry.type |
+ else: |
+ self.not_implemented("infer_type") |
+ |
+ def nonlocally_immutable(self): |
+ # Returns whether this variable is a safe reference, i.e. |
+ # can't be modified as part of globals or closures. |
+ return self.is_literal or self.is_temp or self.type.is_array or self.type.is_cfunction |
+ |
+ # --------------- Type Analysis ------------------ |
+ |
+ def analyse_as_module(self, env): |
+ # If this node can be interpreted as a reference to a |
+ # cimported module, return its scope, else None. |
+ return None |
+ |
+ def analyse_as_type(self, env): |
+ # If this node can be interpreted as a reference to a |
+ # type, return that type, else None. |
+ return None |
+ |
+ def analyse_as_extension_type(self, env): |
+ # If this node can be interpreted as a reference to an |
+ # extension type or builtin type, return its type, else None. |
+ return None |
+ |
+ def analyse_types(self, env): |
+ self.not_implemented("analyse_types") |
+ |
+ def analyse_target_types(self, env): |
+ return self.analyse_types(env) |
+ |
+ def nogil_check(self, env): |
+ # By default, any expression based on Python objects is |
+ # prevented in nogil environments. Subtypes must override |
+ # this if they can work without the GIL. |
+ if self.type and self.type.is_pyobject: |
+ self.gil_error() |
+ |
+ def gil_assignment_check(self, env): |
+ if env.nogil and self.type.is_pyobject: |
+ error(self.pos, "Assignment of Python object not allowed without gil") |
+ |
+ def check_const(self): |
+ self.not_const() |
+ return False |
+ |
+ def not_const(self): |
+ error(self.pos, "Not allowed in a constant expression") |
+ |
+ def check_const_addr(self): |
+ self.addr_not_const() |
+ return False |
+ |
+ def addr_not_const(self): |
+ error(self.pos, "Address is not constant") |
+ |
+ # ----------------- Result Allocation ----------------- |
+ |
+ def result_in_temp(self): |
+ # Return true if result is in a temporary owned by |
+ # this node or one of its subexpressions. Overridden |
+ # by certain nodes which can share the result of |
+ # a subnode. |
+ return self.is_temp |
+ |
+ def target_code(self): |
+ # Return code fragment for use as LHS of a C assignment. |
+ return self.calculate_result_code() |
+ |
+ def calculate_result_code(self): |
+ self.not_implemented("calculate_result_code") |
+ |
+# def release_target_temp(self, env): |
+# # Release temporaries used by LHS of an assignment. |
+# self.release_subexpr_temps(env) |
+ |
+ def allocate_temp_result(self, code): |
+ if self.temp_code: |
+ raise RuntimeError("Temp allocated multiple times in %r: %r" % (self.__class__.__name__, self.pos)) |
+ type = self.type |
+ if not type.is_void: |
+ if type.is_pyobject: |
+ type = PyrexTypes.py_object_type |
+ self.temp_code = code.funcstate.allocate_temp( |
+ type, manage_ref=self.use_managed_ref) |
+ else: |
+ self.temp_code = None |
+ |
+ def release_temp_result(self, code): |
+ if not self.temp_code: |
+ if not self.result_is_used: |
+ # not used anyway, so ignore if not set up |
+ return |
+ if self.old_temp: |
+ raise RuntimeError("temp %s released multiple times in %s" % ( |
+ self.old_temp, self.__class__.__name__)) |
+ else: |
+ raise RuntimeError("no temp, but release requested in %s" % ( |
+ self.__class__.__name__)) |
+ code.funcstate.release_temp(self.temp_code) |
+ self.old_temp = self.temp_code |
+ self.temp_code = None |
+ |
+ # ---------------- Code Generation ----------------- |
+ |
+ def make_owned_reference(self, code): |
+ """ |
+ If result is a pyobject, make sure we own a reference to it. |
+ If the result is in a temp, it is already a new reference. |
+ """ |
+ if self.type.is_pyobject and not self.result_in_temp(): |
+ code.put_incref(self.result(), self.ctype()) |
+ |
+ def make_owned_memoryviewslice(self, code): |
+ """ |
+ Make sure we own the reference to this memoryview slice. |
+ """ |
+ if not self.result_in_temp(): |
+ code.put_incref_memoryviewslice(self.result(), |
+ have_gil=self.in_nogil_context) |
+ |
+ def generate_evaluation_code(self, code): |
+ # Generate code to evaluate this node and |
+ # its sub-expressions, and dispose of any |
+ # temporary results of its sub-expressions. |
+ self.generate_subexpr_evaluation_code(code) |
+ |
+ code.mark_pos(self.pos) |
+ if self.is_temp: |
+ self.allocate_temp_result(code) |
+ |
+ self.generate_result_code(code) |
+ if self.is_temp: |
+ # If we are temp we do not need to wait until this node is disposed |
+ # before disposing children. |
+ self.generate_subexpr_disposal_code(code) |
+ self.free_subexpr_temps(code) |
+ |
+ def generate_subexpr_evaluation_code(self, code): |
+ for node in self.subexpr_nodes(): |
+ node.generate_evaluation_code(code) |
+ |
+ def generate_result_code(self, code): |
+ self.not_implemented("generate_result_code") |
+ |
+ def generate_disposal_code(self, code): |
+ if self.is_temp: |
+ if self.result(): |
+ if self.type.is_pyobject: |
+ code.put_decref_clear(self.result(), self.ctype()) |
+ elif self.type.is_memoryviewslice: |
+ code.put_xdecref_memoryviewslice( |
+ self.result(), have_gil=not self.in_nogil_context) |
+ else: |
+ # Already done if self.is_temp |
+ self.generate_subexpr_disposal_code(code) |
+ |
+ def generate_subexpr_disposal_code(self, code): |
+ # Generate code to dispose of temporary results |
+ # of all sub-expressions. |
+ for node in self.subexpr_nodes(): |
+ node.generate_disposal_code(code) |
+ |
+ def generate_post_assignment_code(self, code): |
+ if self.is_temp: |
+ if self.type.is_pyobject: |
+ code.putln("%s = 0;" % self.result()) |
+ elif self.type.is_memoryviewslice: |
+ code.putln("%s.memview = NULL;" % self.result()) |
+ code.putln("%s.data = NULL;" % self.result()) |
+ else: |
+ self.generate_subexpr_disposal_code(code) |
+ |
+ def generate_assignment_code(self, rhs, code): |
+ # Stub method for nodes which are not legal as |
+ # the LHS of an assignment. An error will have |
+ # been reported earlier. |
+ pass |
+ |
+ def generate_deletion_code(self, code, ignore_nonexisting=False): |
+ # Stub method for nodes that are not legal as |
+ # the argument of a del statement. An error |
+ # will have been reported earlier. |
+ pass |
+ |
+ def free_temps(self, code): |
+ if self.is_temp: |
+ if not self.type.is_void: |
+ self.release_temp_result(code) |
+ else: |
+ self.free_subexpr_temps(code) |
+ |
+ def free_subexpr_temps(self, code): |
+ for sub in self.subexpr_nodes(): |
+ sub.free_temps(code) |
+ |
+ def generate_function_definitions(self, env, code): |
+ pass |
+ |
+ # ---------------- Annotation --------------------- |
+ |
+ def annotate(self, code): |
+ for node in self.subexpr_nodes(): |
+ node.annotate(code) |
+ |
+ # ----------------- Coercion ---------------------- |
+ |
+ def coerce_to(self, dst_type, env): |
+ # Coerce the result so that it can be assigned to |
+ # something of type dst_type. If processing is necessary, |
+ # wraps this node in a coercion node and returns that. |
+ # Otherwise, returns this node unchanged. |
+ # |
+ # This method is called during the analyse_expressions |
+ # phase of the src_node's processing. |
+ # |
+ # Note that subclasses that override this (especially |
+ # ConstNodes) must not (re-)set their own .type attribute |
+ # here. Since expression nodes may turn up in different |
+ # places in the tree (e.g. inside of CloneNodes in cascaded |
+ # assignments), this method must return a new node instance |
+ # if it changes the type. |
+ # |
+ src = self |
+ src_type = self.type |
+ |
+ if self.check_for_coercion_error(dst_type, env): |
+ return self |
+ |
+ if dst_type.is_reference and not src_type.is_reference: |
+ dst_type = dst_type.ref_base_type |
+ |
+ if src_type.is_const: |
+ src_type = src_type.const_base_type |
+ |
+ if src_type.is_fused or dst_type.is_fused: |
+ # See if we are coercing a fused function to a pointer to a |
+ # specialized function |
+ if (src_type.is_cfunction and not dst_type.is_fused and |
+ dst_type.is_ptr and dst_type.base_type.is_cfunction): |
+ |
+ dst_type = dst_type.base_type |
+ |
+ for signature in src_type.get_all_specialized_function_types(): |
+ if signature.same_as(dst_type): |
+ src.type = signature |
+ src.entry = src.type.entry |
+ src.entry.used = True |
+ return self |
+ |
+ if src_type.is_fused: |
+ error(self.pos, "Type is not specialized") |
+ else: |
+ error(self.pos, "Cannot coerce to a type that is not specialized") |
+ |
+ self.type = error_type |
+ return self |
+ |
+ if self.coercion_type is not None: |
+ # This is purely for error checking purposes! |
+ node = NameNode(self.pos, name='', type=self.coercion_type) |
+ node.coerce_to(dst_type, env) |
+ |
+ if dst_type.is_memoryviewslice: |
+ import MemoryView |
+ if not src.type.is_memoryviewslice: |
+ if src.type.is_pyobject: |
+ src = CoerceToMemViewSliceNode(src, dst_type, env) |
+ elif src.type.is_array: |
+ src = CythonArrayNode.from_carray(src, env).coerce_to( |
+ dst_type, env) |
+ elif not src_type.is_error: |
+ error(self.pos, |
+ "Cannot convert '%s' to memoryviewslice" % |
+ (src_type,)) |
+ elif not MemoryView.src_conforms_to_dst( |
+ src.type, dst_type, broadcast=self.memslice_broadcast): |
+ if src.type.dtype.same_as(dst_type.dtype): |
+ msg = "Memoryview '%s' not conformable to memoryview '%s'." |
+ tup = src.type, dst_type |
+ else: |
+ msg = "Different base types for memoryviews (%s, %s)" |
+ tup = src.type.dtype, dst_type.dtype |
+ |
+ error(self.pos, msg % tup) |
+ |
+ elif dst_type.is_pyobject: |
+ if not src.type.is_pyobject: |
+ if dst_type is bytes_type and src.type.is_int: |
+ src = CoerceIntToBytesNode(src, env) |
+ else: |
+ src = CoerceToPyTypeNode(src, env, type=dst_type) |
+ if not src.type.subtype_of(dst_type): |
+ if src.constant_result is not None: |
+ src = PyTypeTestNode(src, dst_type, env) |
+ elif src.type.is_pyobject: |
+ src = CoerceFromPyTypeNode(dst_type, src, env) |
+ elif (dst_type.is_complex |
+ and src_type != dst_type |
+ and dst_type.assignable_from(src_type)): |
+ src = CoerceToComplexNode(src, dst_type, env) |
+ else: # neither src nor dst are py types |
+ # Added the string comparison, since for c types that |
+ # is enough, but Cython gets confused when the types are |
+ # in different pxi files. |
+ if not (str(src.type) == str(dst_type) or dst_type.assignable_from(src_type)): |
+ self.fail_assignment(dst_type) |
+ return src |
+ |
+ def fail_assignment(self, dst_type): |
+ error(self.pos, "Cannot assign type '%s' to '%s'" % (self.type, dst_type)) |
+ |
+ def check_for_coercion_error(self, dst_type, env, fail=False, default=None): |
+ if fail and not default: |
+ default = "Cannot assign type '%(FROM)s' to '%(TO)s'" |
+ message = find_coercion_error((self.type, dst_type), default, env) |
+ if message is not None: |
+ error(self.pos, message % {'FROM': self.type, 'TO': dst_type}) |
+ return True |
+ if fail: |
+ self.fail_assignment(dst_type) |
+ return True |
+ return False |
+ |
+ def coerce_to_pyobject(self, env): |
+ return self.coerce_to(PyrexTypes.py_object_type, env) |
+ |
+ def coerce_to_boolean(self, env): |
+ # Coerce result to something acceptable as |
+ # a boolean value. |
+ |
+ # if it's constant, calculate the result now |
+ if self.has_constant_result(): |
+ bool_value = bool(self.constant_result) |
+ return BoolNode(self.pos, value=bool_value, |
+ constant_result=bool_value) |
+ |
+ type = self.type |
+ if type.is_enum or type.is_error: |
+ return self |
+ elif type.is_pyobject or type.is_int or type.is_ptr or type.is_float: |
+ return CoerceToBooleanNode(self, env) |
+ else: |
+ error(self.pos, "Type '%s' not acceptable as a boolean" % type) |
+ return self |
+ |
+ def coerce_to_integer(self, env): |
+ # If not already some C integer type, coerce to longint. |
+ if self.type.is_int: |
+ return self |
+ else: |
+ return self.coerce_to(PyrexTypes.c_long_type, env) |
+ |
+ def coerce_to_temp(self, env): |
+ # Ensure that the result is in a temporary. |
+ if self.result_in_temp(): |
+ return self |
+ else: |
+ return CoerceToTempNode(self, env) |
+ |
+ def coerce_to_simple(self, env): |
+ # Ensure that the result is simple (see is_simple). |
+ if self.is_simple(): |
+ return self |
+ else: |
+ return self.coerce_to_temp(env) |
+ |
+ def is_simple(self): |
+ # A node is simple if its result is something that can |
+ # be referred to without performing any operations, e.g. |
+ # a constant, local var, C global var, struct member |
+ # reference, or temporary. |
+ return self.result_in_temp() |
+ |
+ def may_be_none(self): |
+ if self.type and not (self.type.is_pyobject or |
+ self.type.is_memoryviewslice): |
+ return False |
+ if self.has_constant_result(): |
+ return self.constant_result is not None |
+ return True |
+ |
+ def as_cython_attribute(self): |
+ return None |
+ |
+ def as_none_safe_node(self, message, error="PyExc_TypeError", format_args=()): |
+ # Wraps the node in a NoneCheckNode if it is not known to be |
+ # not-None (e.g. because it is a Python literal). |
+ if self.may_be_none(): |
+ return NoneCheckNode(self, error, message, format_args) |
+ else: |
+ return self |
+ |
+ @classmethod |
+ def from_node(cls, node, **kwargs): |
+ """Instantiate this node class from another node, properly |
+ copying over all attributes that one would forget otherwise. |
+ """ |
+ attributes = "cf_state cf_maybe_null cf_is_null constant_result".split() |
+ for attr_name in attributes: |
+ if attr_name in kwargs: |
+ continue |
+ try: |
+ value = getattr(node, attr_name) |
+ except AttributeError: |
+ pass |
+ else: |
+ kwargs[attr_name] = value |
+ return cls(node.pos, **kwargs) |
+ |
+ |
+class AtomicExprNode(ExprNode): |
+ # Abstract base class for expression nodes which have |
+ # no sub-expressions. |
+ |
+ subexprs = [] |
+ |
+ # Override to optimize -- we know we have no children |
+ def generate_subexpr_evaluation_code(self, code): |
+ pass |
+ def generate_subexpr_disposal_code(self, code): |
+ pass |
+ |
+class PyConstNode(AtomicExprNode): |
+ # Abstract base class for constant Python values. |
+ |
+ is_literal = 1 |
+ type = py_object_type |
+ |
+ def is_simple(self): |
+ return 1 |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ def analyse_types(self, env): |
+ return self |
+ |
+ def calculate_result_code(self): |
+ return self.value |
+ |
+ def generate_result_code(self, code): |
+ pass |
+ |
+ |
+class NoneNode(PyConstNode): |
+ # The constant value None |
+ |
+ is_none = 1 |
+ value = "Py_None" |
+ |
+ constant_result = None |
+ |
+ nogil_check = None |
+ |
+ def compile_time_value(self, denv): |
+ return None |
+ |
+ def may_be_none(self): |
+ return True |
+ |
+ |
+class EllipsisNode(PyConstNode): |
+ # '...' in a subscript list. |
+ |
+ value = "Py_Ellipsis" |
+ |
+ constant_result = Ellipsis |
+ |
+ def compile_time_value(self, denv): |
+ return Ellipsis |
+ |
+ |
+class ConstNode(AtomicExprNode): |
+ # Abstract base type for literal constant nodes. |
+ # |
+ # value string C code fragment |
+ |
+ is_literal = 1 |
+ nogil_check = None |
+ |
+ def is_simple(self): |
+ return 1 |
+ |
+ def nonlocally_immutable(self): |
+ return 1 |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ def analyse_types(self, env): |
+ return self # Types are held in class variables |
+ |
+ def check_const(self): |
+ return True |
+ |
+ def get_constant_c_result_code(self): |
+ return self.calculate_result_code() |
+ |
+ def calculate_result_code(self): |
+ return str(self.value) |
+ |
+ def generate_result_code(self, code): |
+ pass |
+ |
+ |
+class BoolNode(ConstNode): |
+ type = PyrexTypes.c_bint_type |
+ # The constant value True or False |
+ |
+ def calculate_constant_result(self): |
+ self.constant_result = self.value |
+ |
+ def compile_time_value(self, denv): |
+ return self.value |
+ |
+ def calculate_result_code(self): |
+ if self.type.is_pyobject: |
+ return self.value and 'Py_True' or 'Py_False' |
+ else: |
+ return str(int(self.value)) |
+ |
+ def coerce_to(self, dst_type, env): |
+ if dst_type.is_pyobject and self.type.is_int: |
+ return BoolNode( |
+ self.pos, value=self.value, |
+ constant_result=self.constant_result, |
+ type=Builtin.bool_type) |
+ if dst_type.is_int and self.type.is_pyobject: |
+ return BoolNode( |
+ self.pos, value=self.value, |
+ constant_result=self.constant_result, |
+ type=PyrexTypes.c_bint_type) |
+ return ConstNode.coerce_to(self, dst_type, env) |
+ |
+ |
+class NullNode(ConstNode): |
+ type = PyrexTypes.c_null_ptr_type |
+ value = "NULL" |
+ constant_result = 0 |
+ |
+ def get_constant_c_result_code(self): |
+ return self.value |
+ |
+ |
+class CharNode(ConstNode): |
+ type = PyrexTypes.c_char_type |
+ |
+ def calculate_constant_result(self): |
+ self.constant_result = ord(self.value) |
+ |
+ def compile_time_value(self, denv): |
+ return ord(self.value) |
+ |
+ def calculate_result_code(self): |
+ return "'%s'" % StringEncoding.escape_char(self.value) |
+ |
+ |
+class IntNode(ConstNode): |
+ |
+ # unsigned "" or "U" |
+ # longness "" or "L" or "LL" |
+ # is_c_literal True/False/None creator considers this a C integer literal |
+ |
+ unsigned = "" |
+ longness = "" |
+ is_c_literal = None # unknown |
+ |
+ def __init__(self, pos, **kwds): |
+ ExprNode.__init__(self, pos, **kwds) |
+ if 'type' not in kwds: |
+ self.type = self.find_suitable_type_for_value() |
+ |
+ def find_suitable_type_for_value(self): |
+ if self.constant_result is constant_value_not_set: |
+ try: |
+ self.calculate_constant_result() |
+ except ValueError: |
+ pass |
+ # we ignore 'is_c_literal = True' and instead map signed 32bit |
+ # integers as C long values |
+ if self.is_c_literal or \ |
+ self.constant_result in (constant_value_not_set, not_a_constant) or \ |
+ self.unsigned or self.longness == 'LL': |
+ # clearly a C literal |
+ rank = (self.longness == 'LL') and 2 or 1 |
+ suitable_type = PyrexTypes.modifiers_and_name_to_type[not self.unsigned, rank, "int"] |
+ if self.type: |
+ suitable_type = PyrexTypes.widest_numeric_type(suitable_type, self.type) |
+ else: |
+ # C literal or Python literal - split at 32bit boundary |
+ if -2**31 <= self.constant_result < 2**31: |
+ if self.type and self.type.is_int: |
+ suitable_type = self.type |
+ else: |
+ suitable_type = PyrexTypes.c_long_type |
+ else: |
+ suitable_type = PyrexTypes.py_object_type |
+ return suitable_type |
+ |
+ def coerce_to(self, dst_type, env): |
+ if self.type is dst_type: |
+ return self |
+ elif dst_type.is_float: |
+ if self.has_constant_result(): |
+ return FloatNode(self.pos, value='%d.0' % int(self.constant_result), type=dst_type, |
+ constant_result=float(self.constant_result)) |
+ else: |
+ return FloatNode(self.pos, value=self.value, type=dst_type, |
+ constant_result=not_a_constant) |
+ if dst_type.is_numeric and not dst_type.is_complex: |
+ node = IntNode(self.pos, value=self.value, constant_result=self.constant_result, |
+ type = dst_type, is_c_literal = True, |
+ unsigned=self.unsigned, longness=self.longness) |
+ return node |
+ elif dst_type.is_pyobject: |
+ node = IntNode(self.pos, value=self.value, constant_result=self.constant_result, |
+ type = PyrexTypes.py_object_type, is_c_literal = False, |
+ unsigned=self.unsigned, longness=self.longness) |
+ else: |
+ # FIXME: not setting the type here to keep it working with |
+ # complex numbers. Should they be special cased? |
+ node = IntNode(self.pos, value=self.value, constant_result=self.constant_result, |
+ unsigned=self.unsigned, longness=self.longness) |
+ # We still need to perform normal coerce_to processing on the |
+ # result, because we might be coercing to an extension type, |
+ # in which case a type test node will be needed. |
+ return ConstNode.coerce_to(node, dst_type, env) |
+ |
+ def coerce_to_boolean(self, env): |
+ return IntNode( |
+ self.pos, value=self.value, |
+ constant_result=self.constant_result, |
+ type=PyrexTypes.c_bint_type, |
+ unsigned=self.unsigned, longness=self.longness) |
+ |
+ def generate_evaluation_code(self, code): |
+ if self.type.is_pyobject: |
+ # pre-allocate a Python version of the number |
+ plain_integer_string = str(Utils.str_to_number(self.value)) |
+ self.result_code = code.get_py_int(plain_integer_string, self.longness) |
+ else: |
+ self.result_code = self.get_constant_c_result_code() |
+ |
+ def get_constant_c_result_code(self): |
+ return self.value_as_c_integer_string() + self.unsigned + self.longness |
+ |
+ def value_as_c_integer_string(self): |
+ value = self.value |
+ if len(value) > 2: |
+ # convert C-incompatible Py3 oct/bin notations |
+ if value[1] in 'oO': |
+ value = value[0] + value[2:] # '0o123' => '0123' |
+ elif value[1] in 'bB': |
+ value = int(value[2:], 2) |
+ return str(value) |
+ |
+ def calculate_result_code(self): |
+ return self.result_code |
+ |
+ def calculate_constant_result(self): |
+ self.constant_result = Utils.str_to_number(self.value) |
+ |
+ def compile_time_value(self, denv): |
+ return Utils.str_to_number(self.value) |
+ |
+ |
+class FloatNode(ConstNode): |
+ type = PyrexTypes.c_double_type |
+ |
+ def calculate_constant_result(self): |
+ self.constant_result = float(self.value) |
+ |
+ def compile_time_value(self, denv): |
+ return float(self.value) |
+ |
+ def coerce_to(self, dst_type, env): |
+ if dst_type.is_pyobject and self.type.is_float: |
+ return FloatNode( |
+ self.pos, value=self.value, |
+ constant_result=self.constant_result, |
+ type=Builtin.float_type) |
+ if dst_type.is_float and self.type.is_pyobject: |
+ return FloatNode( |
+ self.pos, value=self.value, |
+ constant_result=self.constant_result, |
+ type=dst_type) |
+ return ConstNode.coerce_to(self, dst_type, env) |
+ |
+ def calculate_result_code(self): |
+ return self.result_code |
+ |
+ def get_constant_c_result_code(self): |
+ strval = self.value |
+ assert isinstance(strval, (str, unicode)) |
+ cmpval = repr(float(strval)) |
+ if cmpval == 'nan': |
+ return "(Py_HUGE_VAL * 0)" |
+ elif cmpval == 'inf': |
+ return "Py_HUGE_VAL" |
+ elif cmpval == '-inf': |
+ return "(-Py_HUGE_VAL)" |
+ else: |
+ return strval |
+ |
+ def generate_evaluation_code(self, code): |
+ c_value = self.get_constant_c_result_code() |
+ if self.type.is_pyobject: |
+ self.result_code = code.get_py_float(self.value, c_value) |
+ else: |
+ self.result_code = c_value |
+ |
+ |
+class BytesNode(ConstNode): |
+ # A char* or bytes literal |
+ # |
+ # value BytesLiteral |
+ |
+ is_string_literal = True |
+ # start off as Python 'bytes' to support len() in O(1) |
+ type = bytes_type |
+ |
+ def calculate_constant_result(self): |
+ self.constant_result = self.value |
+ |
+ def as_sliced_node(self, start, stop, step=None): |
+ value = StringEncoding.BytesLiteral(self.value[start:stop:step]) |
+ value.encoding = self.value.encoding |
+ return BytesNode( |
+ self.pos, value=value, constant_result=value) |
+ |
+ def compile_time_value(self, denv): |
+ return self.value |
+ |
+ def analyse_as_type(self, env): |
+ type = PyrexTypes.parse_basic_type(self.value) |
+ if type is not None: |
+ return type |
+ from TreeFragment import TreeFragment |
+ pos = (self.pos[0], self.pos[1], self.pos[2]-7) |
+ declaration = TreeFragment(u"sizeof(%s)" % self.value, name=pos[0].filename, initial_pos=pos) |
+ sizeof_node = declaration.root.stats[0].expr |
+ sizeof_node = sizeof_node.analyse_types(env) |
+ if isinstance(sizeof_node, SizeofTypeNode): |
+ return sizeof_node.arg_type |
+ |
+ def can_coerce_to_char_literal(self): |
+ return len(self.value) == 1 |
+ |
+ def coerce_to_boolean(self, env): |
+ # This is special because testing a C char* for truth directly |
+ # would yield the wrong result. |
+ bool_value = bool(self.value) |
+ return BoolNode(self.pos, value=bool_value, constant_result=bool_value) |
+ |
+ def coerce_to(self, dst_type, env): |
+ if self.type == dst_type: |
+ return self |
+ if dst_type.is_int: |
+ if not self.can_coerce_to_char_literal(): |
+ error(self.pos, "Only single-character string literals can be coerced into ints.") |
+ return self |
+ if dst_type.is_unicode_char: |
+ error(self.pos, "Bytes literals cannot coerce to Py_UNICODE/Py_UCS4, use a unicode literal instead.") |
+ return self |
+ return CharNode(self.pos, value=self.value, |
+ constant_result=ord(self.value)) |
+ |
+ node = BytesNode(self.pos, value=self.value, |
+ constant_result=self.constant_result) |
+ if dst_type.is_pyobject: |
+ if dst_type in (py_object_type, Builtin.bytes_type): |
+ node.type = Builtin.bytes_type |
+ else: |
+ self.check_for_coercion_error(dst_type, env, fail=True) |
+ return node |
+ elif dst_type == PyrexTypes.c_char_ptr_type: |
+ node.type = dst_type |
+ return node |
+ elif dst_type == PyrexTypes.c_uchar_ptr_type: |
+ node.type = PyrexTypes.c_char_ptr_type |
+ return CastNode(node, PyrexTypes.c_uchar_ptr_type) |
+ elif dst_type.assignable_from(PyrexTypes.c_char_ptr_type): |
+ node.type = dst_type |
+ return node |
+ |
+ # We still need to perform normal coerce_to processing on the |
+ # result, because we might be coercing to an extension type, |
+ # in which case a type test node will be needed. |
+ return ConstNode.coerce_to(node, dst_type, env) |
+ |
+ def generate_evaluation_code(self, code): |
+ if self.type.is_pyobject: |
+ self.result_code = code.get_py_string_const(self.value) |
+ else: |
+ self.result_code = code.get_string_const(self.value) |
+ |
+ def get_constant_c_result_code(self): |
+ return None # FIXME |
+ |
+ def calculate_result_code(self): |
+ return self.result_code |
+ |
+ |
+class UnicodeNode(ConstNode): |
+ # A Py_UNICODE* or unicode literal |
+ # |
+ # value EncodedString |
+ # bytes_value BytesLiteral the literal parsed as bytes string |
+ # ('-3' unicode literals only) |
+ |
+ is_string_literal = True |
+ bytes_value = None |
+ type = unicode_type |
+ |
+ def calculate_constant_result(self): |
+ self.constant_result = self.value |
+ |
+ def as_sliced_node(self, start, stop, step=None): |
+ if StringEncoding.string_contains_surrogates(self.value[:stop]): |
+ # this is unsafe as it may give different results |
+ # in different runtimes |
+ return None |
+ value = StringEncoding.EncodedString(self.value[start:stop:step]) |
+ value.encoding = self.value.encoding |
+ if self.bytes_value is not None: |
+ bytes_value = StringEncoding.BytesLiteral( |
+ self.bytes_value[start:stop:step]) |
+ bytes_value.encoding = self.bytes_value.encoding |
+ else: |
+ bytes_value = None |
+ return UnicodeNode( |
+ self.pos, value=value, bytes_value=bytes_value, |
+ constant_result=value) |
+ |
+ def coerce_to(self, dst_type, env): |
+ if dst_type is self.type: |
+ pass |
+ elif dst_type.is_unicode_char: |
+ if not self.can_coerce_to_char_literal(): |
+ error(self.pos, |
+ "Only single-character Unicode string literals or " |
+ "surrogate pairs can be coerced into Py_UCS4/Py_UNICODE.") |
+ return self |
+ int_value = ord(self.value) |
+ return IntNode(self.pos, type=dst_type, value=str(int_value), |
+ constant_result=int_value) |
+ elif not dst_type.is_pyobject: |
+ if dst_type.is_string and self.bytes_value is not None: |
+ # special case: '-3' enforced unicode literal used in a |
+ # C char* context |
+ return BytesNode(self.pos, value=self.bytes_value |
+ ).coerce_to(dst_type, env) |
+ if dst_type.is_pyunicode_ptr: |
+ node = UnicodeNode(self.pos, value=self.value) |
+ node.type = dst_type |
+ return node |
+ error(self.pos, |
+ "Unicode literals do not support coercion to C types other " |
+ "than Py_UNICODE/Py_UCS4 (for characters) or Py_UNICODE* " |
+ "(for strings).") |
+ elif dst_type not in (py_object_type, Builtin.basestring_type): |
+ self.check_for_coercion_error(dst_type, env, fail=True) |
+ return self |
+ |
+ def can_coerce_to_char_literal(self): |
+ return len(self.value) == 1 |
+ ## or (len(self.value) == 2 |
+ ## and (0xD800 <= self.value[0] <= 0xDBFF) |
+ ## and (0xDC00 <= self.value[1] <= 0xDFFF)) |
+ |
+ def coerce_to_boolean(self, env): |
+ bool_value = bool(self.value) |
+ return BoolNode(self.pos, value=bool_value, constant_result=bool_value) |
+ |
+ def contains_surrogates(self): |
+ return StringEncoding.string_contains_surrogates(self.value) |
+ |
+ def generate_evaluation_code(self, code): |
+ if self.type.is_pyobject: |
+ if self.contains_surrogates(): |
+ # surrogates are not really portable and cannot be |
+ # decoded by the UTF-8 codec in Py3.3 |
+ self.result_code = code.get_py_const(py_object_type, 'ustring') |
+ data_cname = code.get_pyunicode_ptr_const(self.value) |
+ code = code.get_cached_constants_writer() |
+ code.mark_pos(self.pos) |
+ code.putln( |
+ "%s = PyUnicode_FromUnicode(%s, (sizeof(%s) / sizeof(Py_UNICODE))-1); %s" % ( |
+ self.result_code, |
+ data_cname, |
+ data_cname, |
+ code.error_goto_if_null(self.result_code, self.pos))) |
+ code.putln("#if CYTHON_PEP393_ENABLED") |
+ code.put_error_if_neg( |
+ self.pos, "PyUnicode_READY(%s)" % self.result_code) |
+ code.putln("#endif") |
+ else: |
+ self.result_code = code.get_py_string_const(self.value) |
+ else: |
+ self.result_code = code.get_pyunicode_ptr_const(self.value) |
+ |
+ def calculate_result_code(self): |
+ return self.result_code |
+ |
+ def compile_time_value(self, env): |
+ return self.value |
+ |
+ |
+class StringNode(PyConstNode): |
+ # A Python str object, i.e. a byte string in Python 2.x and a |
+ # unicode string in Python 3.x |
+ # |
+ # value BytesLiteral (or EncodedString with ASCII content) |
+ # unicode_value EncodedString or None |
+ # is_identifier boolean |
+ |
+ type = str_type |
+ is_string_literal = True |
+ is_identifier = None |
+ unicode_value = None |
+ |
+ def calculate_constant_result(self): |
+ if self.unicode_value is not None: |
+ # only the Unicode value is portable across Py2/3 |
+ self.constant_result = self.unicode_value |
+ |
+ def as_sliced_node(self, start, stop, step=None): |
+ value = type(self.value)(self.value[start:stop:step]) |
+ value.encoding = self.value.encoding |
+ if self.unicode_value is not None: |
+ if StringEncoding.string_contains_surrogates(self.unicode_value[:stop]): |
+ # this is unsafe as it may give different results in different runtimes |
+ return None |
+ unicode_value = StringEncoding.EncodedString( |
+ self.unicode_value[start:stop:step]) |
+ else: |
+ unicode_value = None |
+ return StringNode( |
+ self.pos, value=value, unicode_value=unicode_value, |
+ constant_result=value, is_identifier=self.is_identifier) |
+ |
+ def coerce_to(self, dst_type, env): |
+ if dst_type is not py_object_type and not str_type.subtype_of(dst_type): |
+# if dst_type is Builtin.bytes_type: |
+# # special case: bytes = 'str literal' |
+# return BytesNode(self.pos, value=self.value) |
+ if not dst_type.is_pyobject: |
+ return BytesNode(self.pos, value=self.value).coerce_to(dst_type, env) |
+ if dst_type is not Builtin.basestring_type: |
+ self.check_for_coercion_error(dst_type, env, fail=True) |
+ return self |
+ |
+ def can_coerce_to_char_literal(self): |
+ return not self.is_identifier and len(self.value) == 1 |
+ |
+ def generate_evaluation_code(self, code): |
+ self.result_code = code.get_py_string_const( |
+ self.value, identifier=self.is_identifier, is_str=True, |
+ unicode_value=self.unicode_value) |
+ |
+ def get_constant_c_result_code(self): |
+ return None |
+ |
+ def calculate_result_code(self): |
+ return self.result_code |
+ |
+ def compile_time_value(self, env): |
+ return self.value |
+ |
+ |
+class IdentifierStringNode(StringNode): |
+ # A special str value that represents an identifier (bytes in Py2, |
+ # unicode in Py3). |
+ is_identifier = True |
+ |
+ |
+class ImagNode(AtomicExprNode): |
+ # Imaginary number literal |
+ # |
+ # value float imaginary part |
+ |
+ type = PyrexTypes.c_double_complex_type |
+ |
+ def calculate_constant_result(self): |
+ self.constant_result = complex(0.0, self.value) |
+ |
+ def compile_time_value(self, denv): |
+ return complex(0.0, self.value) |
+ |
+ def analyse_types(self, env): |
+ self.type.create_declaration_utility_code(env) |
+ return self |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ def coerce_to(self, dst_type, env): |
+ if self.type is dst_type: |
+ return self |
+ node = ImagNode(self.pos, value=self.value) |
+ if dst_type.is_pyobject: |
+ node.is_temp = 1 |
+ node.type = PyrexTypes.py_object_type |
+ # We still need to perform normal coerce_to processing on the |
+ # result, because we might be coercing to an extension type, |
+ # in which case a type test node will be needed. |
+ return AtomicExprNode.coerce_to(node, dst_type, env) |
+ |
+ gil_message = "Constructing complex number" |
+ |
+ def calculate_result_code(self): |
+ if self.type.is_pyobject: |
+ return self.result() |
+ else: |
+ return "%s(0, %r)" % (self.type.from_parts, float(self.value)) |
+ |
+ def generate_result_code(self, code): |
+ if self.type.is_pyobject: |
+ code.putln( |
+ "%s = PyComplex_FromDoubles(0.0, %r); %s" % ( |
+ self.result(), |
+ float(self.value), |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ |
+ |
+class NewExprNode(AtomicExprNode): |
+ |
+ # C++ new statement |
+ # |
+ # cppclass node c++ class to create |
+ |
+ type = None |
+ |
+ def infer_type(self, env): |
+ type = self.cppclass.analyse_as_type(env) |
+ if type is None or not type.is_cpp_class: |
+ error(self.pos, "new operator can only be applied to a C++ class") |
+ self.type = error_type |
+ return |
+ self.cpp_check(env) |
+ constructor = type.scope.lookup(u'<init>') |
+ if constructor is None: |
+ func_type = PyrexTypes.CFuncType(type, [], exception_check='+') |
+ type.scope.declare_cfunction(u'<init>', func_type, self.pos) |
+ constructor = type.scope.lookup(u'<init>') |
+ self.class_type = type |
+ self.entry = constructor |
+ self.type = constructor.type |
+ return self.type |
+ |
+ def analyse_types(self, env): |
+ if self.type is None: |
+ self.infer_type(env) |
+ return self |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ def generate_result_code(self, code): |
+ pass |
+ |
+ def calculate_result_code(self): |
+ return "new " + self.class_type.declaration_code("") |
+ |
+ |
+class NameNode(AtomicExprNode): |
+ # Reference to a local or global variable name. |
+ # |
+ # name string Python name of the variable |
+ # entry Entry Symbol table entry |
+ # type_entry Entry For extension type names, the original type entry |
+ # cf_is_null boolean Is uninitialized before this node |
+ # cf_maybe_null boolean Maybe uninitialized before this node |
+ # allow_null boolean Don't raise UnboundLocalError |
+ # nogil boolean Whether it is used in a nogil context |
+ |
+ is_name = True |
+ is_cython_module = False |
+ cython_attribute = None |
+ lhs_of_first_assignment = False # TODO: remove me |
+ is_used_as_rvalue = 0 |
+ entry = None |
+ type_entry = None |
+ cf_maybe_null = True |
+ cf_is_null = False |
+ allow_null = False |
+ nogil = False |
+ inferred_type = None |
+ |
+ def as_cython_attribute(self): |
+ return self.cython_attribute |
+ |
+ def type_dependencies(self, env): |
+ if self.entry is None: |
+ self.entry = env.lookup(self.name) |
+ if self.entry is not None and self.entry.type.is_unspecified: |
+ return (self,) |
+ else: |
+ return () |
+ |
+ def infer_type(self, env): |
+ if self.entry is None: |
+ self.entry = env.lookup(self.name) |
+ if self.entry is None or self.entry.type is unspecified_type: |
+ if self.inferred_type is not None: |
+ return self.inferred_type |
+ return py_object_type |
+ elif (self.entry.type.is_extension_type or self.entry.type.is_builtin_type) and \ |
+ self.name == self.entry.type.name: |
+ # Unfortunately the type attribute of type objects |
+ # is used for the pointer to the type they represent. |
+ return type_type |
+ elif self.entry.type.is_cfunction: |
+ if self.entry.scope.is_builtin_scope: |
+ # special case: optimised builtin functions must be treated as Python objects |
+ return py_object_type |
+ else: |
+ # special case: referring to a C function must return its pointer |
+ return PyrexTypes.CPtrType(self.entry.type) |
+ else: |
+ # If entry is inferred as pyobject it's safe to use local |
+ # NameNode's inferred_type. |
+ if self.entry.type.is_pyobject and self.inferred_type: |
+ # Overflow may happen if integer |
+ if not (self.inferred_type.is_int and self.entry.might_overflow): |
+ return self.inferred_type |
+ return self.entry.type |
+ |
+ def compile_time_value(self, denv): |
+ try: |
+ return denv.lookup(self.name) |
+ except KeyError: |
+ error(self.pos, "Compile-time name '%s' not defined" % self.name) |
+ |
+ def get_constant_c_result_code(self): |
+ if not self.entry or self.entry.type.is_pyobject: |
+ return None |
+ return self.entry.cname |
+ |
+ def coerce_to(self, dst_type, env): |
+ # If coercing to a generic pyobject and this is a builtin |
+ # C function with a Python equivalent, manufacture a NameNode |
+ # referring to the Python builtin. |
+ #print "NameNode.coerce_to:", self.name, dst_type ### |
+ if dst_type is py_object_type: |
+ entry = self.entry |
+ if entry and entry.is_cfunction: |
+ var_entry = entry.as_variable |
+ if var_entry: |
+ if var_entry.is_builtin and var_entry.is_const: |
+ var_entry = env.declare_builtin(var_entry.name, self.pos) |
+ node = NameNode(self.pos, name = self.name) |
+ node.entry = var_entry |
+ node.analyse_rvalue_entry(env) |
+ return node |
+ |
+ return super(NameNode, self).coerce_to(dst_type, env) |
+ |
+ def analyse_as_module(self, env): |
+ # Try to interpret this as a reference to a cimported module. |
+ # Returns the module scope, or None. |
+ entry = self.entry |
+ if not entry: |
+ entry = env.lookup(self.name) |
+ if entry and entry.as_module: |
+ return entry.as_module |
+ return None |
+ |
+ def analyse_as_type(self, env): |
+ if self.cython_attribute: |
+ type = PyrexTypes.parse_basic_type(self.cython_attribute) |
+ else: |
+ type = PyrexTypes.parse_basic_type(self.name) |
+ if type: |
+ return type |
+ entry = self.entry |
+ if not entry: |
+ entry = env.lookup(self.name) |
+ if entry and entry.is_type: |
+ return entry.type |
+ else: |
+ return None |
+ |
+ def analyse_as_extension_type(self, env): |
+ # Try to interpret this as a reference to an extension type. |
+ # Returns the extension type, or None. |
+ entry = self.entry |
+ if not entry: |
+ entry = env.lookup(self.name) |
+ if entry and entry.is_type: |
+ if entry.type.is_extension_type or entry.type.is_builtin_type: |
+ return entry.type |
+ return None |
+ |
+ def analyse_target_declaration(self, env): |
+ if not self.entry: |
+ self.entry = env.lookup_here(self.name) |
+ if not self.entry: |
+ if env.directives['warn.undeclared']: |
+ warning(self.pos, "implicit declaration of '%s'" % self.name, 1) |
+ if env.directives['infer_types'] != False: |
+ type = unspecified_type |
+ else: |
+ type = py_object_type |
+ self.entry = env.declare_var(self.name, type, self.pos) |
+ if self.entry.is_declared_generic: |
+ self.result_ctype = py_object_type |
+ |
+ def analyse_types(self, env): |
+ self.initialized_check = env.directives['initializedcheck'] |
+ if self.entry is None: |
+ self.entry = env.lookup(self.name) |
+ if not self.entry: |
+ self.entry = env.declare_builtin(self.name, self.pos) |
+ if not self.entry: |
+ self.type = PyrexTypes.error_type |
+ return self |
+ entry = self.entry |
+ if entry: |
+ entry.used = 1 |
+ if entry.type.is_buffer: |
+ import Buffer |
+ Buffer.used_buffer_aux_vars(entry) |
+ self.analyse_rvalue_entry(env) |
+ return self |
+ |
+ def analyse_target_types(self, env): |
+ self.analyse_entry(env, is_target=True) |
+ |
+ if (not self.is_lvalue() and self.entry.is_cfunction and |
+ self.entry.fused_cfunction and self.entry.as_variable): |
+ # We need this for the fused 'def' TreeFragment |
+ self.entry = self.entry.as_variable |
+ self.type = self.entry.type |
+ |
+ if self.type.is_const: |
+ error(self.pos, "Assignment to const '%s'" % self.name) |
+ if self.type.is_reference: |
+ error(self.pos, "Assignment to reference '%s'" % self.name) |
+ if not self.is_lvalue(): |
+ error(self.pos, "Assignment to non-lvalue '%s'" |
+ % self.name) |
+ self.type = PyrexTypes.error_type |
+ self.entry.used = 1 |
+ if self.entry.type.is_buffer: |
+ import Buffer |
+ Buffer.used_buffer_aux_vars(self.entry) |
+ return self |
+ |
+ def analyse_rvalue_entry(self, env): |
+ #print "NameNode.analyse_rvalue_entry:", self.name ### |
+ #print "Entry:", self.entry.__dict__ ### |
+ self.analyse_entry(env) |
+ entry = self.entry |
+ |
+ if entry.is_declared_generic: |
+ self.result_ctype = py_object_type |
+ |
+ if entry.is_pyglobal or entry.is_builtin: |
+ if entry.is_builtin and entry.is_const: |
+ self.is_temp = 0 |
+ else: |
+ self.is_temp = 1 |
+ |
+ self.is_used_as_rvalue = 1 |
+ elif entry.type.is_memoryviewslice: |
+ self.is_temp = False |
+ self.is_used_as_rvalue = True |
+ self.use_managed_ref = True |
+ return self |
+ |
+ def nogil_check(self, env): |
+ self.nogil = True |
+ if self.is_used_as_rvalue: |
+ entry = self.entry |
+ if entry.is_builtin: |
+ if not entry.is_const: # cached builtins are ok |
+ self.gil_error() |
+ elif entry.is_pyglobal: |
+ self.gil_error() |
+ elif self.entry.type.is_memoryviewslice: |
+ if self.cf_is_null or self.cf_maybe_null: |
+ import MemoryView |
+ MemoryView.err_if_nogil_initialized_check(self.pos, env) |
+ |
+ gil_message = "Accessing Python global or builtin" |
+ |
+ def analyse_entry(self, env, is_target=False): |
+ #print "NameNode.analyse_entry:", self.name ### |
+ self.check_identifier_kind() |
+ entry = self.entry |
+ type = entry.type |
+ if (not is_target and type.is_pyobject and self.inferred_type and |
+ self.inferred_type.is_builtin_type): |
+ # assume that type inference is smarter than the static entry |
+ type = self.inferred_type |
+ self.type = type |
+ |
+ def check_identifier_kind(self): |
+ # Check that this is an appropriate kind of name for use in an |
+ # expression. Also finds the variable entry associated with |
+ # an extension type. |
+ entry = self.entry |
+ if entry.is_type and entry.type.is_extension_type: |
+ self.type_entry = entry |
+ if not (entry.is_const or entry.is_variable |
+ or entry.is_builtin or entry.is_cfunction |
+ or entry.is_cpp_class): |
+ if self.entry.as_variable: |
+ self.entry = self.entry.as_variable |
+ else: |
+ error(self.pos, |
+ "'%s' is not a constant, variable or function identifier" % self.name) |
+ |
+ def is_simple(self): |
+ # If it's not a C variable, it'll be in a temp. |
+ return 1 |
+ |
+ def may_be_none(self): |
+ if self.cf_state and self.type and (self.type.is_pyobject or |
+ self.type.is_memoryviewslice): |
+ # gard against infinite recursion on self-dependencies |
+ if getattr(self, '_none_checking', False): |
+ # self-dependency - either this node receives a None |
+ # value from *another* node, or it can not reference |
+ # None at this point => safe to assume "not None" |
+ return False |
+ self._none_checking = True |
+ # evaluate control flow state to see if there were any |
+ # potential None values assigned to the node so far |
+ may_be_none = False |
+ for assignment in self.cf_state: |
+ if assignment.rhs.may_be_none(): |
+ may_be_none = True |
+ break |
+ del self._none_checking |
+ return may_be_none |
+ return super(NameNode, self).may_be_none() |
+ |
+ def nonlocally_immutable(self): |
+ if ExprNode.nonlocally_immutable(self): |
+ return True |
+ entry = self.entry |
+ if not entry or entry.in_closure: |
+ return False |
+ return entry.is_local or entry.is_arg or entry.is_builtin or entry.is_readonly |
+ |
+ def calculate_target_results(self, env): |
+ pass |
+ |
+ def check_const(self): |
+ entry = self.entry |
+ if entry is not None and not (entry.is_const or entry.is_cfunction or entry.is_builtin): |
+ self.not_const() |
+ return False |
+ return True |
+ |
+ def check_const_addr(self): |
+ entry = self.entry |
+ if not (entry.is_cglobal or entry.is_cfunction or entry.is_builtin): |
+ self.addr_not_const() |
+ return False |
+ return True |
+ |
+ def is_lvalue(self): |
+ return self.entry.is_variable and \ |
+ not self.entry.type.is_array and \ |
+ not self.entry.is_readonly |
+ |
+ def is_addressable(self): |
+ return self.entry.is_variable and not self.type.is_memoryviewslice |
+ |
+ def is_ephemeral(self): |
+ # Name nodes are never ephemeral, even if the |
+ # result is in a temporary. |
+ return 0 |
+ |
+ def calculate_result_code(self): |
+ entry = self.entry |
+ if not entry: |
+ return "<error>" # There was an error earlier |
+ return entry.cname |
+ |
+ def generate_result_code(self, code): |
+ assert hasattr(self, 'entry') |
+ entry = self.entry |
+ if entry is None: |
+ return # There was an error earlier |
+ if entry.is_builtin and entry.is_const: |
+ return # Lookup already cached |
+ elif entry.is_pyclass_attr: |
+ assert entry.type.is_pyobject, "Python global or builtin not a Python object" |
+ interned_cname = code.intern_identifier(self.entry.name) |
+ if entry.is_builtin: |
+ namespace = Naming.builtins_cname |
+ else: # entry.is_pyglobal |
+ namespace = entry.scope.namespace_cname |
+ if not self.cf_is_null: |
+ code.putln( |
+ '%s = PyObject_GetItem(%s, %s);' % ( |
+ self.result(), |
+ namespace, |
+ interned_cname)) |
+ code.putln('if (unlikely(!%s)) {' % self.result()) |
+ code.putln('PyErr_Clear();') |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("GetModuleGlobalName", "ObjectHandling.c")) |
+ code.putln( |
+ '%s = __Pyx_GetModuleGlobalName(%s);' % ( |
+ self.result(), |
+ interned_cname)) |
+ if not self.cf_is_null: |
+ code.putln("}") |
+ code.putln(code.error_goto_if_null(self.result(), self.pos)) |
+ code.put_gotref(self.py_result()) |
+ |
+ elif entry.is_builtin: |
+ assert entry.type.is_pyobject, "Python global or builtin not a Python object" |
+ interned_cname = code.intern_identifier(self.entry.name) |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("GetBuiltinName", "ObjectHandling.c")) |
+ code.putln( |
+ '%s = __Pyx_GetBuiltinName(%s); %s' % ( |
+ self.result(), |
+ interned_cname, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ |
+ elif entry.is_pyglobal: |
+ assert entry.type.is_pyobject, "Python global or builtin not a Python object" |
+ interned_cname = code.intern_identifier(self.entry.name) |
+ if entry.scope.is_module_scope: |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("GetModuleGlobalName", "ObjectHandling.c")) |
+ code.putln( |
+ '%s = __Pyx_GetModuleGlobalName(%s); %s' % ( |
+ self.result(), |
+ interned_cname, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ else: |
+ # FIXME: is_pyglobal is also used for class namespace |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("GetNameInClass", "ObjectHandling.c")) |
+ code.putln( |
+ '%s = __Pyx_GetNameInClass(%s, %s); %s' % ( |
+ self.result(), |
+ entry.scope.namespace_cname, |
+ interned_cname, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ |
+ elif entry.is_local or entry.in_closure or entry.from_closure or entry.type.is_memoryviewslice: |
+ # Raise UnboundLocalError for objects and memoryviewslices |
+ raise_unbound = ( |
+ (self.cf_maybe_null or self.cf_is_null) and not self.allow_null) |
+ null_code = entry.type.check_for_null_code(entry.cname) |
+ |
+ memslice_check = entry.type.is_memoryviewslice and self.initialized_check |
+ |
+ if null_code and raise_unbound and (entry.type.is_pyobject or memslice_check): |
+ code.put_error_if_unbound(self.pos, entry, self.in_nogil_context) |
+ |
+ def generate_assignment_code(self, rhs, code): |
+ #print "NameNode.generate_assignment_code:", self.name ### |
+ entry = self.entry |
+ if entry is None: |
+ return # There was an error earlier |
+ |
+ if (self.entry.type.is_ptr and isinstance(rhs, ListNode) |
+ and not self.lhs_of_first_assignment and not rhs.in_module_scope): |
+ error(self.pos, "Literal list must be assigned to pointer at time of declaration") |
+ |
+ # is_pyglobal seems to be True for module level-globals only. |
+ # We use this to access class->tp_dict if necessary. |
+ if entry.is_pyglobal: |
+ assert entry.type.is_pyobject, "Python global or builtin not a Python object" |
+ interned_cname = code.intern_identifier(self.entry.name) |
+ namespace = self.entry.scope.namespace_cname |
+ if entry.is_member: |
+ # if the entry is a member we have to cheat: SetAttr does not work |
+ # on types, so we create a descriptor which is then added to tp_dict |
+ setter = 'PyDict_SetItem' |
+ namespace = '%s->tp_dict' % namespace |
+ elif entry.scope.is_module_scope: |
+ setter = 'PyDict_SetItem' |
+ namespace = Naming.moddict_cname |
+ elif entry.is_pyclass_attr: |
+ setter = 'PyObject_SetItem' |
+ else: |
+ assert False, repr(entry) |
+ code.put_error_if_neg( |
+ self.pos, |
+ '%s(%s, %s, %s)' % ( |
+ setter, |
+ namespace, |
+ interned_cname, |
+ rhs.py_result())) |
+ if debug_disposal_code: |
+ print("NameNode.generate_assignment_code:") |
+ print("...generating disposal code for %s" % rhs) |
+ rhs.generate_disposal_code(code) |
+ rhs.free_temps(code) |
+ if entry.is_member: |
+ # in Py2.6+, we need to invalidate the method cache |
+ code.putln("PyType_Modified(%s);" % |
+ entry.scope.parent_type.typeptr_cname) |
+ else: |
+ if self.type.is_memoryviewslice: |
+ self.generate_acquire_memoryviewslice(rhs, code) |
+ |
+ elif self.type.is_buffer: |
+ # Generate code for doing the buffer release/acquisition. |
+ # This might raise an exception in which case the assignment (done |
+ # below) will not happen. |
+ # |
+ # The reason this is not in a typetest-like node is because the |
+ # variables that the acquired buffer info is stored to is allocated |
+ # per entry and coupled with it. |
+ self.generate_acquire_buffer(rhs, code) |
+ assigned = False |
+ if self.type.is_pyobject: |
+ #print "NameNode.generate_assignment_code: to", self.name ### |
+ #print "...from", rhs ### |
+ #print "...LHS type", self.type, "ctype", self.ctype() ### |
+ #print "...RHS type", rhs.type, "ctype", rhs.ctype() ### |
+ if self.use_managed_ref: |
+ rhs.make_owned_reference(code) |
+ is_external_ref = entry.is_cglobal or self.entry.in_closure or self.entry.from_closure |
+ if is_external_ref: |
+ if not self.cf_is_null: |
+ if self.cf_maybe_null: |
+ code.put_xgotref(self.py_result()) |
+ else: |
+ code.put_gotref(self.py_result()) |
+ assigned = True |
+ if entry.is_cglobal: |
+ code.put_decref_set( |
+ self.result(), rhs.result_as(self.ctype())) |
+ else: |
+ if not self.cf_is_null: |
+ if self.cf_maybe_null: |
+ code.put_xdecref_set( |
+ self.result(), rhs.result_as(self.ctype())) |
+ else: |
+ code.put_decref_set( |
+ self.result(), rhs.result_as(self.ctype())) |
+ else: |
+ assigned = False |
+ if is_external_ref: |
+ code.put_giveref(rhs.py_result()) |
+ if not self.type.is_memoryviewslice: |
+ if not assigned: |
+ code.putln('%s = %s;' % ( |
+ self.result(), rhs.result_as(self.ctype()))) |
+ if debug_disposal_code: |
+ print("NameNode.generate_assignment_code:") |
+ print("...generating post-assignment code for %s" % rhs) |
+ rhs.generate_post_assignment_code(code) |
+ elif rhs.result_in_temp(): |
+ rhs.generate_post_assignment_code(code) |
+ |
+ rhs.free_temps(code) |
+ |
+ def generate_acquire_memoryviewslice(self, rhs, code): |
+ """ |
+ Slices, coercions from objects, return values etc are new references. |
+ We have a borrowed reference in case of dst = src |
+ """ |
+ import MemoryView |
+ |
+ MemoryView.put_acquire_memoryviewslice( |
+ lhs_cname=self.result(), |
+ lhs_type=self.type, |
+ lhs_pos=self.pos, |
+ rhs=rhs, |
+ code=code, |
+ have_gil=not self.in_nogil_context, |
+ first_assignment=self.cf_is_null) |
+ |
+ def generate_acquire_buffer(self, rhs, code): |
+ # rhstmp is only used in case the rhs is a complicated expression leading to |
+ # the object, to avoid repeating the same C expression for every reference |
+ # to the rhs. It does NOT hold a reference. |
+ pretty_rhs = isinstance(rhs, NameNode) or rhs.is_temp |
+ if pretty_rhs: |
+ rhstmp = rhs.result_as(self.ctype()) |
+ else: |
+ rhstmp = code.funcstate.allocate_temp(self.entry.type, manage_ref=False) |
+ code.putln('%s = %s;' % (rhstmp, rhs.result_as(self.ctype()))) |
+ |
+ import Buffer |
+ Buffer.put_assign_to_buffer(self.result(), rhstmp, self.entry, |
+ is_initialized=not self.lhs_of_first_assignment, |
+ pos=self.pos, code=code) |
+ |
+ if not pretty_rhs: |
+ code.putln("%s = 0;" % rhstmp) |
+ code.funcstate.release_temp(rhstmp) |
+ |
+ def generate_deletion_code(self, code, ignore_nonexisting=False): |
+ if self.entry is None: |
+ return # There was an error earlier |
+ elif self.entry.is_pyclass_attr: |
+ namespace = self.entry.scope.namespace_cname |
+ interned_cname = code.intern_identifier(self.entry.name) |
+ if ignore_nonexisting: |
+ key_error_code = 'PyErr_Clear(); else' |
+ else: |
+ # minor hack: fake a NameError on KeyError |
+ key_error_code = ( |
+ '{ PyErr_Clear(); PyErr_Format(PyExc_NameError, "name \'%%s\' is not defined", "%s"); }' % |
+ self.entry.name) |
+ code.putln( |
+ 'if (unlikely(PyObject_DelItem(%s, %s) < 0)) {' |
+ ' if (likely(PyErr_ExceptionMatches(PyExc_KeyError))) %s' |
+ ' %s ' |
+ '}' % (namespace, interned_cname, |
+ key_error_code, |
+ code.error_goto(self.pos))) |
+ elif self.entry.is_pyglobal: |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("PyObjectSetAttrStr", "ObjectHandling.c")) |
+ interned_cname = code.intern_identifier(self.entry.name) |
+ del_code = '__Pyx_PyObject_DelAttrStr(%s, %s)' % ( |
+ Naming.module_cname, interned_cname) |
+ if ignore_nonexisting: |
+ code.putln('if (unlikely(%s < 0)) { if (likely(PyErr_ExceptionMatches(PyExc_AttributeError))) PyErr_Clear(); else %s }' % ( |
+ del_code, |
+ code.error_goto(self.pos))) |
+ else: |
+ code.put_error_if_neg(self.pos, del_code) |
+ elif self.entry.type.is_pyobject or self.entry.type.is_memoryviewslice: |
+ if not self.cf_is_null: |
+ if self.cf_maybe_null and not ignore_nonexisting: |
+ code.put_error_if_unbound(self.pos, self.entry) |
+ |
+ if self.entry.type.is_pyobject: |
+ if self.entry.in_closure: |
+ # generator |
+ if ignore_nonexisting and self.cf_maybe_null: |
+ code.put_xgotref(self.result()) |
+ else: |
+ code.put_gotref(self.result()) |
+ if ignore_nonexisting and self.cf_maybe_null: |
+ code.put_xdecref(self.result(), self.ctype()) |
+ else: |
+ code.put_decref(self.result(), self.ctype()) |
+ code.putln('%s = NULL;' % self.result()) |
+ else: |
+ code.put_xdecref_memoryviewslice(self.entry.cname, |
+ have_gil=not self.nogil) |
+ else: |
+ error(self.pos, "Deletion of C names not supported") |
+ |
+ def annotate(self, code): |
+ if hasattr(self, 'is_called') and self.is_called: |
+ pos = (self.pos[0], self.pos[1], self.pos[2] - len(self.name) - 1) |
+ if self.type.is_pyobject: |
+ style, text = 'py_call', 'python function (%s)' |
+ else: |
+ style, text = 'c_call', 'c function (%s)' |
+ code.annotate(pos, AnnotationItem(style, text % self.type, size=len(self.name))) |
+ |
+class BackquoteNode(ExprNode): |
+ # `expr` |
+ # |
+ # arg ExprNode |
+ |
+ type = py_object_type |
+ |
+ subexprs = ['arg'] |
+ |
+ def analyse_types(self, env): |
+ self.arg = self.arg.analyse_types(env) |
+ self.arg = self.arg.coerce_to_pyobject(env) |
+ self.is_temp = 1 |
+ return self |
+ |
+ gil_message = "Backquote expression" |
+ |
+ def calculate_constant_result(self): |
+ self.constant_result = repr(self.arg.constant_result) |
+ |
+ def generate_result_code(self, code): |
+ code.putln( |
+ "%s = PyObject_Repr(%s); %s" % ( |
+ self.result(), |
+ self.arg.py_result(), |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ |
+ |
+class ImportNode(ExprNode): |
+ # Used as part of import statement implementation. |
+ # Implements result = |
+ # __import__(module_name, globals(), None, name_list, level) |
+ # |
+ # module_name StringNode dotted name of module. Empty module |
+ # name means importing the parent package according |
+ # to level |
+ # name_list ListNode or None list of names to be imported |
+ # level int relative import level: |
+ # -1: attempt both relative import and absolute import; |
+ # 0: absolute import; |
+ # >0: the number of parent directories to search |
+ # relative to the current module. |
+ # None: decide the level according to language level and |
+ # directives |
+ |
+ type = py_object_type |
+ |
+ subexprs = ['module_name', 'name_list'] |
+ |
+ def analyse_types(self, env): |
+ if self.level is None: |
+ if (env.directives['py2_import'] or |
+ Future.absolute_import not in env.global_scope().context.future_directives): |
+ self.level = -1 |
+ else: |
+ self.level = 0 |
+ module_name = self.module_name.analyse_types(env) |
+ self.module_name = module_name.coerce_to_pyobject(env) |
+ if self.name_list: |
+ name_list = self.name_list.analyse_types(env) |
+ self.name_list = name_list.coerce_to_pyobject(env) |
+ self.is_temp = 1 |
+ env.use_utility_code(UtilityCode.load_cached("Import", "ImportExport.c")) |
+ return self |
+ |
+ gil_message = "Python import" |
+ |
+ def generate_result_code(self, code): |
+ if self.name_list: |
+ name_list_code = self.name_list.py_result() |
+ else: |
+ name_list_code = "0" |
+ code.putln( |
+ "%s = __Pyx_Import(%s, %s, %d); %s" % ( |
+ self.result(), |
+ self.module_name.py_result(), |
+ name_list_code, |
+ self.level, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ |
+ |
+class IteratorNode(ExprNode): |
+ # Used as part of for statement implementation. |
+ # |
+ # Implements result = iter(sequence) |
+ # |
+ # sequence ExprNode |
+ |
+ type = py_object_type |
+ iter_func_ptr = None |
+ counter_cname = None |
+ cpp_iterator_cname = None |
+ reversed = False # currently only used for list/tuple types (see Optimize.py) |
+ |
+ subexprs = ['sequence'] |
+ |
+ def analyse_types(self, env): |
+ self.sequence = self.sequence.analyse_types(env) |
+ if (self.sequence.type.is_array or self.sequence.type.is_ptr) and \ |
+ not self.sequence.type.is_string: |
+ # C array iteration will be transformed later on |
+ self.type = self.sequence.type |
+ elif self.sequence.type.is_cpp_class: |
+ self.analyse_cpp_types(env) |
+ else: |
+ self.sequence = self.sequence.coerce_to_pyobject(env) |
+ if self.sequence.type is list_type or \ |
+ self.sequence.type is tuple_type: |
+ self.sequence = self.sequence.as_none_safe_node("'NoneType' object is not iterable") |
+ self.is_temp = 1 |
+ return self |
+ |
+ gil_message = "Iterating over Python object" |
+ |
+ _func_iternext_type = PyrexTypes.CPtrType(PyrexTypes.CFuncType( |
+ PyrexTypes.py_object_type, [ |
+ PyrexTypes.CFuncTypeArg("it", PyrexTypes.py_object_type, None), |
+ ])) |
+ |
+ def type_dependencies(self, env): |
+ return self.sequence.type_dependencies(env) |
+ |
+ def infer_type(self, env): |
+ sequence_type = self.sequence.infer_type(env) |
+ if sequence_type.is_array or sequence_type.is_ptr: |
+ return sequence_type |
+ elif sequence_type.is_cpp_class: |
+ begin = sequence_type.scope.lookup("begin") |
+ if begin is not None: |
+ return begin.type.return_type |
+ elif sequence_type.is_pyobject: |
+ return sequence_type |
+ return py_object_type |
+ |
+ def analyse_cpp_types(self, env): |
+ sequence_type = self.sequence.type |
+ if sequence_type.is_ptr: |
+ sequence_type = sequence_type.base_type |
+ begin = sequence_type.scope.lookup("begin") |
+ end = sequence_type.scope.lookup("end") |
+ if (begin is None |
+ or not begin.type.is_cfunction |
+ or begin.type.args): |
+ error(self.pos, "missing begin() on %s" % self.sequence.type) |
+ self.type = error_type |
+ return |
+ if (end is None |
+ or not end.type.is_cfunction |
+ or end.type.args): |
+ error(self.pos, "missing end() on %s" % self.sequence.type) |
+ self.type = error_type |
+ return |
+ iter_type = begin.type.return_type |
+ if iter_type.is_cpp_class: |
+ if env.lookup_operator_for_types( |
+ self.pos, |
+ "!=", |
+ [iter_type, end.type.return_type]) is None: |
+ error(self.pos, "missing operator!= on result of begin() on %s" % self.sequence.type) |
+ self.type = error_type |
+ return |
+ if env.lookup_operator_for_types(self.pos, '++', [iter_type]) is None: |
+ error(self.pos, "missing operator++ on result of begin() on %s" % self.sequence.type) |
+ self.type = error_type |
+ return |
+ if env.lookup_operator_for_types(self.pos, '*', [iter_type]) is None: |
+ error(self.pos, "missing operator* on result of begin() on %s" % self.sequence.type) |
+ self.type = error_type |
+ return |
+ self.type = iter_type |
+ elif iter_type.is_ptr: |
+ if not (iter_type == end.type.return_type): |
+ error(self.pos, "incompatible types for begin() and end()") |
+ self.type = iter_type |
+ else: |
+ error(self.pos, "result type of begin() on %s must be a C++ class or pointer" % self.sequence.type) |
+ self.type = error_type |
+ return |
+ |
+ def generate_result_code(self, code): |
+ sequence_type = self.sequence.type |
+ if sequence_type.is_cpp_class: |
+ if self.sequence.is_name: |
+ # safe: C++ won't allow you to reassign to class references |
+ begin_func = "%s.begin" % self.sequence.result() |
+ else: |
+ sequence_type = PyrexTypes.c_ptr_type(sequence_type) |
+ self.cpp_iterator_cname = code.funcstate.allocate_temp(sequence_type, manage_ref=False) |
+ code.putln("%s = &%s;" % (self.cpp_iterator_cname, self.sequence.result())) |
+ begin_func = "%s->begin" % self.cpp_iterator_cname |
+ # TODO: Limit scope. |
+ code.putln("%s = %s();" % (self.result(), begin_func)) |
+ return |
+ if sequence_type.is_array or sequence_type.is_ptr: |
+ raise InternalError("for in carray slice not transformed") |
+ is_builtin_sequence = sequence_type is list_type or \ |
+ sequence_type is tuple_type |
+ if not is_builtin_sequence: |
+ # reversed() not currently optimised (see Optimize.py) |
+ assert not self.reversed, "internal error: reversed() only implemented for list/tuple objects" |
+ self.may_be_a_sequence = not sequence_type.is_builtin_type |
+ if self.may_be_a_sequence: |
+ code.putln( |
+ "if (PyList_CheckExact(%s) || PyTuple_CheckExact(%s)) {" % ( |
+ self.sequence.py_result(), |
+ self.sequence.py_result())) |
+ if is_builtin_sequence or self.may_be_a_sequence: |
+ self.counter_cname = code.funcstate.allocate_temp( |
+ PyrexTypes.c_py_ssize_t_type, manage_ref=False) |
+ if self.reversed: |
+ if sequence_type is list_type: |
+ init_value = 'PyList_GET_SIZE(%s) - 1' % self.result() |
+ else: |
+ init_value = 'PyTuple_GET_SIZE(%s) - 1' % self.result() |
+ else: |
+ init_value = '0' |
+ code.putln( |
+ "%s = %s; __Pyx_INCREF(%s); %s = %s;" % ( |
+ self.result(), |
+ self.sequence.py_result(), |
+ self.result(), |
+ self.counter_cname, |
+ init_value |
+ )) |
+ if not is_builtin_sequence: |
+ self.iter_func_ptr = code.funcstate.allocate_temp(self._func_iternext_type, manage_ref=False) |
+ if self.may_be_a_sequence: |
+ code.putln("%s = NULL;" % self.iter_func_ptr) |
+ code.putln("} else {") |
+ code.put("%s = -1; " % self.counter_cname) |
+ code.putln("%s = PyObject_GetIter(%s); %s" % ( |
+ self.result(), |
+ self.sequence.py_result(), |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ code.putln("%s = Py_TYPE(%s)->tp_iternext;" % (self.iter_func_ptr, self.py_result())) |
+ if self.may_be_a_sequence: |
+ code.putln("}") |
+ |
+ def generate_next_sequence_item(self, test_name, result_name, code): |
+ assert self.counter_cname, "internal error: counter_cname temp not prepared" |
+ final_size = 'Py%s_GET_SIZE(%s)' % (test_name, self.py_result()) |
+ if self.sequence.is_sequence_constructor: |
+ item_count = len(self.sequence.args) |
+ if self.sequence.mult_factor is None: |
+ final_size = item_count |
+ elif isinstance(self.sequence.mult_factor.constant_result, (int, long)): |
+ final_size = item_count * self.sequence.mult_factor.constant_result |
+ code.putln("if (%s >= %s) break;" % (self.counter_cname, final_size)) |
+ if self.reversed: |
+ inc_dec = '--' |
+ else: |
+ inc_dec = '++' |
+ code.putln("#if CYTHON_COMPILING_IN_CPYTHON") |
+ code.putln( |
+ "%s = Py%s_GET_ITEM(%s, %s); __Pyx_INCREF(%s); %s%s; %s" % ( |
+ result_name, |
+ test_name, |
+ self.py_result(), |
+ self.counter_cname, |
+ result_name, |
+ self.counter_cname, |
+ inc_dec, |
+ # use the error label to avoid C compiler warnings if we only use it below |
+ code.error_goto_if_neg('0', self.pos) |
+ )) |
+ code.putln("#else") |
+ code.putln( |
+ "%s = PySequence_ITEM(%s, %s); %s%s; %s" % ( |
+ result_name, |
+ self.py_result(), |
+ self.counter_cname, |
+ self.counter_cname, |
+ inc_dec, |
+ code.error_goto_if_null(result_name, self.pos))) |
+ code.putln("#endif") |
+ |
+ def generate_iter_next_result_code(self, result_name, code): |
+ sequence_type = self.sequence.type |
+ if self.reversed: |
+ code.putln("if (%s < 0) break;" % self.counter_cname) |
+ if sequence_type.is_cpp_class: |
+ if self.cpp_iterator_cname: |
+ end_func = "%s->end" % self.cpp_iterator_cname |
+ else: |
+ end_func = "%s.end" % self.sequence.result() |
+ # TODO: Cache end() call? |
+ code.putln("if (!(%s != %s())) break;" % ( |
+ self.result(), |
+ end_func)) |
+ code.putln("%s = *%s;" % ( |
+ result_name, |
+ self.result())) |
+ code.putln("++%s;" % self.result()) |
+ return |
+ elif sequence_type is list_type: |
+ self.generate_next_sequence_item('List', result_name, code) |
+ return |
+ elif sequence_type is tuple_type: |
+ self.generate_next_sequence_item('Tuple', result_name, code) |
+ return |
+ |
+ if self.may_be_a_sequence: |
+ for test_name in ('List', 'Tuple'): |
+ code.putln("if (!%s && Py%s_CheckExact(%s)) {" % ( |
+ self.iter_func_ptr, test_name, self.py_result())) |
+ self.generate_next_sequence_item(test_name, result_name, code) |
+ code.put("} else ") |
+ |
+ code.putln("{") |
+ code.putln( |
+ "%s = %s(%s);" % ( |
+ result_name, |
+ self.iter_func_ptr, |
+ self.py_result())) |
+ code.putln("if (unlikely(!%s)) {" % result_name) |
+ code.putln("PyObject* exc_type = PyErr_Occurred();") |
+ code.putln("if (exc_type) {") |
+ code.putln("if (likely(exc_type == PyExc_StopIteration ||" |
+ " PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear();") |
+ code.putln("else %s" % code.error_goto(self.pos)) |
+ code.putln("}") |
+ code.putln("break;") |
+ code.putln("}") |
+ code.put_gotref(result_name) |
+ code.putln("}") |
+ |
+ def free_temps(self, code): |
+ if self.counter_cname: |
+ code.funcstate.release_temp(self.counter_cname) |
+ if self.iter_func_ptr: |
+ code.funcstate.release_temp(self.iter_func_ptr) |
+ self.iter_func_ptr = None |
+ if self.cpp_iterator_cname: |
+ code.funcstate.release_temp(self.cpp_iterator_cname) |
+ ExprNode.free_temps(self, code) |
+ |
+ |
+class NextNode(AtomicExprNode): |
+ # Used as part of for statement implementation. |
+ # Implements result = iterator.next() |
+ # Created during analyse_types phase. |
+ # The iterator is not owned by this node. |
+ # |
+ # iterator IteratorNode |
+ |
+ def __init__(self, iterator): |
+ AtomicExprNode.__init__(self, iterator.pos) |
+ self.iterator = iterator |
+ |
+ def type_dependencies(self, env): |
+ return self.iterator.type_dependencies(env) |
+ |
+ def infer_type(self, env, iterator_type = None): |
+ if iterator_type is None: |
+ iterator_type = self.iterator.infer_type(env) |
+ if iterator_type.is_ptr or iterator_type.is_array: |
+ return iterator_type.base_type |
+ elif iterator_type.is_cpp_class: |
+ item_type = env.lookup_operator_for_types(self.pos, "*", [iterator_type]).type.return_type |
+ if item_type.is_reference: |
+ item_type = item_type.ref_base_type |
+ if item_type.is_const: |
+ item_type = item_type.const_base_type |
+ return item_type |
+ else: |
+ # Avoid duplication of complicated logic. |
+ fake_index_node = IndexNode( |
+ self.pos, |
+ base=self.iterator.sequence, |
+ index=IntNode(self.pos, value='PY_SSIZE_T_MAX', |
+ type=PyrexTypes.c_py_ssize_t_type)) |
+ return fake_index_node.infer_type(env) |
+ |
+ def analyse_types(self, env): |
+ self.type = self.infer_type(env, self.iterator.type) |
+ self.is_temp = 1 |
+ return self |
+ |
+ def generate_result_code(self, code): |
+ self.iterator.generate_iter_next_result_code(self.result(), code) |
+ |
+ |
+class WithExitCallNode(ExprNode): |
+ # The __exit__() call of a 'with' statement. Used in both the |
+ # except and finally clauses. |
+ |
+ # with_stat WithStatNode the surrounding 'with' statement |
+ # args TupleNode or ResultStatNode the exception info tuple |
+ |
+ subexprs = ['args'] |
+ test_if_run = True |
+ |
+ def analyse_types(self, env): |
+ self.args = self.args.analyse_types(env) |
+ self.type = PyrexTypes.c_bint_type |
+ self.is_temp = True |
+ return self |
+ |
+ def generate_evaluation_code(self, code): |
+ if self.test_if_run: |
+ # call only if it was not already called (and decref-cleared) |
+ code.putln("if (%s) {" % self.with_stat.exit_var) |
+ |
+ self.args.generate_evaluation_code(code) |
+ result_var = code.funcstate.allocate_temp(py_object_type, manage_ref=False) |
+ |
+ code.mark_pos(self.pos) |
+ code.globalstate.use_utility_code(UtilityCode.load_cached( |
+ "PyObjectCall", "ObjectHandling.c")) |
+ code.putln("%s = __Pyx_PyObject_Call(%s, %s, NULL);" % ( |
+ result_var, |
+ self.with_stat.exit_var, |
+ self.args.result())) |
+ code.put_decref_clear(self.with_stat.exit_var, type=py_object_type) |
+ self.args.generate_disposal_code(code) |
+ self.args.free_temps(code) |
+ |
+ code.putln(code.error_goto_if_null(result_var, self.pos)) |
+ code.put_gotref(result_var) |
+ if self.result_is_used: |
+ self.allocate_temp_result(code) |
+ code.putln("%s = __Pyx_PyObject_IsTrue(%s);" % (self.result(), result_var)) |
+ code.put_decref_clear(result_var, type=py_object_type) |
+ if self.result_is_used: |
+ code.put_error_if_neg(self.pos, self.result()) |
+ code.funcstate.release_temp(result_var) |
+ if self.test_if_run: |
+ code.putln("}") |
+ |
+ |
+class ExcValueNode(AtomicExprNode): |
+ # Node created during analyse_types phase |
+ # of an ExceptClauseNode to fetch the current |
+ # exception value. |
+ |
+ type = py_object_type |
+ |
+ def __init__(self, pos): |
+ ExprNode.__init__(self, pos) |
+ |
+ def set_var(self, var): |
+ self.var = var |
+ |
+ def calculate_result_code(self): |
+ return self.var |
+ |
+ def generate_result_code(self, code): |
+ pass |
+ |
+ def analyse_types(self, env): |
+ return self |
+ |
+ |
+class TempNode(ExprNode): |
+ # Node created during analyse_types phase |
+ # of some nodes to hold a temporary value. |
+ # |
+ # Note: One must call "allocate" and "release" on |
+ # the node during code generation to get/release the temp. |
+ # This is because the temp result is often used outside of |
+ # the regular cycle. |
+ |
+ subexprs = [] |
+ |
+ def __init__(self, pos, type, env=None): |
+ ExprNode.__init__(self, pos) |
+ self.type = type |
+ if type.is_pyobject: |
+ self.result_ctype = py_object_type |
+ self.is_temp = 1 |
+ |
+ def analyse_types(self, env): |
+ return self |
+ |
+ def analyse_target_declaration(self, env): |
+ pass |
+ |
+ def generate_result_code(self, code): |
+ pass |
+ |
+ def allocate(self, code): |
+ self.temp_cname = code.funcstate.allocate_temp(self.type, manage_ref=True) |
+ |
+ def release(self, code): |
+ code.funcstate.release_temp(self.temp_cname) |
+ self.temp_cname = None |
+ |
+ def result(self): |
+ try: |
+ return self.temp_cname |
+ except: |
+ assert False, "Remember to call allocate/release on TempNode" |
+ raise |
+ |
+ # Do not participate in normal temp alloc/dealloc: |
+ def allocate_temp_result(self, code): |
+ pass |
+ |
+ def release_temp_result(self, code): |
+ pass |
+ |
+class PyTempNode(TempNode): |
+ # TempNode holding a Python value. |
+ |
+ def __init__(self, pos, env): |
+ TempNode.__init__(self, pos, PyrexTypes.py_object_type, env) |
+ |
+class RawCNameExprNode(ExprNode): |
+ subexprs = [] |
+ |
+ def __init__(self, pos, type=None, cname=None): |
+ ExprNode.__init__(self, pos, type=type) |
+ if cname is not None: |
+ self.cname = cname |
+ |
+ def analyse_types(self, env): |
+ return self |
+ |
+ def set_cname(self, cname): |
+ self.cname = cname |
+ |
+ def result(self): |
+ return self.cname |
+ |
+ def generate_result_code(self, code): |
+ pass |
+ |
+ |
+#------------------------------------------------------------------- |
+# |
+# Parallel nodes (cython.parallel.thread(savailable|id)) |
+# |
+#------------------------------------------------------------------- |
+ |
+class ParallelThreadsAvailableNode(AtomicExprNode): |
+ """ |
+ Note: this is disabled and not a valid directive at this moment |
+ |
+ Implements cython.parallel.threadsavailable(). If we are called from the |
+ sequential part of the application, we need to call omp_get_max_threads(), |
+ and in the parallel part we can just call omp_get_num_threads() |
+ """ |
+ |
+ type = PyrexTypes.c_int_type |
+ |
+ def analyse_types(self, env): |
+ self.is_temp = True |
+ # env.add_include_file("omp.h") |
+ return self |
+ |
+ def generate_result_code(self, code): |
+ code.putln("#ifdef _OPENMP") |
+ code.putln("if (omp_in_parallel()) %s = omp_get_max_threads();" % |
+ self.temp_code) |
+ code.putln("else %s = omp_get_num_threads();" % self.temp_code) |
+ code.putln("#else") |
+ code.putln("%s = 1;" % self.temp_code) |
+ code.putln("#endif") |
+ |
+ def result(self): |
+ return self.temp_code |
+ |
+ |
+class ParallelThreadIdNode(AtomicExprNode): #, Nodes.ParallelNode): |
+ """ |
+ Implements cython.parallel.threadid() |
+ """ |
+ |
+ type = PyrexTypes.c_int_type |
+ |
+ def analyse_types(self, env): |
+ self.is_temp = True |
+ # env.add_include_file("omp.h") |
+ return self |
+ |
+ def generate_result_code(self, code): |
+ code.putln("#ifdef _OPENMP") |
+ code.putln("%s = omp_get_thread_num();" % self.temp_code) |
+ code.putln("#else") |
+ code.putln("%s = 0;" % self.temp_code) |
+ code.putln("#endif") |
+ |
+ def result(self): |
+ return self.temp_code |
+ |
+ |
+#------------------------------------------------------------------- |
+# |
+# Trailer nodes |
+# |
+#------------------------------------------------------------------- |
+ |
+class IndexNode(ExprNode): |
+ # Sequence indexing. |
+ # |
+ # base ExprNode |
+ # index ExprNode |
+ # indices [ExprNode] |
+ # type_indices [PyrexType] |
+ # is_buffer_access boolean Whether this is a buffer access. |
+ # |
+ # indices is used on buffer access, index on non-buffer access. |
+ # The former contains a clean list of index parameters, the |
+ # latter whatever Python object is needed for index access. |
+ # |
+ # is_fused_index boolean Whether the index is used to specialize a |
+ # c(p)def function |
+ |
+ subexprs = ['base', 'index', 'indices'] |
+ indices = None |
+ type_indices = None |
+ |
+ is_subscript = True |
+ is_fused_index = False |
+ |
+ # Whether we're assigning to a buffer (in that case it needs to be |
+ # writable) |
+ writable_needed = False |
+ |
+ # Whether we are indexing or slicing a memoryviewslice |
+ memslice_index = False |
+ memslice_slice = False |
+ is_memslice_copy = False |
+ memslice_ellipsis_noop = False |
+ warned_untyped_idx = False |
+ # set by SingleAssignmentNode after analyse_types() |
+ is_memslice_scalar_assignment = False |
+ |
+ def __init__(self, pos, index, **kw): |
+ ExprNode.__init__(self, pos, index=index, **kw) |
+ self._index = index |
+ |
+ def calculate_constant_result(self): |
+ self.constant_result = \ |
+ self.base.constant_result[self.index.constant_result] |
+ |
+ def compile_time_value(self, denv): |
+ base = self.base.compile_time_value(denv) |
+ index = self.index.compile_time_value(denv) |
+ try: |
+ return base[index] |
+ except Exception, e: |
+ self.compile_time_value_error(e) |
+ |
+ def is_ephemeral(self): |
+ return self.base.is_ephemeral() |
+ |
+ def is_simple(self): |
+ if self.is_buffer_access or self.memslice_index: |
+ return False |
+ elif self.memslice_slice: |
+ return True |
+ |
+ base = self.base |
+ return (base.is_simple() and self.index.is_simple() |
+ and base.type and (base.type.is_ptr or base.type.is_array)) |
+ |
+ def may_be_none(self): |
+ base_type = self.base.type |
+ if base_type: |
+ if base_type.is_string: |
+ return False |
+ if isinstance(self.index, SliceNode): |
+ # slicing! |
+ if base_type in (bytes_type, str_type, unicode_type, |
+ basestring_type, list_type, tuple_type): |
+ return False |
+ return ExprNode.may_be_none(self) |
+ |
+ def analyse_target_declaration(self, env): |
+ pass |
+ |
+ def analyse_as_type(self, env): |
+ base_type = self.base.analyse_as_type(env) |
+ if base_type and not base_type.is_pyobject: |
+ if base_type.is_cpp_class: |
+ if isinstance(self.index, TupleNode): |
+ template_values = self.index.args |
+ else: |
+ template_values = [self.index] |
+ import Nodes |
+ type_node = Nodes.TemplatedTypeNode( |
+ pos = self.pos, |
+ positional_args = template_values, |
+ keyword_args = None) |
+ return type_node.analyse(env, base_type = base_type) |
+ else: |
+ index = self.index.compile_time_value(env) |
+ if index is not None: |
+ return PyrexTypes.CArrayType(base_type, int(index)) |
+ error(self.pos, "Array size must be a compile time constant") |
+ return None |
+ |
+ def type_dependencies(self, env): |
+ return self.base.type_dependencies(env) + self.index.type_dependencies(env) |
+ |
+ def infer_type(self, env): |
+ base_type = self.base.infer_type(env) |
+ if isinstance(self.index, SliceNode): |
+ # slicing! |
+ if base_type.is_string: |
+ # sliced C strings must coerce to Python |
+ return bytes_type |
+ elif base_type.is_pyunicode_ptr: |
+ # sliced Py_UNICODE* strings must coerce to Python |
+ return unicode_type |
+ elif base_type in (unicode_type, bytes_type, str_type, |
+ bytearray_type, list_type, tuple_type): |
+ # slicing these returns the same type |
+ return base_type |
+ else: |
+ # TODO: Handle buffers (hopefully without too much redundancy). |
+ return py_object_type |
+ |
+ index_type = self.index.infer_type(env) |
+ if index_type and index_type.is_int or isinstance(self.index, IntNode): |
+ # indexing! |
+ if base_type is unicode_type: |
+ # Py_UCS4 will automatically coerce to a unicode string |
+ # if required, so this is safe. We only infer Py_UCS4 |
+ # when the index is a C integer type. Otherwise, we may |
+ # need to use normal Python item access, in which case |
+ # it's faster to return the one-char unicode string than |
+ # to receive it, throw it away, and potentially rebuild it |
+ # on a subsequent PyObject coercion. |
+ return PyrexTypes.c_py_ucs4_type |
+ elif base_type is str_type: |
+ # always returns str - Py2: bytes, Py3: unicode |
+ return base_type |
+ elif base_type is bytearray_type: |
+ return PyrexTypes.c_uchar_type |
+ elif isinstance(self.base, BytesNode): |
+ #if env.global_scope().context.language_level >= 3: |
+ # # inferring 'char' can be made to work in Python 3 mode |
+ # return PyrexTypes.c_char_type |
+ # Py2/3 return different types on indexing bytes objects |
+ return py_object_type |
+ elif base_type in (tuple_type, list_type): |
+ # if base is a literal, take a look at its values |
+ item_type = infer_sequence_item_type( |
+ env, self.base, self.index, seq_type=base_type) |
+ if item_type is not None: |
+ return item_type |
+ elif base_type.is_ptr or base_type.is_array: |
+ return base_type.base_type |
+ |
+ if base_type.is_cpp_class: |
+ class FakeOperand: |
+ def __init__(self, **kwds): |
+ self.__dict__.update(kwds) |
+ operands = [ |
+ FakeOperand(pos=self.pos, type=base_type), |
+ FakeOperand(pos=self.pos, type=index_type), |
+ ] |
+ index_func = env.lookup_operator('[]', operands) |
+ if index_func is not None: |
+ return index_func.type.return_type |
+ |
+ # may be slicing or indexing, we don't know |
+ if base_type in (unicode_type, str_type): |
+ # these types always returns their own type on Python indexing/slicing |
+ return base_type |
+ else: |
+ # TODO: Handle buffers (hopefully without too much redundancy). |
+ return py_object_type |
+ |
+ def analyse_types(self, env): |
+ return self.analyse_base_and_index_types(env, getting=True) |
+ |
+ def analyse_target_types(self, env): |
+ node = self.analyse_base_and_index_types(env, setting=True) |
+ if node.type.is_const: |
+ error(self.pos, "Assignment to const dereference") |
+ if not node.is_lvalue(): |
+ error(self.pos, "Assignment to non-lvalue of type '%s'" % node.type) |
+ return node |
+ |
+ def analyse_base_and_index_types(self, env, getting=False, setting=False, |
+ analyse_base=True): |
+ # Note: This might be cleaned up by having IndexNode |
+ # parsed in a saner way and only construct the tuple if |
+ # needed. |
+ |
+ # Note that this function must leave IndexNode in a cloneable state. |
+ # For buffers, self.index is packed out on the initial analysis, and |
+ # when cloning self.indices is copied. |
+ self.is_buffer_access = False |
+ |
+ # a[...] = b |
+ self.is_memslice_copy = False |
+ # incomplete indexing, Ellipsis indexing or slicing |
+ self.memslice_slice = False |
+ # integer indexing |
+ self.memslice_index = False |
+ |
+ if analyse_base: |
+ self.base = self.base.analyse_types(env) |
+ |
+ if self.base.type.is_error: |
+ # Do not visit child tree if base is undeclared to avoid confusing |
+ # error messages |
+ self.type = PyrexTypes.error_type |
+ return self |
+ |
+ is_slice = isinstance(self.index, SliceNode) |
+ |
+ if not env.directives['wraparound']: |
+ if is_slice: |
+ check_negative_indices(self.index.start, self.index.stop) |
+ else: |
+ check_negative_indices(self.index) |
+ |
+ # Potentially overflowing index value. |
+ if not is_slice and isinstance(self.index, IntNode) and Utils.long_literal(self.index.value): |
+ self.index = self.index.coerce_to_pyobject(env) |
+ |
+ is_memslice = self.base.type.is_memoryviewslice |
+ |
+ # Handle the case where base is a literal char* (and we expect a string, not an int) |
+ if not is_memslice and (isinstance(self.base, BytesNode) or is_slice): |
+ if self.base.type.is_string or not (self.base.type.is_ptr or self.base.type.is_array): |
+ self.base = self.base.coerce_to_pyobject(env) |
+ |
+ skip_child_analysis = False |
+ buffer_access = False |
+ |
+ if self.indices: |
+ indices = self.indices |
+ elif isinstance(self.index, TupleNode): |
+ indices = self.index.args |
+ else: |
+ indices = [self.index] |
+ |
+ if (is_memslice and not self.indices and |
+ isinstance(self.index, EllipsisNode)): |
+ # Memoryviewslice copying |
+ self.is_memslice_copy = True |
+ |
+ elif is_memslice: |
+ # memoryviewslice indexing or slicing |
+ import MemoryView |
+ |
+ skip_child_analysis = True |
+ newaxes = [newaxis for newaxis in indices if newaxis.is_none] |
+ have_slices, indices = MemoryView.unellipsify(indices, |
+ newaxes, |
+ self.base.type.ndim) |
+ |
+ self.memslice_index = (not newaxes and |
+ len(indices) == self.base.type.ndim) |
+ axes = [] |
+ |
+ index_type = PyrexTypes.c_py_ssize_t_type |
+ new_indices = [] |
+ |
+ if len(indices) - len(newaxes) > self.base.type.ndim: |
+ self.type = error_type |
+ error(indices[self.base.type.ndim].pos, |
+ "Too many indices specified for type %s" % |
+ self.base.type) |
+ return self |
+ |
+ axis_idx = 0 |
+ for i, index in enumerate(indices[:]): |
+ index = index.analyse_types(env) |
+ if not index.is_none: |
+ access, packing = self.base.type.axes[axis_idx] |
+ axis_idx += 1 |
+ |
+ if isinstance(index, SliceNode): |
+ self.memslice_slice = True |
+ if index.step.is_none: |
+ axes.append((access, packing)) |
+ else: |
+ axes.append((access, 'strided')) |
+ |
+ # Coerce start, stop and step to temps of the right type |
+ for attr in ('start', 'stop', 'step'): |
+ value = getattr(index, attr) |
+ if not value.is_none: |
+ value = value.coerce_to(index_type, env) |
+ #value = value.coerce_to_temp(env) |
+ setattr(index, attr, value) |
+ new_indices.append(value) |
+ |
+ elif index.is_none: |
+ self.memslice_slice = True |
+ new_indices.append(index) |
+ axes.append(('direct', 'strided')) |
+ |
+ elif index.type.is_int or index.type.is_pyobject: |
+ if index.type.is_pyobject and not self.warned_untyped_idx: |
+ warning(index.pos, "Index should be typed for more " |
+ "efficient access", level=2) |
+ IndexNode.warned_untyped_idx = True |
+ |
+ self.memslice_index = True |
+ index = index.coerce_to(index_type, env) |
+ indices[i] = index |
+ new_indices.append(index) |
+ |
+ else: |
+ self.type = error_type |
+ error(index.pos, "Invalid index for memoryview specified") |
+ return self |
+ |
+ self.memslice_index = self.memslice_index and not self.memslice_slice |
+ self.original_indices = indices |
+ # All indices with all start/stop/step for slices. |
+ # We need to keep this around |
+ self.indices = new_indices |
+ self.env = env |
+ |
+ elif self.base.type.is_buffer: |
+ # Buffer indexing |
+ if len(indices) == self.base.type.ndim: |
+ buffer_access = True |
+ skip_child_analysis = True |
+ for x in indices: |
+ x = x.analyse_types(env) |
+ if not x.type.is_int: |
+ buffer_access = False |
+ |
+ if buffer_access and not self.base.type.is_memoryviewslice: |
+ assert hasattr(self.base, "entry") # Must be a NameNode-like node |
+ |
+ # On cloning, indices is cloned. Otherwise, unpack index into indices |
+ assert not (buffer_access and isinstance(self.index, CloneNode)) |
+ |
+ self.nogil = env.nogil |
+ |
+ if buffer_access or self.memslice_index: |
+ #if self.base.type.is_memoryviewslice and not self.base.is_name: |
+ # self.base = self.base.coerce_to_temp(env) |
+ self.base = self.base.coerce_to_simple(env) |
+ |
+ self.indices = indices |
+ self.index = None |
+ self.type = self.base.type.dtype |
+ self.is_buffer_access = True |
+ self.buffer_type = self.base.type #self.base.entry.type |
+ |
+ if getting and self.type.is_pyobject: |
+ self.is_temp = True |
+ |
+ if setting and self.base.type.is_memoryviewslice: |
+ self.base.type.writable_needed = True |
+ elif setting: |
+ if not self.base.entry.type.writable: |
+ error(self.pos, "Writing to readonly buffer") |
+ else: |
+ self.writable_needed = True |
+ if self.base.type.is_buffer: |
+ self.base.entry.buffer_aux.writable_needed = True |
+ |
+ elif self.is_memslice_copy: |
+ self.type = self.base.type |
+ if getting: |
+ self.memslice_ellipsis_noop = True |
+ else: |
+ self.memslice_broadcast = True |
+ |
+ elif self.memslice_slice: |
+ self.index = None |
+ self.is_temp = True |
+ self.use_managed_ref = True |
+ |
+ if not MemoryView.validate_axes(self.pos, axes): |
+ self.type = error_type |
+ return self |
+ |
+ self.type = PyrexTypes.MemoryViewSliceType( |
+ self.base.type.dtype, axes) |
+ |
+ if (self.base.type.is_memoryviewslice and not |
+ self.base.is_name and not |
+ self.base.result_in_temp()): |
+ self.base = self.base.coerce_to_temp(env) |
+ |
+ if setting: |
+ self.memslice_broadcast = True |
+ |
+ else: |
+ base_type = self.base.type |
+ |
+ if not base_type.is_cfunction: |
+ if isinstance(self.index, TupleNode): |
+ self.index = self.index.analyse_types( |
+ env, skip_children=skip_child_analysis) |
+ elif not skip_child_analysis: |
+ self.index = self.index.analyse_types(env) |
+ self.original_index_type = self.index.type |
+ |
+ if base_type.is_unicode_char: |
+ # we infer Py_UNICODE/Py_UCS4 for unicode strings in some |
+ # cases, but indexing must still work for them |
+ if setting: |
+ warning(self.pos, "cannot assign to Unicode string index", level=1) |
+ elif self.index.constant_result in (0, -1): |
+ # uchar[0] => uchar |
+ return self.base |
+ self.base = self.base.coerce_to_pyobject(env) |
+ base_type = self.base.type |
+ if base_type.is_pyobject: |
+ if self.index.type.is_int and base_type is not dict_type: |
+ if (getting |
+ and (base_type in (list_type, tuple_type, bytearray_type)) |
+ and (not self.index.type.signed |
+ or not env.directives['wraparound'] |
+ or (isinstance(self.index, IntNode) and |
+ self.index.has_constant_result() and self.index.constant_result >= 0)) |
+ and not env.directives['boundscheck']): |
+ self.is_temp = 0 |
+ else: |
+ self.is_temp = 1 |
+ self.index = self.index.coerce_to(PyrexTypes.c_py_ssize_t_type, env).coerce_to_simple(env) |
+ self.original_index_type.create_to_py_utility_code(env) |
+ else: |
+ self.index = self.index.coerce_to_pyobject(env) |
+ self.is_temp = 1 |
+ if self.index.type.is_int and base_type is unicode_type: |
+ # Py_UNICODE/Py_UCS4 will automatically coerce to a unicode string |
+ # if required, so this is fast and safe |
+ self.type = PyrexTypes.c_py_ucs4_type |
+ elif self.index.type.is_int and base_type is bytearray_type: |
+ if setting: |
+ self.type = PyrexTypes.c_uchar_type |
+ else: |
+ # not using 'uchar' to enable fast and safe error reporting as '-1' |
+ self.type = PyrexTypes.c_int_type |
+ elif is_slice and base_type in (bytes_type, str_type, unicode_type, list_type, tuple_type): |
+ self.type = base_type |
+ else: |
+ item_type = None |
+ if base_type in (list_type, tuple_type) and self.index.type.is_int: |
+ item_type = infer_sequence_item_type( |
+ env, self.base, self.index, seq_type=base_type) |
+ if item_type is None: |
+ item_type = py_object_type |
+ self.type = item_type |
+ if base_type in (list_type, tuple_type, dict_type): |
+ # do the None check explicitly (not in a helper) to allow optimising it away |
+ self.base = self.base.as_none_safe_node("'NoneType' object is not subscriptable") |
+ else: |
+ if base_type.is_ptr or base_type.is_array: |
+ self.type = base_type.base_type |
+ if is_slice: |
+ self.type = base_type |
+ elif self.index.type.is_pyobject: |
+ self.index = self.index.coerce_to( |
+ PyrexTypes.c_py_ssize_t_type, env) |
+ elif not self.index.type.is_int: |
+ error(self.pos, |
+ "Invalid index type '%s'" % |
+ self.index.type) |
+ elif base_type.is_cpp_class: |
+ function = env.lookup_operator("[]", [self.base, self.index]) |
+ if function is None: |
+ error(self.pos, "Indexing '%s' not supported for index type '%s'" % (base_type, self.index.type)) |
+ self.type = PyrexTypes.error_type |
+ self.result_code = "<error>" |
+ return self |
+ func_type = function.type |
+ if func_type.is_ptr: |
+ func_type = func_type.base_type |
+ self.index = self.index.coerce_to(func_type.args[0].type, env) |
+ self.type = func_type.return_type |
+ if setting and not func_type.return_type.is_reference: |
+ error(self.pos, "Can't set non-reference result '%s'" % self.type) |
+ elif base_type.is_cfunction: |
+ if base_type.is_fused: |
+ self.parse_indexed_fused_cdef(env) |
+ else: |
+ self.type_indices = self.parse_index_as_types(env) |
+ if base_type.templates is None: |
+ error(self.pos, "Can only parameterize template functions.") |
+ elif len(base_type.templates) != len(self.type_indices): |
+ error(self.pos, "Wrong number of template arguments: expected %s, got %s" % ( |
+ (len(base_type.templates), len(self.type_indices)))) |
+ self.type = base_type.specialize(dict(zip(base_type.templates, self.type_indices))) |
+ else: |
+ error(self.pos, |
+ "Attempting to index non-array type '%s'" % |
+ base_type) |
+ self.type = PyrexTypes.error_type |
+ |
+ self.wrap_in_nonecheck_node(env, getting) |
+ return self |
+ |
+ def wrap_in_nonecheck_node(self, env, getting): |
+ if not env.directives['nonecheck'] or not self.base.may_be_none(): |
+ return |
+ |
+ if self.base.type.is_memoryviewslice: |
+ if self.is_memslice_copy and not getting: |
+ msg = "Cannot assign to None memoryview slice" |
+ elif self.memslice_slice: |
+ msg = "Cannot slice None memoryview slice" |
+ else: |
+ msg = "Cannot index None memoryview slice" |
+ else: |
+ msg = "'NoneType' object is not subscriptable" |
+ |
+ self.base = self.base.as_none_safe_node(msg) |
+ |
+ def parse_index_as_types(self, env, required=True): |
+ if isinstance(self.index, TupleNode): |
+ indices = self.index.args |
+ else: |
+ indices = [self.index] |
+ type_indices = [] |
+ for index in indices: |
+ type_indices.append(index.analyse_as_type(env)) |
+ if type_indices[-1] is None: |
+ if required: |
+ error(index.pos, "not parsable as a type") |
+ return None |
+ return type_indices |
+ |
+ def parse_indexed_fused_cdef(self, env): |
+ """ |
+ Interpret fused_cdef_func[specific_type1, ...] |
+ |
+ Note that if this method is called, we are an indexed cdef function |
+ with fused argument types, and this IndexNode will be replaced by the |
+ NameNode with specific entry just after analysis of expressions by |
+ AnalyseExpressionsTransform. |
+ """ |
+ self.type = PyrexTypes.error_type |
+ |
+ self.is_fused_index = True |
+ |
+ base_type = self.base.type |
+ specific_types = [] |
+ positions = [] |
+ |
+ if self.index.is_name or self.index.is_attribute: |
+ positions.append(self.index.pos) |
+ elif isinstance(self.index, TupleNode): |
+ for arg in self.index.args: |
+ positions.append(arg.pos) |
+ specific_types = self.parse_index_as_types(env, required=False) |
+ |
+ if specific_types is None: |
+ self.index = self.index.analyse_types(env) |
+ |
+ if not self.base.entry.as_variable: |
+ error(self.pos, "Can only index fused functions with types") |
+ else: |
+ # A cpdef function indexed with Python objects |
+ self.base.entry = self.entry = self.base.entry.as_variable |
+ self.base.type = self.type = self.entry.type |
+ |
+ self.base.is_temp = True |
+ self.is_temp = True |
+ |
+ self.entry.used = True |
+ |
+ self.is_fused_index = False |
+ return |
+ |
+ for i, type in enumerate(specific_types): |
+ specific_types[i] = type.specialize_fused(env) |
+ |
+ fused_types = base_type.get_fused_types() |
+ if len(specific_types) > len(fused_types): |
+ return error(self.pos, "Too many types specified") |
+ elif len(specific_types) < len(fused_types): |
+ t = fused_types[len(specific_types)] |
+ return error(self.pos, "Not enough types specified to specialize " |
+ "the function, %s is still fused" % t) |
+ |
+ # See if our index types form valid specializations |
+ for pos, specific_type, fused_type in zip(positions, |
+ specific_types, |
+ fused_types): |
+ if not Utils.any([specific_type.same_as(t) |
+ for t in fused_type.types]): |
+ return error(pos, "Type not in fused type") |
+ |
+ if specific_type is None or specific_type.is_error: |
+ return |
+ |
+ fused_to_specific = dict(zip(fused_types, specific_types)) |
+ type = base_type.specialize(fused_to_specific) |
+ |
+ if type.is_fused: |
+ # Only partially specific, this is invalid |
+ error(self.pos, |
+ "Index operation makes function only partially specific") |
+ else: |
+ # Fully specific, find the signature with the specialized entry |
+ for signature in self.base.type.get_all_specialized_function_types(): |
+ if type.same_as(signature): |
+ self.type = signature |
+ |
+ if self.base.is_attribute: |
+ # Pretend to be a normal attribute, for cdef extension |
+ # methods |
+ self.entry = signature.entry |
+ self.is_attribute = True |
+ self.obj = self.base.obj |
+ |
+ self.type.entry.used = True |
+ self.base.type = signature |
+ self.base.entry = signature.entry |
+ |
+ break |
+ else: |
+ # This is a bug |
+ raise InternalError("Couldn't find the right signature") |
+ |
+ gil_message = "Indexing Python object" |
+ |
+ def nogil_check(self, env): |
+ if self.is_buffer_access or self.memslice_index or self.memslice_slice: |
+ if not self.memslice_slice and env.directives['boundscheck']: |
+ # error(self.pos, "Cannot check buffer index bounds without gil; " |
+ # "use boundscheck(False) directive") |
+ warning(self.pos, "Use boundscheck(False) for faster access", |
+ level=1) |
+ if self.type.is_pyobject: |
+ error(self.pos, "Cannot access buffer with object dtype without gil") |
+ return |
+ super(IndexNode, self).nogil_check(env) |
+ |
+ |
+ def check_const_addr(self): |
+ return self.base.check_const_addr() and self.index.check_const() |
+ |
+ def is_lvalue(self): |
+ # NOTE: references currently have both is_reference and is_ptr |
+ # set. Since pointers and references have different lvalue |
+ # rules, we must be careful to separate the two. |
+ if self.type.is_reference: |
+ if self.type.ref_base_type.is_array: |
+ # fixed-sized arrays aren't l-values |
+ return False |
+ elif self.type.is_ptr: |
+ # non-const pointers can always be reassigned |
+ return True |
+ elif self.type.is_array: |
+ # fixed-sized arrays aren't l-values |
+ return False |
+ # Just about everything else returned by the index operator |
+ # can be an lvalue. |
+ return True |
+ |
+ def calculate_result_code(self): |
+ if self.is_buffer_access: |
+ return "(*%s)" % self.buffer_ptr_code |
+ elif self.is_memslice_copy: |
+ return self.base.result() |
+ elif self.base.type in (list_type, tuple_type, bytearray_type): |
+ if self.base.type is list_type: |
+ index_code = "PyList_GET_ITEM(%s, %s)" |
+ elif self.base.type is tuple_type: |
+ index_code = "PyTuple_GET_ITEM(%s, %s)" |
+ elif self.base.type is bytearray_type: |
+ index_code = "((unsigned char)(PyByteArray_AS_STRING(%s)[%s]))" |
+ else: |
+ assert False, "unexpected base type in indexing: %s" % self.base.type |
+ elif self.base.type.is_cfunction: |
+ return "%s<%s>" % ( |
+ self.base.result(), |
+ ",".join([param.declaration_code("") for param in self.type_indices])) |
+ else: |
+ if (self.type.is_ptr or self.type.is_array) and self.type == self.base.type: |
+ error(self.pos, "Invalid use of pointer slice") |
+ return |
+ index_code = "(%s[%s])" |
+ return index_code % (self.base.result(), self.index.result()) |
+ |
+ def extra_index_params(self, code): |
+ if self.index.type.is_int: |
+ is_list = self.base.type is list_type |
+ wraparound = ( |
+ bool(code.globalstate.directives['wraparound']) and |
+ self.original_index_type.signed and |
+ not (isinstance(self.index.constant_result, (int, long)) |
+ and self.index.constant_result >= 0)) |
+ boundscheck = bool(code.globalstate.directives['boundscheck']) |
+ return ", %s, %d, %s, %d, %d, %d" % ( |
+ self.original_index_type.declaration_code(""), |
+ self.original_index_type.signed and 1 or 0, |
+ self.original_index_type.to_py_function, |
+ is_list, wraparound, boundscheck) |
+ else: |
+ return "" |
+ |
+ def generate_subexpr_evaluation_code(self, code): |
+ self.base.generate_evaluation_code(code) |
+ if self.type_indices is not None: |
+ pass |
+ elif self.indices is None: |
+ self.index.generate_evaluation_code(code) |
+ else: |
+ for i in self.indices: |
+ i.generate_evaluation_code(code) |
+ |
+ def generate_subexpr_disposal_code(self, code): |
+ self.base.generate_disposal_code(code) |
+ if self.type_indices is not None: |
+ pass |
+ elif self.indices is None: |
+ self.index.generate_disposal_code(code) |
+ else: |
+ for i in self.indices: |
+ i.generate_disposal_code(code) |
+ |
+ def free_subexpr_temps(self, code): |
+ self.base.free_temps(code) |
+ if self.indices is None: |
+ self.index.free_temps(code) |
+ else: |
+ for i in self.indices: |
+ i.free_temps(code) |
+ |
+ def generate_result_code(self, code): |
+ if self.is_buffer_access or self.memslice_index: |
+ buffer_entry, self.buffer_ptr_code = self.buffer_lookup_code(code) |
+ if self.type.is_pyobject: |
+ # is_temp is True, so must pull out value and incref it. |
+ # NOTE: object temporary results for nodes are declared |
+ # as PyObject *, so we need a cast |
+ code.putln("%s = (PyObject *) *%s;" % (self.temp_code, |
+ self.buffer_ptr_code)) |
+ code.putln("__Pyx_INCREF((PyObject*)%s);" % self.temp_code) |
+ |
+ elif self.memslice_slice: |
+ self.put_memoryviewslice_slice_code(code) |
+ |
+ elif self.is_temp: |
+ if self.type.is_pyobject: |
+ error_value = 'NULL' |
+ if self.index.type.is_int: |
+ if self.base.type is list_type: |
+ function = "__Pyx_GetItemInt_List" |
+ elif self.base.type is tuple_type: |
+ function = "__Pyx_GetItemInt_Tuple" |
+ else: |
+ function = "__Pyx_GetItemInt" |
+ code.globalstate.use_utility_code( |
+ TempitaUtilityCode.load_cached("GetItemInt", "ObjectHandling.c")) |
+ else: |
+ if self.base.type is dict_type: |
+ function = "__Pyx_PyDict_GetItem" |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("DictGetItem", "ObjectHandling.c")) |
+ else: |
+ function = "PyObject_GetItem" |
+ elif self.type.is_unicode_char and self.base.type is unicode_type: |
+ assert self.index.type.is_int |
+ function = "__Pyx_GetItemInt_Unicode" |
+ error_value = '(Py_UCS4)-1' |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("GetItemIntUnicode", "StringTools.c")) |
+ elif self.base.type is bytearray_type: |
+ assert self.index.type.is_int |
+ assert self.type.is_int |
+ function = "__Pyx_GetItemInt_ByteArray" |
+ error_value = '-1' |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("GetItemIntByteArray", "StringTools.c")) |
+ else: |
+ assert False, "unexpected type %s and base type %s for indexing" % ( |
+ self.type, self.base.type) |
+ |
+ if self.index.type.is_int: |
+ index_code = self.index.result() |
+ else: |
+ index_code = self.index.py_result() |
+ |
+ code.putln( |
+ "%s = %s(%s, %s%s); if (unlikely(%s == %s)) %s;" % ( |
+ self.result(), |
+ function, |
+ self.base.py_result(), |
+ index_code, |
+ self.extra_index_params(code), |
+ self.result(), |
+ error_value, |
+ code.error_goto(self.pos))) |
+ if self.type.is_pyobject: |
+ code.put_gotref(self.py_result()) |
+ |
+ def generate_setitem_code(self, value_code, code): |
+ if self.index.type.is_int: |
+ if self.base.type is bytearray_type: |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("SetItemIntByteArray", "StringTools.c")) |
+ function = "__Pyx_SetItemInt_ByteArray" |
+ else: |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("SetItemInt", "ObjectHandling.c")) |
+ function = "__Pyx_SetItemInt" |
+ index_code = self.index.result() |
+ else: |
+ index_code = self.index.py_result() |
+ if self.base.type is dict_type: |
+ function = "PyDict_SetItem" |
+ # It would seem that we could specialized lists/tuples, but that |
+ # shouldn't happen here. |
+ # Both PyList_SetItem() and PyTuple_SetItem() take a Py_ssize_t as |
+ # index instead of an object, and bad conversion here would give |
+ # the wrong exception. Also, tuples are supposed to be immutable, |
+ # and raise a TypeError when trying to set their entries |
+ # (PyTuple_SetItem() is for creating new tuples from scratch). |
+ else: |
+ function = "PyObject_SetItem" |
+ code.putln( |
+ "if (unlikely(%s(%s, %s, %s%s) < 0)) %s" % ( |
+ function, |
+ self.base.py_result(), |
+ index_code, |
+ value_code, |
+ self.extra_index_params(code), |
+ code.error_goto(self.pos))) |
+ |
+ def generate_buffer_setitem_code(self, rhs, code, op=""): |
+ # Used from generate_assignment_code and InPlaceAssignmentNode |
+ buffer_entry, ptrexpr = self.buffer_lookup_code(code) |
+ |
+ if self.buffer_type.dtype.is_pyobject: |
+ # Must manage refcounts. Decref what is already there |
+ # and incref what we put in. |
+ ptr = code.funcstate.allocate_temp(buffer_entry.buf_ptr_type, |
+ manage_ref=False) |
+ rhs_code = rhs.result() |
+ code.putln("%s = %s;" % (ptr, ptrexpr)) |
+ code.put_gotref("*%s" % ptr) |
+ code.putln("__Pyx_INCREF(%s); __Pyx_DECREF(*%s);" % ( |
+ rhs_code, ptr)) |
+ code.putln("*%s %s= %s;" % (ptr, op, rhs_code)) |
+ code.put_giveref("*%s" % ptr) |
+ code.funcstate.release_temp(ptr) |
+ else: |
+ # Simple case |
+ code.putln("*%s %s= %s;" % (ptrexpr, op, rhs.result())) |
+ |
+ def generate_assignment_code(self, rhs, code): |
+ generate_evaluation_code = (self.is_memslice_scalar_assignment or |
+ self.memslice_slice) |
+ if generate_evaluation_code: |
+ self.generate_evaluation_code(code) |
+ else: |
+ self.generate_subexpr_evaluation_code(code) |
+ |
+ if self.is_buffer_access or self.memslice_index: |
+ self.generate_buffer_setitem_code(rhs, code) |
+ elif self.is_memslice_scalar_assignment: |
+ self.generate_memoryviewslice_assign_scalar_code(rhs, code) |
+ elif self.memslice_slice or self.is_memslice_copy: |
+ self.generate_memoryviewslice_setslice_code(rhs, code) |
+ elif self.type.is_pyobject: |
+ self.generate_setitem_code(rhs.py_result(), code) |
+ elif self.base.type is bytearray_type: |
+ value_code = self._check_byte_value(code, rhs) |
+ self.generate_setitem_code(value_code, code) |
+ else: |
+ code.putln( |
+ "%s = %s;" % ( |
+ self.result(), rhs.result())) |
+ |
+ if generate_evaluation_code: |
+ self.generate_disposal_code(code) |
+ else: |
+ self.generate_subexpr_disposal_code(code) |
+ self.free_subexpr_temps(code) |
+ |
+ rhs.generate_disposal_code(code) |
+ rhs.free_temps(code) |
+ |
+ def _check_byte_value(self, code, rhs): |
+ # TODO: should we do this generally on downcasts, or just here? |
+ assert rhs.type.is_int, repr(rhs.type) |
+ value_code = rhs.result() |
+ if rhs.has_constant_result(): |
+ if 0 <= rhs.constant_result < 256: |
+ return value_code |
+ needs_cast = True # make at least the C compiler happy |
+ warning(rhs.pos, |
+ "value outside of range(0, 256)" |
+ " when assigning to byte: %s" % rhs.constant_result, |
+ level=1) |
+ else: |
+ needs_cast = rhs.type != PyrexTypes.c_uchar_type |
+ |
+ if not self.nogil: |
+ conditions = [] |
+ if rhs.is_literal or rhs.type.signed: |
+ conditions.append('%s < 0' % value_code) |
+ if (rhs.is_literal or not |
+ (rhs.is_temp and rhs.type in ( |
+ PyrexTypes.c_uchar_type, PyrexTypes.c_char_type, |
+ PyrexTypes.c_schar_type))): |
+ conditions.append('%s > 255' % value_code) |
+ if conditions: |
+ code.putln("if (unlikely(%s)) {" % ' || '.join(conditions)) |
+ code.putln( |
+ 'PyErr_SetString(PyExc_ValueError,' |
+ ' "byte must be in range(0, 256)"); %s' % |
+ code.error_goto(self.pos)) |
+ code.putln("}") |
+ |
+ if needs_cast: |
+ value_code = '((unsigned char)%s)' % value_code |
+ return value_code |
+ |
+ def generate_deletion_code(self, code, ignore_nonexisting=False): |
+ self.generate_subexpr_evaluation_code(code) |
+ #if self.type.is_pyobject: |
+ if self.index.type.is_int: |
+ function = "__Pyx_DelItemInt" |
+ index_code = self.index.result() |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("DelItemInt", "ObjectHandling.c")) |
+ else: |
+ index_code = self.index.py_result() |
+ if self.base.type is dict_type: |
+ function = "PyDict_DelItem" |
+ else: |
+ function = "PyObject_DelItem" |
+ code.putln( |
+ "if (%s(%s, %s%s) < 0) %s" % ( |
+ function, |
+ self.base.py_result(), |
+ index_code, |
+ self.extra_index_params(code), |
+ code.error_goto(self.pos))) |
+ self.generate_subexpr_disposal_code(code) |
+ self.free_subexpr_temps(code) |
+ |
+ def buffer_entry(self): |
+ import Buffer, MemoryView |
+ |
+ base = self.base |
+ if self.base.is_nonecheck: |
+ base = base.arg |
+ |
+ if base.is_name: |
+ entry = base.entry |
+ else: |
+ # SimpleCallNode is_simple is not consistent with coerce_to_simple |
+ assert base.is_simple() or base.is_temp |
+ cname = base.result() |
+ entry = Symtab.Entry(cname, cname, self.base.type, self.base.pos) |
+ |
+ if entry.type.is_buffer: |
+ buffer_entry = Buffer.BufferEntry(entry) |
+ else: |
+ buffer_entry = MemoryView.MemoryViewSliceBufferEntry(entry) |
+ |
+ return buffer_entry |
+ |
+ def buffer_lookup_code(self, code): |
+ "ndarray[1, 2, 3] and memslice[1, 2, 3]" |
+ # Assign indices to temps |
+ index_temps = [code.funcstate.allocate_temp(i.type, manage_ref=False) |
+ for i in self.indices] |
+ |
+ for temp, index in zip(index_temps, self.indices): |
+ code.putln("%s = %s;" % (temp, index.result())) |
+ |
+ # Generate buffer access code using these temps |
+ import Buffer |
+ buffer_entry = self.buffer_entry() |
+ if buffer_entry.type.is_buffer: |
+ negative_indices = buffer_entry.type.negative_indices |
+ else: |
+ negative_indices = Buffer.buffer_defaults['negative_indices'] |
+ |
+ return buffer_entry, Buffer.put_buffer_lookup_code( |
+ entry=buffer_entry, |
+ index_signeds=[i.type.signed for i in self.indices], |
+ index_cnames=index_temps, |
+ directives=code.globalstate.directives, |
+ pos=self.pos, code=code, |
+ negative_indices=negative_indices, |
+ in_nogil_context=self.in_nogil_context) |
+ |
+ def put_memoryviewslice_slice_code(self, code): |
+ "memslice[:]" |
+ buffer_entry = self.buffer_entry() |
+ have_gil = not self.in_nogil_context |
+ |
+ if sys.version_info < (3,): |
+ def next_(it): |
+ return it.next() |
+ else: |
+ next_ = next |
+ |
+ have_slices = False |
+ it = iter(self.indices) |
+ for index in self.original_indices: |
+ is_slice = isinstance(index, SliceNode) |
+ have_slices = have_slices or is_slice |
+ if is_slice: |
+ if not index.start.is_none: |
+ index.start = next_(it) |
+ if not index.stop.is_none: |
+ index.stop = next_(it) |
+ if not index.step.is_none: |
+ index.step = next_(it) |
+ else: |
+ next_(it) |
+ |
+ assert not list(it) |
+ |
+ buffer_entry.generate_buffer_slice_code(code, self.original_indices, |
+ self.result(), |
+ have_gil=have_gil, |
+ have_slices=have_slices, |
+ directives=code.globalstate.directives) |
+ |
+ def generate_memoryviewslice_setslice_code(self, rhs, code): |
+ "memslice1[...] = memslice2 or memslice1[:] = memslice2" |
+ import MemoryView |
+ MemoryView.copy_broadcast_memview_src_to_dst(rhs, self, code) |
+ |
+ def generate_memoryviewslice_assign_scalar_code(self, rhs, code): |
+ "memslice1[...] = 0.0 or memslice1[:] = 0.0" |
+ import MemoryView |
+ MemoryView.assign_scalar(self, rhs, code) |
+ |
+ |
+class SliceIndexNode(ExprNode): |
+ # 2-element slice indexing |
+ # |
+ # base ExprNode |
+ # start ExprNode or None |
+ # stop ExprNode or None |
+ # slice ExprNode or None constant slice object |
+ |
+ subexprs = ['base', 'start', 'stop', 'slice'] |
+ |
+ slice = None |
+ |
+ def infer_type(self, env): |
+ base_type = self.base.infer_type(env) |
+ if base_type.is_string or base_type.is_cpp_class: |
+ return bytes_type |
+ elif base_type.is_pyunicode_ptr: |
+ return unicode_type |
+ elif base_type in (bytes_type, str_type, unicode_type, |
+ basestring_type, list_type, tuple_type): |
+ return base_type |
+ elif base_type.is_ptr or base_type.is_array: |
+ return PyrexTypes.c_array_type(base_type.base_type, None) |
+ return py_object_type |
+ |
+ def may_be_none(self): |
+ base_type = self.base.type |
+ if base_type: |
+ if base_type.is_string: |
+ return False |
+ if base_type in (bytes_type, str_type, unicode_type, |
+ basestring_type, list_type, tuple_type): |
+ return False |
+ return ExprNode.may_be_none(self) |
+ |
+ def calculate_constant_result(self): |
+ if self.start is None: |
+ start = None |
+ else: |
+ start = self.start.constant_result |
+ if self.stop is None: |
+ stop = None |
+ else: |
+ stop = self.stop.constant_result |
+ self.constant_result = self.base.constant_result[start:stop] |
+ |
+ def compile_time_value(self, denv): |
+ base = self.base.compile_time_value(denv) |
+ if self.start is None: |
+ start = 0 |
+ else: |
+ start = self.start.compile_time_value(denv) |
+ if self.stop is None: |
+ stop = None |
+ else: |
+ stop = self.stop.compile_time_value(denv) |
+ try: |
+ return base[start:stop] |
+ except Exception, e: |
+ self.compile_time_value_error(e) |
+ |
+ def analyse_target_declaration(self, env): |
+ pass |
+ |
+ def analyse_target_types(self, env): |
+ node = self.analyse_types(env, getting=False) |
+ # when assigning, we must accept any Python type |
+ if node.type.is_pyobject: |
+ node.type = py_object_type |
+ return node |
+ |
+ def analyse_types(self, env, getting=True): |
+ self.base = self.base.analyse_types(env) |
+ |
+ if self.base.type.is_memoryviewslice: |
+ none_node = NoneNode(self.pos) |
+ index = SliceNode(self.pos, |
+ start=self.start or none_node, |
+ stop=self.stop or none_node, |
+ step=none_node) |
+ index_node = IndexNode(self.pos, index, base=self.base) |
+ return index_node.analyse_base_and_index_types( |
+ env, getting=getting, setting=not getting, |
+ analyse_base=False) |
+ |
+ if self.start: |
+ self.start = self.start.analyse_types(env) |
+ if self.stop: |
+ self.stop = self.stop.analyse_types(env) |
+ |
+ if not env.directives['wraparound']: |
+ check_negative_indices(self.start, self.stop) |
+ |
+ base_type = self.base.type |
+ if base_type.is_string or base_type.is_cpp_string: |
+ self.type = default_str_type(env) |
+ elif base_type.is_pyunicode_ptr: |
+ self.type = unicode_type |
+ elif base_type.is_ptr: |
+ self.type = base_type |
+ elif base_type.is_array: |
+ # we need a ptr type here instead of an array type, as |
+ # array types can result in invalid type casts in the C |
+ # code |
+ self.type = PyrexTypes.CPtrType(base_type.base_type) |
+ else: |
+ self.base = self.base.coerce_to_pyobject(env) |
+ self.type = py_object_type |
+ if base_type.is_builtin_type: |
+ # slicing builtin types returns something of the same type |
+ self.type = base_type |
+ self.base = self.base.as_none_safe_node("'NoneType' object is not subscriptable") |
+ |
+ if self.type is py_object_type: |
+ if (not self.start or self.start.is_literal) and \ |
+ (not self.stop or self.stop.is_literal): |
+ # cache the constant slice object, in case we need it |
+ none_node = NoneNode(self.pos) |
+ self.slice = SliceNode( |
+ self.pos, |
+ start=copy.deepcopy(self.start or none_node), |
+ stop=copy.deepcopy(self.stop or none_node), |
+ step=none_node |
+ ).analyse_types(env) |
+ else: |
+ c_int = PyrexTypes.c_py_ssize_t_type |
+ if self.start: |
+ self.start = self.start.coerce_to(c_int, env) |
+ if self.stop: |
+ self.stop = self.stop.coerce_to(c_int, env) |
+ self.is_temp = 1 |
+ return self |
+ |
+ nogil_check = Node.gil_error |
+ gil_message = "Slicing Python object" |
+ |
+ get_slice_utility_code = TempitaUtilityCode.load( |
+ "SliceObject", "ObjectHandling.c", context={'access': 'Get'}) |
+ |
+ set_slice_utility_code = TempitaUtilityCode.load( |
+ "SliceObject", "ObjectHandling.c", context={'access': 'Set'}) |
+ |
+ def coerce_to(self, dst_type, env): |
+ if ((self.base.type.is_string or self.base.type.is_cpp_string) |
+ and dst_type in (bytes_type, bytearray_type, str_type, unicode_type)): |
+ if (dst_type not in (bytes_type, bytearray_type) |
+ and not env.directives['c_string_encoding']): |
+ error(self.pos, |
+ "default encoding required for conversion from '%s' to '%s'" % |
+ (self.base.type, dst_type)) |
+ self.type = dst_type |
+ return super(SliceIndexNode, self).coerce_to(dst_type, env) |
+ |
+ def generate_result_code(self, code): |
+ if not self.type.is_pyobject: |
+ error(self.pos, |
+ "Slicing is not currently supported for '%s'." % self.type) |
+ return |
+ |
+ base_result = self.base.result() |
+ result = self.result() |
+ start_code = self.start_code() |
+ stop_code = self.stop_code() |
+ if self.base.type.is_string: |
+ base_result = self.base.result() |
+ if self.base.type != PyrexTypes.c_char_ptr_type: |
+ base_result = '((const char*)%s)' % base_result |
+ if self.type is bytearray_type: |
+ type_name = 'ByteArray' |
+ else: |
+ type_name = self.type.name.title() |
+ if self.stop is None: |
+ code.putln( |
+ "%s = __Pyx_Py%s_FromString(%s + %s); %s" % ( |
+ result, |
+ type_name, |
+ base_result, |
+ start_code, |
+ code.error_goto_if_null(result, self.pos))) |
+ else: |
+ code.putln( |
+ "%s = __Pyx_Py%s_FromStringAndSize(%s + %s, %s - %s); %s" % ( |
+ result, |
+ type_name, |
+ base_result, |
+ start_code, |
+ stop_code, |
+ start_code, |
+ code.error_goto_if_null(result, self.pos))) |
+ elif self.base.type.is_pyunicode_ptr: |
+ base_result = self.base.result() |
+ if self.base.type != PyrexTypes.c_py_unicode_ptr_type: |
+ base_result = '((const Py_UNICODE*)%s)' % base_result |
+ if self.stop is None: |
+ code.putln( |
+ "%s = __Pyx_PyUnicode_FromUnicode(%s + %s); %s" % ( |
+ result, |
+ base_result, |
+ start_code, |
+ code.error_goto_if_null(result, self.pos))) |
+ else: |
+ code.putln( |
+ "%s = __Pyx_PyUnicode_FromUnicodeAndLength(%s + %s, %s - %s); %s" % ( |
+ result, |
+ base_result, |
+ start_code, |
+ stop_code, |
+ start_code, |
+ code.error_goto_if_null(result, self.pos))) |
+ |
+ elif self.base.type is unicode_type: |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("PyUnicode_Substring", "StringTools.c")) |
+ code.putln( |
+ "%s = __Pyx_PyUnicode_Substring(%s, %s, %s); %s" % ( |
+ result, |
+ base_result, |
+ start_code, |
+ stop_code, |
+ code.error_goto_if_null(result, self.pos))) |
+ elif self.type is py_object_type: |
+ code.globalstate.use_utility_code(self.get_slice_utility_code) |
+ (has_c_start, has_c_stop, c_start, c_stop, |
+ py_start, py_stop, py_slice) = self.get_slice_config() |
+ code.putln( |
+ "%s = __Pyx_PyObject_GetSlice(%s, %s, %s, %s, %s, %s, %d, %d, %d); %s" % ( |
+ result, |
+ self.base.py_result(), |
+ c_start, c_stop, |
+ py_start, py_stop, py_slice, |
+ has_c_start, has_c_stop, |
+ bool(code.globalstate.directives['wraparound']), |
+ code.error_goto_if_null(result, self.pos))) |
+ else: |
+ if self.base.type is list_type: |
+ code.globalstate.use_utility_code( |
+ TempitaUtilityCode.load_cached("SliceTupleAndList", "ObjectHandling.c")) |
+ cfunc = '__Pyx_PyList_GetSlice' |
+ elif self.base.type is tuple_type: |
+ code.globalstate.use_utility_code( |
+ TempitaUtilityCode.load_cached("SliceTupleAndList", "ObjectHandling.c")) |
+ cfunc = '__Pyx_PyTuple_GetSlice' |
+ else: |
+ cfunc = '__Pyx_PySequence_GetSlice' |
+ code.putln( |
+ "%s = %s(%s, %s, %s); %s" % ( |
+ result, |
+ cfunc, |
+ self.base.py_result(), |
+ start_code, |
+ stop_code, |
+ code.error_goto_if_null(result, self.pos))) |
+ code.put_gotref(self.py_result()) |
+ |
+ def generate_assignment_code(self, rhs, code): |
+ self.generate_subexpr_evaluation_code(code) |
+ if self.type.is_pyobject: |
+ code.globalstate.use_utility_code(self.set_slice_utility_code) |
+ (has_c_start, has_c_stop, c_start, c_stop, |
+ py_start, py_stop, py_slice) = self.get_slice_config() |
+ code.put_error_if_neg(self.pos, |
+ "__Pyx_PyObject_SetSlice(%s, %s, %s, %s, %s, %s, %s, %d, %d, %d)" % ( |
+ self.base.py_result(), |
+ rhs.py_result(), |
+ c_start, c_stop, |
+ py_start, py_stop, py_slice, |
+ has_c_start, has_c_stop, |
+ bool(code.globalstate.directives['wraparound']))) |
+ else: |
+ start_offset = '' |
+ if self.start: |
+ start_offset = self.start_code() |
+ if start_offset == '0': |
+ start_offset = '' |
+ else: |
+ start_offset += '+' |
+ if rhs.type.is_array: |
+ array_length = rhs.type.size |
+ self.generate_slice_guard_code(code, array_length) |
+ else: |
+ error(self.pos, |
+ "Slice assignments from pointers are not yet supported.") |
+ # FIXME: fix the array size according to start/stop |
+ array_length = self.base.type.size |
+ for i in range(array_length): |
+ code.putln("%s[%s%s] = %s[%d];" % ( |
+ self.base.result(), start_offset, i, |
+ rhs.result(), i)) |
+ self.generate_subexpr_disposal_code(code) |
+ self.free_subexpr_temps(code) |
+ rhs.generate_disposal_code(code) |
+ rhs.free_temps(code) |
+ |
+ def generate_deletion_code(self, code, ignore_nonexisting=False): |
+ if not self.base.type.is_pyobject: |
+ error(self.pos, |
+ "Deleting slices is only supported for Python types, not '%s'." % self.type) |
+ return |
+ self.generate_subexpr_evaluation_code(code) |
+ code.globalstate.use_utility_code(self.set_slice_utility_code) |
+ (has_c_start, has_c_stop, c_start, c_stop, |
+ py_start, py_stop, py_slice) = self.get_slice_config() |
+ code.put_error_if_neg(self.pos, |
+ "__Pyx_PyObject_DelSlice(%s, %s, %s, %s, %s, %s, %d, %d, %d)" % ( |
+ self.base.py_result(), |
+ c_start, c_stop, |
+ py_start, py_stop, py_slice, |
+ has_c_start, has_c_stop, |
+ bool(code.globalstate.directives['wraparound']))) |
+ self.generate_subexpr_disposal_code(code) |
+ self.free_subexpr_temps(code) |
+ |
+ def get_slice_config(self): |
+ has_c_start, c_start, py_start = False, '0', 'NULL' |
+ if self.start: |
+ has_c_start = not self.start.type.is_pyobject |
+ if has_c_start: |
+ c_start = self.start.result() |
+ else: |
+ py_start = '&%s' % self.start.py_result() |
+ has_c_stop, c_stop, py_stop = False, '0', 'NULL' |
+ if self.stop: |
+ has_c_stop = not self.stop.type.is_pyobject |
+ if has_c_stop: |
+ c_stop = self.stop.result() |
+ else: |
+ py_stop = '&%s' % self.stop.py_result() |
+ py_slice = self.slice and '&%s' % self.slice.py_result() or 'NULL' |
+ return (has_c_start, has_c_stop, c_start, c_stop, |
+ py_start, py_stop, py_slice) |
+ |
+ def generate_slice_guard_code(self, code, target_size): |
+ if not self.base.type.is_array: |
+ return |
+ slice_size = self.base.type.size |
+ start = stop = None |
+ if self.stop: |
+ stop = self.stop.result() |
+ try: |
+ stop = int(stop) |
+ if stop < 0: |
+ slice_size = self.base.type.size + stop |
+ else: |
+ slice_size = stop |
+ stop = None |
+ except ValueError: |
+ pass |
+ if self.start: |
+ start = self.start.result() |
+ try: |
+ start = int(start) |
+ if start < 0: |
+ start = self.base.type.size + start |
+ slice_size -= start |
+ start = None |
+ except ValueError: |
+ pass |
+ check = None |
+ if slice_size < 0: |
+ if target_size > 0: |
+ error(self.pos, "Assignment to empty slice.") |
+ elif start is None and stop is None: |
+ # we know the exact slice length |
+ if target_size != slice_size: |
+ error(self.pos, "Assignment to slice of wrong length, expected %d, got %d" % ( |
+ slice_size, target_size)) |
+ elif start is not None: |
+ if stop is None: |
+ stop = slice_size |
+ check = "(%s)-(%s)" % (stop, start) |
+ else: # stop is not None: |
+ check = stop |
+ if check: |
+ code.putln("if (unlikely((%s) != %d)) {" % (check, target_size)) |
+ code.putln('PyErr_Format(PyExc_ValueError, "Assignment to slice of wrong length, expected %%" CYTHON_FORMAT_SSIZE_T "d, got %%" CYTHON_FORMAT_SSIZE_T "d", (Py_ssize_t)%d, (Py_ssize_t)(%s));' % ( |
+ target_size, check)) |
+ code.putln(code.error_goto(self.pos)) |
+ code.putln("}") |
+ |
+ def start_code(self): |
+ if self.start: |
+ return self.start.result() |
+ else: |
+ return "0" |
+ |
+ def stop_code(self): |
+ if self.stop: |
+ return self.stop.result() |
+ elif self.base.type.is_array: |
+ return self.base.type.size |
+ else: |
+ return "PY_SSIZE_T_MAX" |
+ |
+ def calculate_result_code(self): |
+ # self.result() is not used, but this method must exist |
+ return "<unused>" |
+ |
+ |
+class SliceNode(ExprNode): |
+ # start:stop:step in subscript list |
+ # |
+ # start ExprNode |
+ # stop ExprNode |
+ # step ExprNode |
+ |
+ subexprs = ['start', 'stop', 'step'] |
+ |
+ type = slice_type |
+ is_temp = 1 |
+ |
+ def calculate_constant_result(self): |
+ self.constant_result = slice( |
+ self.start.constant_result, |
+ self.stop.constant_result, |
+ self.step.constant_result) |
+ |
+ def compile_time_value(self, denv): |
+ start = self.start.compile_time_value(denv) |
+ stop = self.stop.compile_time_value(denv) |
+ step = self.step.compile_time_value(denv) |
+ try: |
+ return slice(start, stop, step) |
+ except Exception, e: |
+ self.compile_time_value_error(e) |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ def analyse_types(self, env): |
+ start = self.start.analyse_types(env) |
+ stop = self.stop.analyse_types(env) |
+ step = self.step.analyse_types(env) |
+ self.start = start.coerce_to_pyobject(env) |
+ self.stop = stop.coerce_to_pyobject(env) |
+ self.step = step.coerce_to_pyobject(env) |
+ if self.start.is_literal and self.stop.is_literal and self.step.is_literal: |
+ self.is_literal = True |
+ self.is_temp = False |
+ return self |
+ |
+ gil_message = "Constructing Python slice object" |
+ |
+ def calculate_result_code(self): |
+ return self.result_code |
+ |
+ def generate_result_code(self, code): |
+ if self.is_literal: |
+ self.result_code = code.get_py_const(py_object_type, 'slice', cleanup_level=2) |
+ code = code.get_cached_constants_writer() |
+ code.mark_pos(self.pos) |
+ |
+ code.putln( |
+ "%s = PySlice_New(%s, %s, %s); %s" % ( |
+ self.result(), |
+ self.start.py_result(), |
+ self.stop.py_result(), |
+ self.step.py_result(), |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ if self.is_literal: |
+ code.put_giveref(self.py_result()) |
+ |
+ def __deepcopy__(self, memo): |
+ """ |
+ There is a copy bug in python 2.4 for slice objects. |
+ """ |
+ return SliceNode( |
+ self.pos, |
+ start=copy.deepcopy(self.start, memo), |
+ stop=copy.deepcopy(self.stop, memo), |
+ step=copy.deepcopy(self.step, memo), |
+ is_temp=self.is_temp, |
+ is_literal=self.is_literal, |
+ constant_result=self.constant_result) |
+ |
+ |
+class CallNode(ExprNode): |
+ |
+ # allow overriding the default 'may_be_none' behaviour |
+ may_return_none = None |
+ |
+ def infer_type(self, env): |
+ function = self.function |
+ func_type = function.infer_type(env) |
+ if isinstance(function, NewExprNode): |
+ # note: needs call to infer_type() above |
+ return PyrexTypes.CPtrType(function.class_type) |
+ if func_type is py_object_type: |
+ # function might have lied for safety => try to find better type |
+ entry = getattr(function, 'entry', None) |
+ if entry is not None: |
+ func_type = entry.type or func_type |
+ if func_type.is_ptr: |
+ func_type = func_type.base_type |
+ if func_type.is_cfunction: |
+ return func_type.return_type |
+ elif func_type is type_type: |
+ if function.is_name and function.entry and function.entry.type: |
+ result_type = function.entry.type |
+ if result_type.is_extension_type: |
+ return result_type |
+ elif result_type.is_builtin_type: |
+ if function.entry.name == 'float': |
+ return PyrexTypes.c_double_type |
+ elif function.entry.name in Builtin.types_that_construct_their_instance: |
+ return result_type |
+ return py_object_type |
+ |
+ def type_dependencies(self, env): |
+ # TODO: Update when Danilo's C++ code merged in to handle the |
+ # the case of function overloading. |
+ return self.function.type_dependencies(env) |
+ |
+ def is_simple(self): |
+ # C function calls could be considered simple, but they may |
+ # have side-effects that may hit when multiple operations must |
+ # be effected in order, e.g. when constructing the argument |
+ # sequence for a function call or comparing values. |
+ return False |
+ |
+ def may_be_none(self): |
+ if self.may_return_none is not None: |
+ return self.may_return_none |
+ func_type = self.function.type |
+ if func_type is type_type and self.function.is_name: |
+ entry = self.function.entry |
+ if entry.type.is_extension_type: |
+ return False |
+ if (entry.type.is_builtin_type and |
+ entry.name in Builtin.types_that_construct_their_instance): |
+ return False |
+ return ExprNode.may_be_none(self) |
+ |
+ def analyse_as_type_constructor(self, env): |
+ type = self.function.analyse_as_type(env) |
+ if type and type.is_struct_or_union: |
+ args, kwds = self.explicit_args_kwds() |
+ items = [] |
+ for arg, member in zip(args, type.scope.var_entries): |
+ items.append(DictItemNode(pos=arg.pos, key=StringNode(pos=arg.pos, value=member.name), value=arg)) |
+ if kwds: |
+ items += kwds.key_value_pairs |
+ self.key_value_pairs = items |
+ self.__class__ = DictNode |
+ self.analyse_types(env) # FIXME |
+ self.coerce_to(type, env) |
+ return True |
+ elif type and type.is_cpp_class: |
+ self.args = [ arg.analyse_types(env) for arg in self.args ] |
+ constructor = type.scope.lookup("<init>") |
+ self.function = RawCNameExprNode(self.function.pos, constructor.type) |
+ self.function.entry = constructor |
+ self.function.set_cname(type.declaration_code("")) |
+ self.analyse_c_function_call(env) |
+ self.type = type |
+ return True |
+ |
+ def is_lvalue(self): |
+ return self.type.is_reference |
+ |
+ def nogil_check(self, env): |
+ func_type = self.function_type() |
+ if func_type.is_pyobject: |
+ self.gil_error() |
+ elif not getattr(func_type, 'nogil', False): |
+ self.gil_error() |
+ |
+ gil_message = "Calling gil-requiring function" |
+ |
+ |
+class SimpleCallNode(CallNode): |
+ # Function call without keyword, * or ** args. |
+ # |
+ # function ExprNode |
+ # args [ExprNode] |
+ # arg_tuple ExprNode or None used internally |
+ # self ExprNode or None used internally |
+ # coerced_self ExprNode or None used internally |
+ # wrapper_call bool used internally |
+ # has_optional_args bool used internally |
+ # nogil bool used internally |
+ |
+ subexprs = ['self', 'coerced_self', 'function', 'args', 'arg_tuple'] |
+ |
+ self = None |
+ coerced_self = None |
+ arg_tuple = None |
+ wrapper_call = False |
+ has_optional_args = False |
+ nogil = False |
+ analysed = False |
+ |
+ def compile_time_value(self, denv): |
+ function = self.function.compile_time_value(denv) |
+ args = [arg.compile_time_value(denv) for arg in self.args] |
+ try: |
+ return function(*args) |
+ except Exception, e: |
+ self.compile_time_value_error(e) |
+ |
+ def analyse_as_type(self, env): |
+ attr = self.function.as_cython_attribute() |
+ if attr == 'pointer': |
+ if len(self.args) != 1: |
+ error(self.args.pos, "only one type allowed.") |
+ else: |
+ type = self.args[0].analyse_as_type(env) |
+ if not type: |
+ error(self.args[0].pos, "Unknown type") |
+ else: |
+ return PyrexTypes.CPtrType(type) |
+ |
+ def explicit_args_kwds(self): |
+ return self.args, None |
+ |
+ def analyse_types(self, env): |
+ if self.analyse_as_type_constructor(env): |
+ return self |
+ if self.analysed: |
+ return self |
+ self.analysed = True |
+ self.function.is_called = 1 |
+ self.function = self.function.analyse_types(env) |
+ function = self.function |
+ |
+ if function.is_attribute and function.entry and function.entry.is_cmethod: |
+ # Take ownership of the object from which the attribute |
+ # was obtained, because we need to pass it as 'self'. |
+ self.self = function.obj |
+ function.obj = CloneNode(self.self) |
+ |
+ func_type = self.function_type() |
+ if func_type.is_pyobject: |
+ self.arg_tuple = TupleNode(self.pos, args = self.args) |
+ self.arg_tuple = self.arg_tuple.analyse_types(env) |
+ self.args = None |
+ if func_type is Builtin.type_type and function.is_name and \ |
+ function.entry and \ |
+ function.entry.is_builtin and \ |
+ function.entry.name in Builtin.types_that_construct_their_instance: |
+ # calling a builtin type that returns a specific object type |
+ if function.entry.name == 'float': |
+ # the following will come true later on in a transform |
+ self.type = PyrexTypes.c_double_type |
+ self.result_ctype = PyrexTypes.c_double_type |
+ else: |
+ self.type = Builtin.builtin_types[function.entry.name] |
+ self.result_ctype = py_object_type |
+ self.may_return_none = False |
+ elif function.is_name and function.type_entry: |
+ # We are calling an extension type constructor. As |
+ # long as we do not support __new__(), the result type |
+ # is clear |
+ self.type = function.type_entry.type |
+ self.result_ctype = py_object_type |
+ self.may_return_none = False |
+ else: |
+ self.type = py_object_type |
+ self.is_temp = 1 |
+ else: |
+ self.args = [ arg.analyse_types(env) for arg in self.args ] |
+ self.analyse_c_function_call(env) |
+ return self |
+ |
+ def function_type(self): |
+ # Return the type of the function being called, coercing a function |
+ # pointer to a function if necessary. If the function has fused |
+ # arguments, return the specific type. |
+ func_type = self.function.type |
+ |
+ if func_type.is_ptr: |
+ func_type = func_type.base_type |
+ |
+ return func_type |
+ |
+ def analyse_c_function_call(self, env): |
+ if self.function.type is error_type: |
+ self.type = error_type |
+ return |
+ |
+ if self.self: |
+ args = [self.self] + self.args |
+ else: |
+ args = self.args |
+ |
+ if self.function.type.is_cpp_class: |
+ overloaded_entry = self.function.type.scope.lookup("operator()") |
+ if overloaded_entry is None: |
+ self.type = PyrexTypes.error_type |
+ self.result_code = "<error>" |
+ return |
+ elif hasattr(self.function, 'entry'): |
+ overloaded_entry = self.function.entry |
+ elif (isinstance(self.function, IndexNode) and |
+ self.function.is_fused_index): |
+ overloaded_entry = self.function.type.entry |
+ else: |
+ overloaded_entry = None |
+ |
+ if overloaded_entry: |
+ if self.function.type.is_fused: |
+ functypes = self.function.type.get_all_specialized_function_types() |
+ alternatives = [f.entry for f in functypes] |
+ else: |
+ alternatives = overloaded_entry.all_alternatives() |
+ |
+ entry = PyrexTypes.best_match(args, alternatives, self.pos, env) |
+ |
+ if not entry: |
+ self.type = PyrexTypes.error_type |
+ self.result_code = "<error>" |
+ return |
+ |
+ entry.used = True |
+ self.function.entry = entry |
+ self.function.type = entry.type |
+ func_type = self.function_type() |
+ else: |
+ entry = None |
+ func_type = self.function_type() |
+ if not func_type.is_cfunction: |
+ error(self.pos, "Calling non-function type '%s'" % func_type) |
+ self.type = PyrexTypes.error_type |
+ self.result_code = "<error>" |
+ return |
+ |
+ # Check no. of args |
+ max_nargs = len(func_type.args) |
+ expected_nargs = max_nargs - func_type.optional_arg_count |
+ actual_nargs = len(args) |
+ if func_type.optional_arg_count and expected_nargs != actual_nargs: |
+ self.has_optional_args = 1 |
+ self.is_temp = 1 |
+ |
+ # check 'self' argument |
+ if entry and entry.is_cmethod and func_type.args: |
+ formal_arg = func_type.args[0] |
+ arg = args[0] |
+ if formal_arg.not_none: |
+ if self.self: |
+ self.self = self.self.as_none_safe_node( |
+ "'NoneType' object has no attribute '%s'", |
+ error='PyExc_AttributeError', |
+ format_args=[entry.name]) |
+ else: |
+ # unbound method |
+ arg = arg.as_none_safe_node( |
+ "descriptor '%s' requires a '%s' object but received a 'NoneType'", |
+ format_args=[entry.name, formal_arg.type.name]) |
+ if self.self: |
+ if formal_arg.accept_builtin_subtypes: |
+ arg = CMethodSelfCloneNode(self.self) |
+ else: |
+ arg = CloneNode(self.self) |
+ arg = self.coerced_self = arg.coerce_to(formal_arg.type, env) |
+ elif formal_arg.type.is_builtin_type: |
+ # special case: unbound methods of builtins accept subtypes |
+ arg = arg.coerce_to(formal_arg.type, env) |
+ if arg.type.is_builtin_type and isinstance(arg, PyTypeTestNode): |
+ arg.exact_builtin_type = False |
+ args[0] = arg |
+ |
+ # Coerce arguments |
+ some_args_in_temps = False |
+ for i in xrange(min(max_nargs, actual_nargs)): |
+ formal_arg = func_type.args[i] |
+ formal_type = formal_arg.type |
+ arg = args[i].coerce_to(formal_type, env) |
+ if formal_arg.not_none: |
+ # C methods must do the None checks at *call* time |
+ arg = arg.as_none_safe_node( |
+ "cannot pass None into a C function argument that is declared 'not None'") |
+ if arg.is_temp: |
+ if i > 0: |
+ # first argument in temp doesn't impact subsequent arguments |
+ some_args_in_temps = True |
+ elif arg.type.is_pyobject and not env.nogil: |
+ if i == 0 and self.self is not None: |
+ # a method's cloned "self" argument is ok |
+ pass |
+ elif arg.nonlocally_immutable(): |
+ # plain local variables are ok |
+ pass |
+ else: |
+ # we do not safely own the argument's reference, |
+ # but we must make sure it cannot be collected |
+ # before we return from the function, so we create |
+ # an owned temp reference to it |
+ if i > 0: # first argument doesn't matter |
+ some_args_in_temps = True |
+ arg = arg.coerce_to_temp(env) |
+ args[i] = arg |
+ |
+ # handle additional varargs parameters |
+ for i in xrange(max_nargs, actual_nargs): |
+ arg = args[i] |
+ if arg.type.is_pyobject: |
+ arg_ctype = arg.type.default_coerced_ctype() |
+ if arg_ctype is None: |
+ error(self.args[i].pos, |
+ "Python object cannot be passed as a varargs parameter") |
+ else: |
+ args[i] = arg = arg.coerce_to(arg_ctype, env) |
+ if arg.is_temp and i > 0: |
+ some_args_in_temps = True |
+ |
+ if some_args_in_temps: |
+ # if some args are temps and others are not, they may get |
+ # constructed in the wrong order (temps first) => make |
+ # sure they are either all temps or all not temps (except |
+ # for the last argument, which is evaluated last in any |
+ # case) |
+ for i in xrange(actual_nargs-1): |
+ if i == 0 and self.self is not None: |
+ continue # self is ok |
+ arg = args[i] |
+ if arg.nonlocally_immutable(): |
+ # locals, C functions, unassignable types are safe. |
+ pass |
+ elif arg.type.is_cpp_class: |
+ # Assignment has side effects, avoid. |
+ pass |
+ elif env.nogil and arg.type.is_pyobject: |
+ # can't copy a Python reference into a temp in nogil |
+ # env (this is safe: a construction would fail in |
+ # nogil anyway) |
+ pass |
+ else: |
+ #self.args[i] = arg.coerce_to_temp(env) |
+ # instead: issue a warning |
+ if i > 0 or i == 1 and self.self is not None: # skip first arg |
+ warning(arg.pos, "Argument evaluation order in C function call is undefined and may not be as expected", 0) |
+ break |
+ |
+ self.args[:] = args |
+ |
+ # Calc result type and code fragment |
+ if isinstance(self.function, NewExprNode): |
+ self.type = PyrexTypes.CPtrType(self.function.class_type) |
+ else: |
+ self.type = func_type.return_type |
+ |
+ if self.function.is_name or self.function.is_attribute: |
+ if self.function.entry and self.function.entry.utility_code: |
+ self.is_temp = 1 # currently doesn't work for self.calculate_result_code() |
+ |
+ if self.type.is_pyobject: |
+ self.result_ctype = py_object_type |
+ self.is_temp = 1 |
+ elif func_type.exception_value is not None \ |
+ or func_type.exception_check: |
+ self.is_temp = 1 |
+ elif self.type.is_memoryviewslice: |
+ self.is_temp = 1 |
+ # func_type.exception_check = True |
+ |
+ # Called in 'nogil' context? |
+ self.nogil = env.nogil |
+ if (self.nogil and |
+ func_type.exception_check and |
+ func_type.exception_check != '+'): |
+ env.use_utility_code(pyerr_occurred_withgil_utility_code) |
+ # C++ exception handler |
+ if func_type.exception_check == '+': |
+ if func_type.exception_value is None: |
+ env.use_utility_code(UtilityCode.load_cached("CppExceptionConversion", "CppSupport.cpp")) |
+ |
+ def calculate_result_code(self): |
+ return self.c_call_code() |
+ |
+ def c_call_code(self): |
+ func_type = self.function_type() |
+ if self.type is PyrexTypes.error_type or not func_type.is_cfunction: |
+ return "<error>" |
+ formal_args = func_type.args |
+ arg_list_code = [] |
+ args = list(zip(formal_args, self.args)) |
+ max_nargs = len(func_type.args) |
+ expected_nargs = max_nargs - func_type.optional_arg_count |
+ actual_nargs = len(self.args) |
+ for formal_arg, actual_arg in args[:expected_nargs]: |
+ arg_code = actual_arg.result_as(formal_arg.type) |
+ arg_list_code.append(arg_code) |
+ |
+ if func_type.is_overridable: |
+ arg_list_code.append(str(int(self.wrapper_call or self.function.entry.is_unbound_cmethod))) |
+ |
+ if func_type.optional_arg_count: |
+ if expected_nargs == actual_nargs: |
+ optional_args = 'NULL' |
+ else: |
+ optional_args = "&%s" % self.opt_arg_struct |
+ arg_list_code.append(optional_args) |
+ |
+ for actual_arg in self.args[len(formal_args):]: |
+ arg_list_code.append(actual_arg.result()) |
+ |
+ result = "%s(%s)" % (self.function.result(), ', '.join(arg_list_code)) |
+ return result |
+ |
+ def generate_result_code(self, code): |
+ func_type = self.function_type() |
+ if self.function.is_name or self.function.is_attribute: |
+ if self.function.entry and self.function.entry.utility_code: |
+ code.globalstate.use_utility_code(self.function.entry.utility_code) |
+ if func_type.is_pyobject: |
+ arg_code = self.arg_tuple.py_result() |
+ code.globalstate.use_utility_code(UtilityCode.load_cached( |
+ "PyObjectCall", "ObjectHandling.c")) |
+ code.putln( |
+ "%s = __Pyx_PyObject_Call(%s, %s, NULL); %s" % ( |
+ self.result(), |
+ self.function.py_result(), |
+ arg_code, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ elif func_type.is_cfunction: |
+ if self.has_optional_args: |
+ actual_nargs = len(self.args) |
+ expected_nargs = len(func_type.args) - func_type.optional_arg_count |
+ self.opt_arg_struct = code.funcstate.allocate_temp( |
+ func_type.op_arg_struct.base_type, manage_ref=True) |
+ code.putln("%s.%s = %s;" % ( |
+ self.opt_arg_struct, |
+ Naming.pyrex_prefix + "n", |
+ len(self.args) - expected_nargs)) |
+ args = list(zip(func_type.args, self.args)) |
+ for formal_arg, actual_arg in args[expected_nargs:actual_nargs]: |
+ code.putln("%s.%s = %s;" % ( |
+ self.opt_arg_struct, |
+ func_type.opt_arg_cname(formal_arg.name), |
+ actual_arg.result_as(formal_arg.type))) |
+ exc_checks = [] |
+ if self.type.is_pyobject and self.is_temp: |
+ exc_checks.append("!%s" % self.result()) |
+ elif self.type.is_memoryviewslice: |
+ assert self.is_temp |
+ exc_checks.append(self.type.error_condition(self.result())) |
+ else: |
+ exc_val = func_type.exception_value |
+ exc_check = func_type.exception_check |
+ if exc_val is not None: |
+ exc_checks.append("%s == %s" % (self.result(), exc_val)) |
+ if exc_check: |
+ if self.nogil: |
+ exc_checks.append("__Pyx_ErrOccurredWithGIL()") |
+ else: |
+ exc_checks.append("PyErr_Occurred()") |
+ if self.is_temp or exc_checks: |
+ rhs = self.c_call_code() |
+ if self.result(): |
+ lhs = "%s = " % self.result() |
+ if self.is_temp and self.type.is_pyobject: |
+ #return_type = self.type # func_type.return_type |
+ #print "SimpleCallNode.generate_result_code: casting", rhs, \ |
+ # "from", return_type, "to pyobject" ### |
+ rhs = typecast(py_object_type, self.type, rhs) |
+ else: |
+ lhs = "" |
+ if func_type.exception_check == '+': |
+ if func_type.exception_value is None: |
+ raise_py_exception = "__Pyx_CppExn2PyErr();" |
+ elif func_type.exception_value.type.is_pyobject: |
+ raise_py_exception = 'try { throw; } catch(const std::exception& exn) { PyErr_SetString(%s, exn.what()); } catch(...) { PyErr_SetNone(%s); }' % ( |
+ func_type.exception_value.entry.cname, |
+ func_type.exception_value.entry.cname) |
+ else: |
+ raise_py_exception = '%s(); if (!PyErr_Occurred()) PyErr_SetString(PyExc_RuntimeError , "Error converting c++ exception.");' % func_type.exception_value.entry.cname |
+ code.putln("try {") |
+ code.putln("%s%s;" % (lhs, rhs)) |
+ code.putln("} catch(...) {") |
+ if self.nogil: |
+ code.put_ensure_gil(declare_gilstate=True) |
+ code.putln(raise_py_exception) |
+ if self.nogil: |
+ code.put_release_ensured_gil() |
+ code.putln(code.error_goto(self.pos)) |
+ code.putln("}") |
+ else: |
+ if exc_checks: |
+ goto_error = code.error_goto_if(" && ".join(exc_checks), self.pos) |
+ else: |
+ goto_error = "" |
+ code.putln("%s%s; %s" % (lhs, rhs, goto_error)) |
+ if self.type.is_pyobject and self.result(): |
+ code.put_gotref(self.py_result()) |
+ if self.has_optional_args: |
+ code.funcstate.release_temp(self.opt_arg_struct) |
+ |
+ |
+class InlinedDefNodeCallNode(CallNode): |
+ # Inline call to defnode |
+ # |
+ # function PyCFunctionNode |
+ # function_name NameNode |
+ # args [ExprNode] |
+ |
+ subexprs = ['args', 'function_name'] |
+ is_temp = 1 |
+ type = py_object_type |
+ function = None |
+ function_name = None |
+ |
+ def can_be_inlined(self): |
+ func_type= self.function.def_node |
+ if func_type.star_arg or func_type.starstar_arg: |
+ return False |
+ if len(func_type.args) != len(self.args): |
+ return False |
+ return True |
+ |
+ def analyse_types(self, env): |
+ self.function_name = self.function_name.analyse_types(env) |
+ |
+ self.args = [ arg.analyse_types(env) for arg in self.args ] |
+ func_type = self.function.def_node |
+ actual_nargs = len(self.args) |
+ |
+ # Coerce arguments |
+ some_args_in_temps = False |
+ for i in xrange(actual_nargs): |
+ formal_type = func_type.args[i].type |
+ arg = self.args[i].coerce_to(formal_type, env) |
+ if arg.is_temp: |
+ if i > 0: |
+ # first argument in temp doesn't impact subsequent arguments |
+ some_args_in_temps = True |
+ elif arg.type.is_pyobject and not env.nogil: |
+ if arg.nonlocally_immutable(): |
+ # plain local variables are ok |
+ pass |
+ else: |
+ # we do not safely own the argument's reference, |
+ # but we must make sure it cannot be collected |
+ # before we return from the function, so we create |
+ # an owned temp reference to it |
+ if i > 0: # first argument doesn't matter |
+ some_args_in_temps = True |
+ arg = arg.coerce_to_temp(env) |
+ self.args[i] = arg |
+ |
+ if some_args_in_temps: |
+ # if some args are temps and others are not, they may get |
+ # constructed in the wrong order (temps first) => make |
+ # sure they are either all temps or all not temps (except |
+ # for the last argument, which is evaluated last in any |
+ # case) |
+ for i in xrange(actual_nargs-1): |
+ arg = self.args[i] |
+ if arg.nonlocally_immutable(): |
+ # locals, C functions, unassignable types are safe. |
+ pass |
+ elif arg.type.is_cpp_class: |
+ # Assignment has side effects, avoid. |
+ pass |
+ elif env.nogil and arg.type.is_pyobject: |
+ # can't copy a Python reference into a temp in nogil |
+ # env (this is safe: a construction would fail in |
+ # nogil anyway) |
+ pass |
+ else: |
+ #self.args[i] = arg.coerce_to_temp(env) |
+ # instead: issue a warning |
+ if i > 0: |
+ warning(arg.pos, "Argument evaluation order in C function call is undefined and may not be as expected", 0) |
+ break |
+ return self |
+ |
+ def generate_result_code(self, code): |
+ arg_code = [self.function_name.py_result()] |
+ func_type = self.function.def_node |
+ for arg, proto_arg in zip(self.args, func_type.args): |
+ if arg.type.is_pyobject: |
+ arg_code.append(arg.result_as(proto_arg.type)) |
+ else: |
+ arg_code.append(arg.result()) |
+ arg_code = ', '.join(arg_code) |
+ code.putln( |
+ "%s = %s(%s); %s" % ( |
+ self.result(), |
+ self.function.def_node.entry.pyfunc_cname, |
+ arg_code, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ |
+ |
+class PythonCapiFunctionNode(ExprNode): |
+ subexprs = [] |
+ |
+ def __init__(self, pos, py_name, cname, func_type, utility_code = None): |
+ ExprNode.__init__(self, pos, name=py_name, cname=cname, |
+ type=func_type, utility_code=utility_code) |
+ |
+ def analyse_types(self, env): |
+ return self |
+ |
+ def generate_result_code(self, code): |
+ if self.utility_code: |
+ code.globalstate.use_utility_code(self.utility_code) |
+ |
+ def calculate_result_code(self): |
+ return self.cname |
+ |
+ |
+class PythonCapiCallNode(SimpleCallNode): |
+ # Python C-API Function call (only created in transforms) |
+ |
+ # By default, we assume that the call never returns None, as this |
+ # is true for most C-API functions in CPython. If this does not |
+ # apply to a call, set the following to True (or None to inherit |
+ # the default behaviour). |
+ may_return_none = False |
+ |
+ def __init__(self, pos, function_name, func_type, |
+ utility_code = None, py_name=None, **kwargs): |
+ self.type = func_type.return_type |
+ self.result_ctype = self.type |
+ self.function = PythonCapiFunctionNode( |
+ pos, py_name, function_name, func_type, |
+ utility_code = utility_code) |
+ # call this last so that we can override the constructed |
+ # attributes above with explicit keyword arguments if required |
+ SimpleCallNode.__init__(self, pos, **kwargs) |
+ |
+ |
+class GeneralCallNode(CallNode): |
+ # General Python function call, including keyword, |
+ # * and ** arguments. |
+ # |
+ # function ExprNode |
+ # positional_args ExprNode Tuple of positional arguments |
+ # keyword_args ExprNode or None Dict of keyword arguments |
+ |
+ type = py_object_type |
+ |
+ subexprs = ['function', 'positional_args', 'keyword_args'] |
+ |
+ nogil_check = Node.gil_error |
+ |
+ def compile_time_value(self, denv): |
+ function = self.function.compile_time_value(denv) |
+ positional_args = self.positional_args.compile_time_value(denv) |
+ keyword_args = self.keyword_args.compile_time_value(denv) |
+ try: |
+ return function(*positional_args, **keyword_args) |
+ except Exception, e: |
+ self.compile_time_value_error(e) |
+ |
+ def explicit_args_kwds(self): |
+ if (self.keyword_args and not isinstance(self.keyword_args, DictNode) or |
+ not isinstance(self.positional_args, TupleNode)): |
+ raise CompileError(self.pos, |
+ 'Compile-time keyword arguments must be explicit.') |
+ return self.positional_args.args, self.keyword_args |
+ |
+ def analyse_types(self, env): |
+ if self.analyse_as_type_constructor(env): |
+ return self |
+ self.function = self.function.analyse_types(env) |
+ if not self.function.type.is_pyobject: |
+ if self.function.type.is_error: |
+ self.type = error_type |
+ return self |
+ if hasattr(self.function, 'entry'): |
+ node = self.map_to_simple_call_node() |
+ if node is not None and node is not self: |
+ return node.analyse_types(env) |
+ elif self.function.entry.as_variable: |
+ self.function = self.function.coerce_to_pyobject(env) |
+ elif node is self: |
+ error(self.pos, |
+ "Non-trivial keyword arguments and starred " |
+ "arguments not allowed in cdef functions.") |
+ else: |
+ # error was already reported |
+ pass |
+ else: |
+ self.function = self.function.coerce_to_pyobject(env) |
+ if self.keyword_args: |
+ self.keyword_args = self.keyword_args.analyse_types(env) |
+ self.positional_args = self.positional_args.analyse_types(env) |
+ self.positional_args = \ |
+ self.positional_args.coerce_to_pyobject(env) |
+ function = self.function |
+ if function.is_name and function.type_entry: |
+ # We are calling an extension type constructor. As long |
+ # as we do not support __new__(), the result type is clear |
+ self.type = function.type_entry.type |
+ self.result_ctype = py_object_type |
+ self.may_return_none = False |
+ else: |
+ self.type = py_object_type |
+ self.is_temp = 1 |
+ return self |
+ |
+ def map_to_simple_call_node(self): |
+ """ |
+ Tries to map keyword arguments to declared positional arguments. |
+ Returns self to try a Python call, None to report an error |
+ or a SimpleCallNode if the mapping succeeds. |
+ """ |
+ if not isinstance(self.positional_args, TupleNode): |
+ # has starred argument |
+ return self |
+ if not isinstance(self.keyword_args, DictNode): |
+ # keywords come from arbitrary expression => nothing to do here |
+ return self |
+ function = self.function |
+ entry = getattr(function, 'entry', None) |
+ if not entry: |
+ return self |
+ function_type = entry.type |
+ if function_type.is_ptr: |
+ function_type = function_type.base_type |
+ if not function_type.is_cfunction: |
+ return self |
+ |
+ pos_args = self.positional_args.args |
+ kwargs = self.keyword_args |
+ declared_args = function_type.args |
+ if entry.is_cmethod: |
+ declared_args = declared_args[1:] # skip 'self' |
+ |
+ if len(pos_args) > len(declared_args): |
+ error(self.pos, "function call got too many positional arguments, " |
+ "expected %d, got %s" % (len(declared_args), |
+ len(pos_args))) |
+ return None |
+ |
+ matched_args = set([ arg.name for arg in declared_args[:len(pos_args)] |
+ if arg.name ]) |
+ unmatched_args = declared_args[len(pos_args):] |
+ matched_kwargs_count = 0 |
+ args = list(pos_args) |
+ |
+ # check for duplicate keywords |
+ seen = set(matched_args) |
+ has_errors = False |
+ for arg in kwargs.key_value_pairs: |
+ name = arg.key.value |
+ if name in seen: |
+ error(arg.pos, "argument '%s' passed twice" % name) |
+ has_errors = True |
+ # continue to report more errors if there are any |
+ seen.add(name) |
+ |
+ # match keywords that are passed in order |
+ for decl_arg, arg in zip(unmatched_args, kwargs.key_value_pairs): |
+ name = arg.key.value |
+ if decl_arg.name == name: |
+ matched_args.add(name) |
+ matched_kwargs_count += 1 |
+ args.append(arg.value) |
+ else: |
+ break |
+ |
+ # match keyword arguments that are passed out-of-order, but keep |
+ # the evaluation of non-simple arguments in order by moving them |
+ # into temps |
+ from Cython.Compiler.UtilNodes import EvalWithTempExprNode, LetRefNode |
+ temps = [] |
+ if len(kwargs.key_value_pairs) > matched_kwargs_count: |
+ unmatched_args = declared_args[len(args):] |
+ keywords = dict([ (arg.key.value, (i+len(pos_args), arg)) |
+ for i, arg in enumerate(kwargs.key_value_pairs) ]) |
+ first_missing_keyword = None |
+ for decl_arg in unmatched_args: |
+ name = decl_arg.name |
+ if name not in keywords: |
+ # missing keyword argument => either done or error |
+ if not first_missing_keyword: |
+ first_missing_keyword = name |
+ continue |
+ elif first_missing_keyword: |
+ if entry.as_variable: |
+ # we might be able to convert the function to a Python |
+ # object, which then allows full calling semantics |
+ # with default values in gaps - currently, we only |
+ # support optional arguments at the end |
+ return self |
+ # wasn't the last keyword => gaps are not supported |
+ error(self.pos, "C function call is missing " |
+ "argument '%s'" % first_missing_keyword) |
+ return None |
+ pos, arg = keywords[name] |
+ matched_args.add(name) |
+ matched_kwargs_count += 1 |
+ if arg.value.is_simple(): |
+ args.append(arg.value) |
+ else: |
+ temp = LetRefNode(arg.value) |
+ assert temp.is_simple() |
+ args.append(temp) |
+ temps.append((pos, temp)) |
+ |
+ if temps: |
+ # may have to move preceding non-simple args into temps |
+ final_args = [] |
+ new_temps = [] |
+ first_temp_arg = temps[0][-1] |
+ for arg_value in args: |
+ if arg_value is first_temp_arg: |
+ break # done |
+ if arg_value.is_simple(): |
+ final_args.append(arg_value) |
+ else: |
+ temp = LetRefNode(arg_value) |
+ new_temps.append(temp) |
+ final_args.append(temp) |
+ if new_temps: |
+ args = final_args |
+ temps = new_temps + [ arg for i,arg in sorted(temps) ] |
+ |
+ # check for unexpected keywords |
+ for arg in kwargs.key_value_pairs: |
+ name = arg.key.value |
+ if name not in matched_args: |
+ has_errors = True |
+ error(arg.pos, |
+ "C function got unexpected keyword argument '%s'" % |
+ name) |
+ |
+ if has_errors: |
+ # error was reported already |
+ return None |
+ |
+ # all keywords mapped to positional arguments |
+ # if we are missing arguments, SimpleCallNode will figure it out |
+ node = SimpleCallNode(self.pos, function=function, args=args) |
+ for temp in temps[::-1]: |
+ node = EvalWithTempExprNode(temp, node) |
+ return node |
+ |
+ def generate_result_code(self, code): |
+ if self.type.is_error: return |
+ if self.keyword_args: |
+ kwargs = self.keyword_args.py_result() |
+ else: |
+ kwargs = 'NULL' |
+ code.globalstate.use_utility_code(UtilityCode.load_cached( |
+ "PyObjectCall", "ObjectHandling.c")) |
+ code.putln( |
+ "%s = __Pyx_PyObject_Call(%s, %s, %s); %s" % ( |
+ self.result(), |
+ self.function.py_result(), |
+ self.positional_args.py_result(), |
+ kwargs, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ |
+ |
+class AsTupleNode(ExprNode): |
+ # Convert argument to tuple. Used for normalising |
+ # the * argument of a function call. |
+ # |
+ # arg ExprNode |
+ |
+ subexprs = ['arg'] |
+ |
+ def calculate_constant_result(self): |
+ self.constant_result = tuple(self.arg.constant_result) |
+ |
+ def compile_time_value(self, denv): |
+ arg = self.arg.compile_time_value(denv) |
+ try: |
+ return tuple(arg) |
+ except Exception, e: |
+ self.compile_time_value_error(e) |
+ |
+ def analyse_types(self, env): |
+ self.arg = self.arg.analyse_types(env) |
+ self.arg = self.arg.coerce_to_pyobject(env) |
+ self.type = tuple_type |
+ self.is_temp = 1 |
+ return self |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ nogil_check = Node.gil_error |
+ gil_message = "Constructing Python tuple" |
+ |
+ def generate_result_code(self, code): |
+ code.putln( |
+ "%s = PySequence_Tuple(%s); %s" % ( |
+ self.result(), |
+ self.arg.py_result(), |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ |
+ |
+class AttributeNode(ExprNode): |
+ # obj.attribute |
+ # |
+ # obj ExprNode |
+ # attribute string |
+ # needs_none_check boolean Used if obj is an extension type. |
+ # If set to True, it is known that the type is not None. |
+ # |
+ # Used internally: |
+ # |
+ # is_py_attr boolean Is a Python getattr operation |
+ # member string C name of struct member |
+ # is_called boolean Function call is being done on result |
+ # entry Entry Symbol table entry of attribute |
+ |
+ is_attribute = 1 |
+ subexprs = ['obj'] |
+ |
+ type = PyrexTypes.error_type |
+ entry = None |
+ is_called = 0 |
+ needs_none_check = True |
+ is_memslice_transpose = False |
+ is_special_lookup = False |
+ |
+ def as_cython_attribute(self): |
+ if (isinstance(self.obj, NameNode) and |
+ self.obj.is_cython_module and not |
+ self.attribute == u"parallel"): |
+ return self.attribute |
+ |
+ cy = self.obj.as_cython_attribute() |
+ if cy: |
+ return "%s.%s" % (cy, self.attribute) |
+ return None |
+ |
+ def coerce_to(self, dst_type, env): |
+ # If coercing to a generic pyobject and this is a cpdef function |
+ # we can create the corresponding attribute |
+ if dst_type is py_object_type: |
+ entry = self.entry |
+ if entry and entry.is_cfunction and entry.as_variable: |
+ # must be a cpdef function |
+ self.is_temp = 1 |
+ self.entry = entry.as_variable |
+ self.analyse_as_python_attribute(env) |
+ return self |
+ return ExprNode.coerce_to(self, dst_type, env) |
+ |
+ def calculate_constant_result(self): |
+ attr = self.attribute |
+ if attr.startswith("__") and attr.endswith("__"): |
+ return |
+ self.constant_result = getattr(self.obj.constant_result, attr) |
+ |
+ def compile_time_value(self, denv): |
+ attr = self.attribute |
+ if attr.startswith("__") and attr.endswith("__"): |
+ error(self.pos, |
+ "Invalid attribute name '%s' in compile-time expression" % attr) |
+ return None |
+ obj = self.obj.compile_time_value(denv) |
+ try: |
+ return getattr(obj, attr) |
+ except Exception, e: |
+ self.compile_time_value_error(e) |
+ |
+ def type_dependencies(self, env): |
+ return self.obj.type_dependencies(env) |
+ |
+ def infer_type(self, env): |
+ # FIXME: this is way too redundant with analyse_types() |
+ node = self.analyse_as_cimported_attribute_node(env, target=False) |
+ if node is not None: |
+ return node.entry.type |
+ node = self.analyse_as_unbound_cmethod_node(env) |
+ if node is not None: |
+ return node.entry.type |
+ obj_type = self.obj.infer_type(env) |
+ self.analyse_attribute(env, obj_type=obj_type) |
+ if obj_type.is_builtin_type and self.type.is_cfunction: |
+ # special case: C-API replacements for C methods of |
+ # builtin types cannot be inferred as C functions as |
+ # that would prevent their use as bound methods |
+ return py_object_type |
+ return self.type |
+ |
+ def analyse_target_declaration(self, env): |
+ pass |
+ |
+ def analyse_target_types(self, env): |
+ node = self.analyse_types(env, target = 1) |
+ if node.type.is_const: |
+ error(self.pos, "Assignment to const attribute '%s'" % self.attribute) |
+ if not node.is_lvalue(): |
+ error(self.pos, "Assignment to non-lvalue of type '%s'" % self.type) |
+ return node |
+ |
+ def analyse_types(self, env, target = 0): |
+ self.initialized_check = env.directives['initializedcheck'] |
+ node = self.analyse_as_cimported_attribute_node(env, target) |
+ if node is None and not target: |
+ node = self.analyse_as_unbound_cmethod_node(env) |
+ if node is None: |
+ node = self.analyse_as_ordinary_attribute_node(env, target) |
+ assert node is not None |
+ if node.entry: |
+ node.entry.used = True |
+ if node.is_attribute: |
+ node.wrap_obj_in_nonecheck(env) |
+ return node |
+ |
+ def analyse_as_cimported_attribute_node(self, env, target): |
+ # Try to interpret this as a reference to an imported |
+ # C const, type, var or function. If successful, mutates |
+ # this node into a NameNode and returns 1, otherwise |
+ # returns 0. |
+ module_scope = self.obj.analyse_as_module(env) |
+ if module_scope: |
+ entry = module_scope.lookup_here(self.attribute) |
+ if entry and ( |
+ entry.is_cglobal or entry.is_cfunction |
+ or entry.is_type or entry.is_const): |
+ return self.as_name_node(env, entry, target) |
+ return None |
+ |
+ def analyse_as_unbound_cmethod_node(self, env): |
+ # Try to interpret this as a reference to an unbound |
+ # C method of an extension type or builtin type. If successful, |
+ # creates a corresponding NameNode and returns it, otherwise |
+ # returns None. |
+ type = self.obj.analyse_as_extension_type(env) |
+ if type: |
+ entry = type.scope.lookup_here(self.attribute) |
+ if entry and entry.is_cmethod: |
+ if type.is_builtin_type: |
+ if not self.is_called: |
+ # must handle this as Python object |
+ return None |
+ ubcm_entry = entry |
+ else: |
+ # Create a temporary entry describing the C method |
+ # as an ordinary function. |
+ ubcm_entry = Symtab.Entry(entry.name, |
+ "%s->%s" % (type.vtabptr_cname, entry.cname), |
+ entry.type) |
+ ubcm_entry.is_cfunction = 1 |
+ ubcm_entry.func_cname = entry.func_cname |
+ ubcm_entry.is_unbound_cmethod = 1 |
+ return self.as_name_node(env, ubcm_entry, target=False) |
+ return None |
+ |
+ def analyse_as_type(self, env): |
+ module_scope = self.obj.analyse_as_module(env) |
+ if module_scope: |
+ return module_scope.lookup_type(self.attribute) |
+ if not self.obj.is_string_literal: |
+ base_type = self.obj.analyse_as_type(env) |
+ if base_type and hasattr(base_type, 'scope') and base_type.scope is not None: |
+ return base_type.scope.lookup_type(self.attribute) |
+ return None |
+ |
+ def analyse_as_extension_type(self, env): |
+ # Try to interpret this as a reference to an extension type |
+ # in a cimported module. Returns the extension type, or None. |
+ module_scope = self.obj.analyse_as_module(env) |
+ if module_scope: |
+ entry = module_scope.lookup_here(self.attribute) |
+ if entry and entry.is_type: |
+ if entry.type.is_extension_type or entry.type.is_builtin_type: |
+ return entry.type |
+ return None |
+ |
+ def analyse_as_module(self, env): |
+ # Try to interpret this as a reference to a cimported module |
+ # in another cimported module. Returns the module scope, or None. |
+ module_scope = self.obj.analyse_as_module(env) |
+ if module_scope: |
+ entry = module_scope.lookup_here(self.attribute) |
+ if entry and entry.as_module: |
+ return entry.as_module |
+ return None |
+ |
+ def as_name_node(self, env, entry, target): |
+ # Create a corresponding NameNode from this node and complete the |
+ # analyse_types phase. |
+ node = NameNode.from_node(self, name=self.attribute, entry=entry) |
+ if target: |
+ node = node.analyse_target_types(env) |
+ else: |
+ node = node.analyse_rvalue_entry(env) |
+ node.entry.used = 1 |
+ return node |
+ |
+ def analyse_as_ordinary_attribute_node(self, env, target): |
+ self.obj = self.obj.analyse_types(env) |
+ self.analyse_attribute(env) |
+ if self.entry and self.entry.is_cmethod and not self.is_called: |
+# error(self.pos, "C method can only be called") |
+ pass |
+ ## Reference to C array turns into pointer to first element. |
+ #while self.type.is_array: |
+ # self.type = self.type.element_ptr_type() |
+ if self.is_py_attr: |
+ if not target: |
+ self.is_temp = 1 |
+ self.result_ctype = py_object_type |
+ elif target and self.obj.type.is_builtin_type: |
+ error(self.pos, "Assignment to an immutable object field") |
+ #elif self.type.is_memoryviewslice and not target: |
+ # self.is_temp = True |
+ return self |
+ |
+ def analyse_attribute(self, env, obj_type = None): |
+ # Look up attribute and set self.type and self.member. |
+ immutable_obj = obj_type is not None # used during type inference |
+ self.is_py_attr = 0 |
+ self.member = self.attribute |
+ if obj_type is None: |
+ if self.obj.type.is_string or self.obj.type.is_pyunicode_ptr: |
+ self.obj = self.obj.coerce_to_pyobject(env) |
+ obj_type = self.obj.type |
+ else: |
+ if obj_type.is_string or obj_type.is_pyunicode_ptr: |
+ obj_type = py_object_type |
+ if obj_type.is_ptr or obj_type.is_array: |
+ obj_type = obj_type.base_type |
+ self.op = "->" |
+ elif obj_type.is_extension_type or obj_type.is_builtin_type: |
+ self.op = "->" |
+ else: |
+ self.op = "." |
+ if obj_type.has_attributes: |
+ if obj_type.attributes_known(): |
+ if (obj_type.is_memoryviewslice and not |
+ obj_type.scope.lookup_here(self.attribute)): |
+ if self.attribute == 'T': |
+ self.is_memslice_transpose = True |
+ self.is_temp = True |
+ self.use_managed_ref = True |
+ self.type = self.obj.type |
+ return |
+ else: |
+ obj_type.declare_attribute(self.attribute, env, self.pos) |
+ entry = obj_type.scope.lookup_here(self.attribute) |
+ if entry and entry.is_member: |
+ entry = None |
+ else: |
+ error(self.pos, |
+ "Cannot select attribute of incomplete type '%s'" |
+ % obj_type) |
+ self.type = PyrexTypes.error_type |
+ return |
+ self.entry = entry |
+ if entry: |
+ if obj_type.is_extension_type and entry.name == "__weakref__": |
+ error(self.pos, "Illegal use of special attribute __weakref__") |
+ |
+ # def methods need the normal attribute lookup |
+ # because they do not have struct entries |
+ # fused function go through assignment synthesis |
+ # (foo = pycfunction(foo_func_obj)) and need to go through |
+ # regular Python lookup as well |
+ if (entry.is_variable and not entry.fused_cfunction) or entry.is_cmethod: |
+ self.type = entry.type |
+ self.member = entry.cname |
+ return |
+ else: |
+ # If it's not a variable or C method, it must be a Python |
+ # method of an extension type, so we treat it like a Python |
+ # attribute. |
+ pass |
+ # If we get here, the base object is not a struct/union/extension |
+ # type, or it is an extension type and the attribute is either not |
+ # declared or is declared as a Python method. Treat it as a Python |
+ # attribute reference. |
+ self.analyse_as_python_attribute(env, obj_type, immutable_obj) |
+ |
+ def analyse_as_python_attribute(self, env, obj_type=None, immutable_obj=False): |
+ if obj_type is None: |
+ obj_type = self.obj.type |
+ # mangle private '__*' Python attributes used inside of a class |
+ self.attribute = env.mangle_class_private_name(self.attribute) |
+ self.member = self.attribute |
+ self.type = py_object_type |
+ self.is_py_attr = 1 |
+ if not obj_type.is_pyobject and not obj_type.is_error: |
+ if obj_type.can_coerce_to_pyobject(env): |
+ if not immutable_obj: |
+ self.obj = self.obj.coerce_to_pyobject(env) |
+ elif (obj_type.is_cfunction and (self.obj.is_name or self.obj.is_attribute) |
+ and self.obj.entry.as_variable |
+ and self.obj.entry.as_variable.type.is_pyobject): |
+ # might be an optimised builtin function => unpack it |
+ if not immutable_obj: |
+ self.obj = self.obj.coerce_to_pyobject(env) |
+ else: |
+ error(self.pos, |
+ "Object of type '%s' has no attribute '%s'" % |
+ (obj_type, self.attribute)) |
+ |
+ def wrap_obj_in_nonecheck(self, env): |
+ if not env.directives['nonecheck']: |
+ return |
+ |
+ msg = None |
+ format_args = () |
+ if (self.obj.type.is_extension_type and self.needs_none_check and not |
+ self.is_py_attr): |
+ msg = "'NoneType' object has no attribute '%s'" |
+ format_args = (self.attribute,) |
+ elif self.obj.type.is_memoryviewslice: |
+ if self.is_memslice_transpose: |
+ msg = "Cannot transpose None memoryview slice" |
+ else: |
+ entry = self.obj.type.scope.lookup_here(self.attribute) |
+ if entry: |
+ # copy/is_c_contig/shape/strides etc |
+ msg = "Cannot access '%s' attribute of None memoryview slice" |
+ format_args = (entry.name,) |
+ |
+ if msg: |
+ self.obj = self.obj.as_none_safe_node(msg, 'PyExc_AttributeError', |
+ format_args=format_args) |
+ |
+ |
+ def nogil_check(self, env): |
+ if self.is_py_attr: |
+ self.gil_error() |
+ elif self.type.is_memoryviewslice: |
+ import MemoryView |
+ MemoryView.err_if_nogil_initialized_check(self.pos, env, 'attribute') |
+ |
+ gil_message = "Accessing Python attribute" |
+ |
+ def is_simple(self): |
+ if self.obj: |
+ return self.result_in_temp() or self.obj.is_simple() |
+ else: |
+ return NameNode.is_simple(self) |
+ |
+ def is_lvalue(self): |
+ if self.obj: |
+ return not self.type.is_array |
+ else: |
+ return NameNode.is_lvalue(self) |
+ |
+ def is_ephemeral(self): |
+ if self.obj: |
+ return self.obj.is_ephemeral() |
+ else: |
+ return NameNode.is_ephemeral(self) |
+ |
+ def calculate_result_code(self): |
+ #print "AttributeNode.calculate_result_code:", self.member ### |
+ #print "...obj node =", self.obj, "code", self.obj.result() ### |
+ #print "...obj type", self.obj.type, "ctype", self.obj.ctype() ### |
+ obj = self.obj |
+ obj_code = obj.result_as(obj.type) |
+ #print "...obj_code =", obj_code ### |
+ if self.entry and self.entry.is_cmethod: |
+ if obj.type.is_extension_type and not self.entry.is_builtin_cmethod: |
+ if self.entry.final_func_cname: |
+ return self.entry.final_func_cname |
+ |
+ if self.type.from_fused: |
+ # If the attribute was specialized through indexing, make |
+ # sure to get the right fused name, as our entry was |
+ # replaced by our parent index node |
+ # (AnalyseExpressionsTransform) |
+ self.member = self.entry.cname |
+ |
+ return "((struct %s *)%s%s%s)->%s" % ( |
+ obj.type.vtabstruct_cname, obj_code, self.op, |
+ obj.type.vtabslot_cname, self.member) |
+ elif self.result_is_used: |
+ return self.member |
+ # Generating no code at all for unused access to optimised builtin |
+ # methods fixes the problem that some optimisations only exist as |
+ # macros, i.e. there is no function pointer to them, so we would |
+ # generate invalid C code here. |
+ return |
+ elif obj.type.is_complex: |
+ return "__Pyx_C%s(%s)" % (self.member.upper(), obj_code) |
+ else: |
+ if obj.type.is_builtin_type and self.entry and self.entry.is_variable: |
+ # accessing a field of a builtin type, need to cast better than result_as() does |
+ obj_code = obj.type.cast_code(obj.result(), to_object_struct = True) |
+ return "%s%s%s" % (obj_code, self.op, self.member) |
+ |
+ def generate_result_code(self, code): |
+ if self.is_py_attr: |
+ if self.is_special_lookup: |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("PyObjectLookupSpecial", "ObjectHandling.c")) |
+ lookup_func_name = '__Pyx_PyObject_LookupSpecial' |
+ else: |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("PyObjectGetAttrStr", "ObjectHandling.c")) |
+ lookup_func_name = '__Pyx_PyObject_GetAttrStr' |
+ code.putln( |
+ '%s = %s(%s, %s); %s' % ( |
+ self.result(), |
+ lookup_func_name, |
+ self.obj.py_result(), |
+ code.intern_identifier(self.attribute), |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ elif self.type.is_memoryviewslice: |
+ if self.is_memslice_transpose: |
+ # transpose the slice |
+ for access, packing in self.type.axes: |
+ if access == 'ptr': |
+ error(self.pos, "Transposing not supported for slices " |
+ "with indirect dimensions") |
+ return |
+ |
+ code.putln("%s = %s;" % (self.result(), self.obj.result())) |
+ if self.obj.is_name or (self.obj.is_attribute and |
+ self.obj.is_memslice_transpose): |
+ code.put_incref_memoryviewslice(self.result(), have_gil=True) |
+ |
+ T = "__pyx_memslice_transpose(&%s) == 0" |
+ code.putln(code.error_goto_if(T % self.result(), self.pos)) |
+ elif self.initialized_check: |
+ code.putln( |
+ 'if (unlikely(!%s.memview)) {' |
+ 'PyErr_SetString(PyExc_AttributeError,' |
+ '"Memoryview is not initialized");' |
+ '%s' |
+ '}' % (self.result(), code.error_goto(self.pos))) |
+ else: |
+ # result_code contains what is needed, but we may need to insert |
+ # a check and raise an exception |
+ if self.obj.type.is_extension_type: |
+ pass |
+ elif self.entry and self.entry.is_cmethod and self.entry.utility_code: |
+ # C method implemented as function call with utility code |
+ code.globalstate.use_utility_code(self.entry.utility_code) |
+ |
+ def generate_disposal_code(self, code): |
+ if self.is_temp and self.type.is_memoryviewslice and self.is_memslice_transpose: |
+ # mirror condition for putting the memview incref here: |
+ if self.obj.is_name or (self.obj.is_attribute and |
+ self.obj.is_memslice_transpose): |
+ code.put_xdecref_memoryviewslice( |
+ self.result(), have_gil=True) |
+ else: |
+ ExprNode.generate_disposal_code(self, code) |
+ |
+ def generate_assignment_code(self, rhs, code): |
+ self.obj.generate_evaluation_code(code) |
+ if self.is_py_attr: |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("PyObjectSetAttrStr", "ObjectHandling.c")) |
+ code.put_error_if_neg(self.pos, |
+ '__Pyx_PyObject_SetAttrStr(%s, %s, %s)' % ( |
+ self.obj.py_result(), |
+ code.intern_identifier(self.attribute), |
+ rhs.py_result())) |
+ rhs.generate_disposal_code(code) |
+ rhs.free_temps(code) |
+ elif self.obj.type.is_complex: |
+ code.putln("__Pyx_SET_C%s(%s, %s);" % ( |
+ self.member.upper(), |
+ self.obj.result_as(self.obj.type), |
+ rhs.result_as(self.ctype()))) |
+ else: |
+ select_code = self.result() |
+ if self.type.is_pyobject and self.use_managed_ref: |
+ rhs.make_owned_reference(code) |
+ code.put_giveref(rhs.py_result()) |
+ code.put_gotref(select_code) |
+ code.put_decref(select_code, self.ctype()) |
+ elif self.type.is_memoryviewslice: |
+ import MemoryView |
+ MemoryView.put_assign_to_memviewslice( |
+ select_code, rhs, rhs.result(), self.type, code) |
+ |
+ if not self.type.is_memoryviewslice: |
+ code.putln( |
+ "%s = %s;" % ( |
+ select_code, |
+ rhs.result_as(self.ctype()))) |
+ #rhs.result())) |
+ rhs.generate_post_assignment_code(code) |
+ rhs.free_temps(code) |
+ self.obj.generate_disposal_code(code) |
+ self.obj.free_temps(code) |
+ |
+ def generate_deletion_code(self, code, ignore_nonexisting=False): |
+ self.obj.generate_evaluation_code(code) |
+ if self.is_py_attr or (self.entry.scope.is_property_scope |
+ and u'__del__' in self.entry.scope.entries): |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("PyObjectSetAttrStr", "ObjectHandling.c")) |
+ code.put_error_if_neg(self.pos, |
+ '__Pyx_PyObject_DelAttrStr(%s, %s)' % ( |
+ self.obj.py_result(), |
+ code.intern_identifier(self.attribute))) |
+ else: |
+ error(self.pos, "Cannot delete C attribute of extension type") |
+ self.obj.generate_disposal_code(code) |
+ self.obj.free_temps(code) |
+ |
+ def annotate(self, code): |
+ if self.is_py_attr: |
+ style, text = 'py_attr', 'python attribute (%s)' |
+ else: |
+ style, text = 'c_attr', 'c attribute (%s)' |
+ code.annotate(self.pos, AnnotationItem(style, text % self.type, size=len(self.attribute))) |
+ |
+ |
+#------------------------------------------------------------------- |
+# |
+# Constructor nodes |
+# |
+#------------------------------------------------------------------- |
+ |
+class StarredTargetNode(ExprNode): |
+ # A starred expression like "*a" |
+ # |
+ # This is only allowed in sequence assignment targets such as |
+ # |
+ # a, *b = (1,2,3,4) => a = 1 ; b = [2,3,4] |
+ # |
+ # and will be removed during type analysis (or generate an error |
+ # if it's found at unexpected places). |
+ # |
+ # target ExprNode |
+ |
+ subexprs = ['target'] |
+ is_starred = 1 |
+ type = py_object_type |
+ is_temp = 1 |
+ |
+ def __init__(self, pos, target): |
+ ExprNode.__init__(self, pos) |
+ self.target = target |
+ |
+ def analyse_declarations(self, env): |
+ error(self.pos, "can use starred expression only as assignment target") |
+ self.target.analyse_declarations(env) |
+ |
+ def analyse_types(self, env): |
+ error(self.pos, "can use starred expression only as assignment target") |
+ self.target = self.target.analyse_types(env) |
+ self.type = self.target.type |
+ return self |
+ |
+ def analyse_target_declaration(self, env): |
+ self.target.analyse_target_declaration(env) |
+ |
+ def analyse_target_types(self, env): |
+ self.target = self.target.analyse_target_types(env) |
+ self.type = self.target.type |
+ return self |
+ |
+ def calculate_result_code(self): |
+ return "" |
+ |
+ def generate_result_code(self, code): |
+ pass |
+ |
+ |
+class SequenceNode(ExprNode): |
+ # Base class for list and tuple constructor nodes. |
+ # Contains common code for performing sequence unpacking. |
+ # |
+ # args [ExprNode] |
+ # unpacked_items [ExprNode] or None |
+ # coerced_unpacked_items [ExprNode] or None |
+ # mult_factor ExprNode the integer number of content repetitions ([1,2]*3) |
+ |
+ subexprs = ['args', 'mult_factor'] |
+ |
+ is_sequence_constructor = 1 |
+ unpacked_items = None |
+ mult_factor = None |
+ slow = False # trade speed for code size (e.g. use PyTuple_Pack()) |
+ |
+ def compile_time_value_list(self, denv): |
+ return [arg.compile_time_value(denv) for arg in self.args] |
+ |
+ def replace_starred_target_node(self): |
+ # replace a starred node in the targets by the contained expression |
+ self.starred_assignment = False |
+ args = [] |
+ for arg in self.args: |
+ if arg.is_starred: |
+ if self.starred_assignment: |
+ error(arg.pos, "more than 1 starred expression in assignment") |
+ self.starred_assignment = True |
+ arg = arg.target |
+ arg.is_starred = True |
+ args.append(arg) |
+ self.args = args |
+ |
+ def analyse_target_declaration(self, env): |
+ self.replace_starred_target_node() |
+ for arg in self.args: |
+ arg.analyse_target_declaration(env) |
+ |
+ def analyse_types(self, env, skip_children=False): |
+ for i in range(len(self.args)): |
+ arg = self.args[i] |
+ if not skip_children: arg = arg.analyse_types(env) |
+ self.args[i] = arg.coerce_to_pyobject(env) |
+ if self.mult_factor: |
+ self.mult_factor = self.mult_factor.analyse_types(env) |
+ if not self.mult_factor.type.is_int: |
+ self.mult_factor = self.mult_factor.coerce_to_pyobject(env) |
+ self.is_temp = 1 |
+ # not setting self.type here, subtypes do this |
+ return self |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ def analyse_target_types(self, env): |
+ if self.mult_factor: |
+ error(self.pos, "can't assign to multiplied sequence") |
+ self.unpacked_items = [] |
+ self.coerced_unpacked_items = [] |
+ self.any_coerced_items = False |
+ for i, arg in enumerate(self.args): |
+ arg = self.args[i] = arg.analyse_target_types(env) |
+ if arg.is_starred: |
+ if not arg.type.assignable_from(Builtin.list_type): |
+ error(arg.pos, |
+ "starred target must have Python object (list) type") |
+ if arg.type is py_object_type: |
+ arg.type = Builtin.list_type |
+ unpacked_item = PyTempNode(self.pos, env) |
+ coerced_unpacked_item = unpacked_item.coerce_to(arg.type, env) |
+ if unpacked_item is not coerced_unpacked_item: |
+ self.any_coerced_items = True |
+ self.unpacked_items.append(unpacked_item) |
+ self.coerced_unpacked_items.append(coerced_unpacked_item) |
+ self.type = py_object_type |
+ return self |
+ |
+ def generate_result_code(self, code): |
+ self.generate_operation_code(code) |
+ |
+ def generate_sequence_packing_code(self, code, target=None, plain=False): |
+ if target is None: |
+ target = self.result() |
+ size_factor = c_mult = '' |
+ mult_factor = None |
+ |
+ if self.mult_factor and not plain: |
+ mult_factor = self.mult_factor |
+ if mult_factor.type.is_int: |
+ c_mult = mult_factor.result() |
+ if isinstance(mult_factor.constant_result, (int,long)) \ |
+ and mult_factor.constant_result > 0: |
+ size_factor = ' * %s' % mult_factor.constant_result |
+ else: |
+ size_factor = ' * ((%s<0) ? 0:%s)' % (c_mult, c_mult) |
+ |
+ if self.type is Builtin.tuple_type and (self.is_literal or self.slow) and not c_mult: |
+ # use PyTuple_Pack() to avoid generating huge amounts of one-time code |
+ code.putln('%s = PyTuple_Pack(%d, %s); %s' % ( |
+ target, |
+ len(self.args), |
+ ', '.join([ arg.py_result() for arg in self.args ]), |
+ code.error_goto_if_null(target, self.pos))) |
+ code.put_gotref(target) |
+ else: |
+ # build the tuple/list step by step, potentially multiplying it as we go |
+ if self.type is Builtin.list_type: |
+ create_func, set_item_func = 'PyList_New', 'PyList_SET_ITEM' |
+ elif self.type is Builtin.tuple_type: |
+ create_func, set_item_func = 'PyTuple_New', 'PyTuple_SET_ITEM' |
+ else: |
+ raise InternalError("sequence packing for unexpected type %s" % self.type) |
+ arg_count = len(self.args) |
+ code.putln("%s = %s(%s%s); %s" % ( |
+ target, create_func, arg_count, size_factor, |
+ code.error_goto_if_null(target, self.pos))) |
+ code.put_gotref(target) |
+ |
+ if c_mult: |
+ # FIXME: can't use a temp variable here as the code may |
+ # end up in the constant building function. Temps |
+ # currently don't work there. |
+ |
+ #counter = code.funcstate.allocate_temp(mult_factor.type, manage_ref=False) |
+ counter = Naming.quick_temp_cname |
+ code.putln('{ Py_ssize_t %s;' % counter) |
+ if arg_count == 1: |
+ offset = counter |
+ else: |
+ offset = '%s * %s' % (counter, arg_count) |
+ code.putln('for (%s=0; %s < %s; %s++) {' % ( |
+ counter, counter, c_mult, counter |
+ )) |
+ else: |
+ offset = '' |
+ |
+ for i in xrange(arg_count): |
+ arg = self.args[i] |
+ if c_mult or not arg.result_in_temp(): |
+ code.put_incref(arg.result(), arg.ctype()) |
+ code.putln("%s(%s, %s, %s);" % ( |
+ set_item_func, |
+ target, |
+ (offset and i) and ('%s + %s' % (offset, i)) or (offset or i), |
+ arg.py_result())) |
+ code.put_giveref(arg.py_result()) |
+ |
+ if c_mult: |
+ code.putln('}') |
+ #code.funcstate.release_temp(counter) |
+ code.putln('}') |
+ |
+ if mult_factor is not None and mult_factor.type.is_pyobject: |
+ code.putln('{ PyObject* %s = PyNumber_InPlaceMultiply(%s, %s); %s' % ( |
+ Naming.quick_temp_cname, target, mult_factor.py_result(), |
+ code.error_goto_if_null(Naming.quick_temp_cname, self.pos) |
+ )) |
+ code.put_gotref(Naming.quick_temp_cname) |
+ code.put_decref(target, py_object_type) |
+ code.putln('%s = %s;' % (target, Naming.quick_temp_cname)) |
+ code.putln('}') |
+ |
+ def generate_subexpr_disposal_code(self, code): |
+ if self.mult_factor and self.mult_factor.type.is_int: |
+ super(SequenceNode, self).generate_subexpr_disposal_code(code) |
+ elif self.type is Builtin.tuple_type and (self.is_literal or self.slow): |
+ super(SequenceNode, self).generate_subexpr_disposal_code(code) |
+ else: |
+ # We call generate_post_assignment_code here instead |
+ # of generate_disposal_code, because values were stored |
+ # in the tuple using a reference-stealing operation. |
+ for arg in self.args: |
+ arg.generate_post_assignment_code(code) |
+ # Should NOT call free_temps -- this is invoked by the default |
+ # generate_evaluation_code which will do that. |
+ if self.mult_factor: |
+ self.mult_factor.generate_disposal_code(code) |
+ |
+ def generate_assignment_code(self, rhs, code): |
+ if self.starred_assignment: |
+ self.generate_starred_assignment_code(rhs, code) |
+ else: |
+ self.generate_parallel_assignment_code(rhs, code) |
+ |
+ for item in self.unpacked_items: |
+ item.release(code) |
+ rhs.free_temps(code) |
+ |
+ _func_iternext_type = PyrexTypes.CPtrType(PyrexTypes.CFuncType( |
+ PyrexTypes.py_object_type, [ |
+ PyrexTypes.CFuncTypeArg("it", PyrexTypes.py_object_type, None), |
+ ])) |
+ |
+ def generate_parallel_assignment_code(self, rhs, code): |
+ # Need to work around the fact that generate_evaluation_code |
+ # allocates the temps in a rather hacky way -- the assignment |
+ # is evaluated twice, within each if-block. |
+ for item in self.unpacked_items: |
+ item.allocate(code) |
+ special_unpack = (rhs.type is py_object_type |
+ or rhs.type in (tuple_type, list_type) |
+ or not rhs.type.is_builtin_type) |
+ long_enough_for_a_loop = len(self.unpacked_items) > 3 |
+ |
+ if special_unpack: |
+ self.generate_special_parallel_unpacking_code( |
+ code, rhs, use_loop=long_enough_for_a_loop) |
+ else: |
+ code.putln("{") |
+ self.generate_generic_parallel_unpacking_code( |
+ code, rhs, self.unpacked_items, use_loop=long_enough_for_a_loop) |
+ code.putln("}") |
+ |
+ for value_node in self.coerced_unpacked_items: |
+ value_node.generate_evaluation_code(code) |
+ for i in range(len(self.args)): |
+ self.args[i].generate_assignment_code( |
+ self.coerced_unpacked_items[i], code) |
+ |
+ def generate_special_parallel_unpacking_code(self, code, rhs, use_loop): |
+ sequence_type_test = '1' |
+ none_check = "likely(%s != Py_None)" % rhs.py_result() |
+ if rhs.type is list_type: |
+ sequence_types = ['List'] |
+ if rhs.may_be_none(): |
+ sequence_type_test = none_check |
+ elif rhs.type is tuple_type: |
+ sequence_types = ['Tuple'] |
+ if rhs.may_be_none(): |
+ sequence_type_test = none_check |
+ else: |
+ sequence_types = ['Tuple', 'List'] |
+ tuple_check = 'likely(PyTuple_CheckExact(%s))' % rhs.py_result() |
+ list_check = 'PyList_CheckExact(%s)' % rhs.py_result() |
+ sequence_type_test = "(%s) || (%s)" % (tuple_check, list_check) |
+ |
+ code.putln("if (%s) {" % sequence_type_test) |
+ code.putln("PyObject* sequence = %s;" % rhs.py_result()) |
+ |
+ # list/tuple => check size |
+ code.putln("#if CYTHON_COMPILING_IN_CPYTHON") |
+ code.putln("Py_ssize_t size = Py_SIZE(sequence);") |
+ code.putln("#else") |
+ code.putln("Py_ssize_t size = PySequence_Size(sequence);") # < 0 => exception |
+ code.putln("#endif") |
+ code.putln("if (unlikely(size != %d)) {" % len(self.args)) |
+ code.globalstate.use_utility_code(raise_too_many_values_to_unpack) |
+ code.putln("if (size > %d) __Pyx_RaiseTooManyValuesError(%d);" % ( |
+ len(self.args), len(self.args))) |
+ code.globalstate.use_utility_code(raise_need_more_values_to_unpack) |
+ code.putln("else if (size >= 0) __Pyx_RaiseNeedMoreValuesError(size);") |
+ code.putln(code.error_goto(self.pos)) |
+ code.putln("}") |
+ |
+ code.putln("#if CYTHON_COMPILING_IN_CPYTHON") |
+ # unpack items from list/tuple in unrolled loop (can't fail) |
+ if len(sequence_types) == 2: |
+ code.putln("if (likely(Py%s_CheckExact(sequence))) {" % sequence_types[0]) |
+ for i, item in enumerate(self.unpacked_items): |
+ code.putln("%s = Py%s_GET_ITEM(sequence, %d); " % ( |
+ item.result(), sequence_types[0], i)) |
+ if len(sequence_types) == 2: |
+ code.putln("} else {") |
+ for i, item in enumerate(self.unpacked_items): |
+ code.putln("%s = Py%s_GET_ITEM(sequence, %d); " % ( |
+ item.result(), sequence_types[1], i)) |
+ code.putln("}") |
+ for item in self.unpacked_items: |
+ code.put_incref(item.result(), item.ctype()) |
+ |
+ code.putln("#else") |
+ # in non-CPython, use the PySequence protocol (which can fail) |
+ if not use_loop: |
+ for i, item in enumerate(self.unpacked_items): |
+ code.putln("%s = PySequence_ITEM(sequence, %d); %s" % ( |
+ item.result(), i, |
+ code.error_goto_if_null(item.result(), self.pos))) |
+ code.put_gotref(item.result()) |
+ else: |
+ code.putln("{") |
+ code.putln("Py_ssize_t i;") |
+ code.putln("PyObject** temps[%s] = {%s};" % ( |
+ len(self.unpacked_items), |
+ ','.join(['&%s' % item.result() for item in self.unpacked_items]))) |
+ code.putln("for (i=0; i < %s; i++) {" % len(self.unpacked_items)) |
+ code.putln("PyObject* item = PySequence_ITEM(sequence, i); %s" % ( |
+ code.error_goto_if_null('item', self.pos))) |
+ code.put_gotref('item') |
+ code.putln("*(temps[i]) = item;") |
+ code.putln("}") |
+ code.putln("}") |
+ |
+ code.putln("#endif") |
+ rhs.generate_disposal_code(code) |
+ |
+ if sequence_type_test == '1': |
+ code.putln("}") # all done |
+ elif sequence_type_test == none_check: |
+ # either tuple/list or None => save some code by generating the error directly |
+ code.putln("} else {") |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("RaiseNoneIterError", "ObjectHandling.c")) |
+ code.putln("__Pyx_RaiseNoneNotIterableError(); %s" % code.error_goto(self.pos)) |
+ code.putln("}") # all done |
+ else: |
+ code.putln("} else {") # needs iteration fallback code |
+ self.generate_generic_parallel_unpacking_code( |
+ code, rhs, self.unpacked_items, use_loop=use_loop) |
+ code.putln("}") |
+ |
+ def generate_generic_parallel_unpacking_code(self, code, rhs, unpacked_items, use_loop, terminate=True): |
+ code.globalstate.use_utility_code(raise_need_more_values_to_unpack) |
+ code.globalstate.use_utility_code(UtilityCode.load_cached("IterFinish", "ObjectHandling.c")) |
+ code.putln("Py_ssize_t index = -1;") # must be at the start of a C block! |
+ |
+ if use_loop: |
+ code.putln("PyObject** temps[%s] = {%s};" % ( |
+ len(self.unpacked_items), |
+ ','.join(['&%s' % item.result() for item in unpacked_items]))) |
+ |
+ iterator_temp = code.funcstate.allocate_temp(py_object_type, manage_ref=True) |
+ code.putln( |
+ "%s = PyObject_GetIter(%s); %s" % ( |
+ iterator_temp, |
+ rhs.py_result(), |
+ code.error_goto_if_null(iterator_temp, self.pos))) |
+ code.put_gotref(iterator_temp) |
+ rhs.generate_disposal_code(code) |
+ |
+ iternext_func = code.funcstate.allocate_temp(self._func_iternext_type, manage_ref=False) |
+ code.putln("%s = Py_TYPE(%s)->tp_iternext;" % ( |
+ iternext_func, iterator_temp)) |
+ |
+ unpacking_error_label = code.new_label('unpacking_failed') |
+ unpack_code = "%s(%s)" % (iternext_func, iterator_temp) |
+ if use_loop: |
+ code.putln("for (index=0; index < %s; index++) {" % len(unpacked_items)) |
+ code.put("PyObject* item = %s; if (unlikely(!item)) " % unpack_code) |
+ code.put_goto(unpacking_error_label) |
+ code.put_gotref("item") |
+ code.putln("*(temps[index]) = item;") |
+ code.putln("}") |
+ else: |
+ for i, item in enumerate(unpacked_items): |
+ code.put( |
+ "index = %d; %s = %s; if (unlikely(!%s)) " % ( |
+ i, |
+ item.result(), |
+ unpack_code, |
+ item.result())) |
+ code.put_goto(unpacking_error_label) |
+ code.put_gotref(item.py_result()) |
+ |
+ if terminate: |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("UnpackItemEndCheck", "ObjectHandling.c")) |
+ code.put_error_if_neg(self.pos, "__Pyx_IternextUnpackEndCheck(%s, %d)" % ( |
+ unpack_code, |
+ len(unpacked_items))) |
+ code.putln("%s = NULL;" % iternext_func) |
+ code.put_decref_clear(iterator_temp, py_object_type) |
+ |
+ unpacking_done_label = code.new_label('unpacking_done') |
+ code.put_goto(unpacking_done_label) |
+ |
+ code.put_label(unpacking_error_label) |
+ code.put_decref_clear(iterator_temp, py_object_type) |
+ code.putln("%s = NULL;" % iternext_func) |
+ code.putln("if (__Pyx_IterFinish() == 0) __Pyx_RaiseNeedMoreValuesError(index);") |
+ code.putln(code.error_goto(self.pos)) |
+ code.put_label(unpacking_done_label) |
+ |
+ code.funcstate.release_temp(iternext_func) |
+ if terminate: |
+ code.funcstate.release_temp(iterator_temp) |
+ iterator_temp = None |
+ |
+ return iterator_temp |
+ |
+ def generate_starred_assignment_code(self, rhs, code): |
+ for i, arg in enumerate(self.args): |
+ if arg.is_starred: |
+ starred_target = self.unpacked_items[i] |
+ unpacked_fixed_items_left = self.unpacked_items[:i] |
+ unpacked_fixed_items_right = self.unpacked_items[i+1:] |
+ break |
+ else: |
+ assert False |
+ |
+ iterator_temp = None |
+ if unpacked_fixed_items_left: |
+ for item in unpacked_fixed_items_left: |
+ item.allocate(code) |
+ code.putln('{') |
+ iterator_temp = self.generate_generic_parallel_unpacking_code( |
+ code, rhs, unpacked_fixed_items_left, |
+ use_loop=True, terminate=False) |
+ for i, item in enumerate(unpacked_fixed_items_left): |
+ value_node = self.coerced_unpacked_items[i] |
+ value_node.generate_evaluation_code(code) |
+ code.putln('}') |
+ |
+ starred_target.allocate(code) |
+ target_list = starred_target.result() |
+ code.putln("%s = PySequence_List(%s); %s" % ( |
+ target_list, |
+ iterator_temp or rhs.py_result(), |
+ code.error_goto_if_null(target_list, self.pos))) |
+ code.put_gotref(target_list) |
+ |
+ if iterator_temp: |
+ code.put_decref_clear(iterator_temp, py_object_type) |
+ code.funcstate.release_temp(iterator_temp) |
+ else: |
+ rhs.generate_disposal_code(code) |
+ |
+ if unpacked_fixed_items_right: |
+ code.globalstate.use_utility_code(raise_need_more_values_to_unpack) |
+ length_temp = code.funcstate.allocate_temp(PyrexTypes.c_py_ssize_t_type, manage_ref=False) |
+ code.putln('%s = PyList_GET_SIZE(%s);' % (length_temp, target_list)) |
+ code.putln("if (unlikely(%s < %d)) {" % (length_temp, len(unpacked_fixed_items_right))) |
+ code.putln("__Pyx_RaiseNeedMoreValuesError(%d+%s); %s" % ( |
+ len(unpacked_fixed_items_left), length_temp, |
+ code.error_goto(self.pos))) |
+ code.putln('}') |
+ |
+ for item in unpacked_fixed_items_right[::-1]: |
+ item.allocate(code) |
+ for i, (item, coerced_arg) in enumerate(zip(unpacked_fixed_items_right[::-1], |
+ self.coerced_unpacked_items[::-1])): |
+ code.putln('#if CYTHON_COMPILING_IN_CPYTHON') |
+ code.putln("%s = PyList_GET_ITEM(%s, %s-%d); " % ( |
+ item.py_result(), target_list, length_temp, i+1)) |
+ # resize the list the hard way |
+ code.putln("((PyVarObject*)%s)->ob_size--;" % target_list) |
+ code.putln('#else') |
+ code.putln("%s = PySequence_ITEM(%s, %s-%d); " % ( |
+ item.py_result(), target_list, length_temp, i+1)) |
+ code.putln('#endif') |
+ code.put_gotref(item.py_result()) |
+ coerced_arg.generate_evaluation_code(code) |
+ |
+ code.putln('#if !CYTHON_COMPILING_IN_CPYTHON') |
+ sublist_temp = code.funcstate.allocate_temp(py_object_type, manage_ref=True) |
+ code.putln('%s = PySequence_GetSlice(%s, 0, %s-%d); %s' % ( |
+ sublist_temp, target_list, length_temp, len(unpacked_fixed_items_right), |
+ code.error_goto_if_null(sublist_temp, self.pos))) |
+ code.put_gotref(sublist_temp) |
+ code.funcstate.release_temp(length_temp) |
+ code.put_decref(target_list, py_object_type) |
+ code.putln('%s = %s; %s = NULL;' % (target_list, sublist_temp, sublist_temp)) |
+ code.putln('#else') |
+ code.putln('%s = %s;' % (sublist_temp, sublist_temp)) # avoid warning about unused variable |
+ code.funcstate.release_temp(sublist_temp) |
+ code.putln('#endif') |
+ |
+ for i, arg in enumerate(self.args): |
+ arg.generate_assignment_code(self.coerced_unpacked_items[i], code) |
+ |
+ def annotate(self, code): |
+ for arg in self.args: |
+ arg.annotate(code) |
+ if self.unpacked_items: |
+ for arg in self.unpacked_items: |
+ arg.annotate(code) |
+ for arg in self.coerced_unpacked_items: |
+ arg.annotate(code) |
+ |
+ |
+class TupleNode(SequenceNode): |
+ # Tuple constructor. |
+ |
+ type = tuple_type |
+ is_partly_literal = False |
+ |
+ gil_message = "Constructing Python tuple" |
+ |
+ def analyse_types(self, env, skip_children=False): |
+ if len(self.args) == 0: |
+ node = self |
+ node.is_temp = False |
+ node.is_literal = True |
+ else: |
+ node = SequenceNode.analyse_types(self, env, skip_children) |
+ for child in node.args: |
+ if not child.is_literal: |
+ break |
+ else: |
+ if not node.mult_factor or node.mult_factor.is_literal and \ |
+ isinstance(node.mult_factor.constant_result, (int, long)): |
+ node.is_temp = False |
+ node.is_literal = True |
+ else: |
+ if not node.mult_factor.type.is_pyobject: |
+ node.mult_factor = node.mult_factor.coerce_to_pyobject(env) |
+ node.is_temp = True |
+ node.is_partly_literal = True |
+ return node |
+ |
+ def is_simple(self): |
+ # either temp or constant => always simple |
+ return True |
+ |
+ def nonlocally_immutable(self): |
+ # either temp or constant => always safe |
+ return True |
+ |
+ def calculate_result_code(self): |
+ if len(self.args) > 0: |
+ return self.result_code |
+ else: |
+ return Naming.empty_tuple |
+ |
+ def calculate_constant_result(self): |
+ self.constant_result = tuple([ |
+ arg.constant_result for arg in self.args]) |
+ |
+ def compile_time_value(self, denv): |
+ values = self.compile_time_value_list(denv) |
+ try: |
+ return tuple(values) |
+ except Exception, e: |
+ self.compile_time_value_error(e) |
+ |
+ def generate_operation_code(self, code): |
+ if len(self.args) == 0: |
+ # result_code is Naming.empty_tuple |
+ return |
+ if self.is_partly_literal: |
+ # underlying tuple is const, but factor is not |
+ tuple_target = code.get_py_const(py_object_type, 'tuple', cleanup_level=2) |
+ const_code = code.get_cached_constants_writer() |
+ const_code.mark_pos(self.pos) |
+ self.generate_sequence_packing_code(const_code, tuple_target, plain=True) |
+ const_code.put_giveref(tuple_target) |
+ code.putln('%s = PyNumber_Multiply(%s, %s); %s' % ( |
+ self.result(), tuple_target, self.mult_factor.py_result(), |
+ code.error_goto_if_null(self.result(), self.pos) |
+ )) |
+ code.put_gotref(self.py_result()) |
+ elif self.is_literal: |
+ # non-empty cached tuple => result is global constant, |
+ # creation code goes into separate code writer |
+ self.result_code = code.get_py_const(py_object_type, 'tuple', cleanup_level=2) |
+ code = code.get_cached_constants_writer() |
+ code.mark_pos(self.pos) |
+ self.generate_sequence_packing_code(code) |
+ code.put_giveref(self.py_result()) |
+ else: |
+ self.generate_sequence_packing_code(code) |
+ |
+ |
+class ListNode(SequenceNode): |
+ # List constructor. |
+ |
+ # obj_conversion_errors [PyrexError] used internally |
+ # orignial_args [ExprNode] used internally |
+ |
+ obj_conversion_errors = [] |
+ type = list_type |
+ in_module_scope = False |
+ |
+ gil_message = "Constructing Python list" |
+ |
+ def type_dependencies(self, env): |
+ return () |
+ |
+ def infer_type(self, env): |
+ # TOOD: Infer non-object list arrays. |
+ return list_type |
+ |
+ def analyse_expressions(self, env): |
+ node = SequenceNode.analyse_expressions(self, env) |
+ return node.coerce_to_pyobject(env) |
+ |
+ def analyse_types(self, env): |
+ hold_errors() |
+ self.original_args = list(self.args) |
+ node = SequenceNode.analyse_types(self, env) |
+ node.obj_conversion_errors = held_errors() |
+ release_errors(ignore=True) |
+ if env.is_module_scope: |
+ self.in_module_scope = True |
+ return node |
+ |
+ def coerce_to(self, dst_type, env): |
+ if dst_type.is_pyobject: |
+ for err in self.obj_conversion_errors: |
+ report_error(err) |
+ self.obj_conversion_errors = [] |
+ if not self.type.subtype_of(dst_type): |
+ error(self.pos, "Cannot coerce list to type '%s'" % dst_type) |
+ elif self.mult_factor: |
+ error(self.pos, "Cannot coerce multiplied list to '%s'" % dst_type) |
+ elif dst_type.is_ptr and dst_type.base_type is not PyrexTypes.c_void_type: |
+ base_type = dst_type.base_type |
+ self.type = PyrexTypes.CArrayType(base_type, len(self.args)) |
+ for i in range(len(self.original_args)): |
+ arg = self.args[i] |
+ if isinstance(arg, CoerceToPyTypeNode): |
+ arg = arg.arg |
+ self.args[i] = arg.coerce_to(base_type, env) |
+ elif dst_type.is_struct: |
+ if len(self.args) > len(dst_type.scope.var_entries): |
+ error(self.pos, "Too may members for '%s'" % dst_type) |
+ else: |
+ if len(self.args) < len(dst_type.scope.var_entries): |
+ warning(self.pos, "Too few members for '%s'" % dst_type, 1) |
+ for i, (arg, member) in enumerate(zip(self.original_args, dst_type.scope.var_entries)): |
+ if isinstance(arg, CoerceToPyTypeNode): |
+ arg = arg.arg |
+ self.args[i] = arg.coerce_to(member.type, env) |
+ self.type = dst_type |
+ else: |
+ self.type = error_type |
+ error(self.pos, "Cannot coerce list to type '%s'" % dst_type) |
+ return self |
+ |
+ def as_tuple(self): |
+ t = TupleNode(self.pos, args=self.args, mult_factor=self.mult_factor) |
+ if isinstance(self.constant_result, list): |
+ t.constant_result = tuple(self.constant_result) |
+ return t |
+ |
+ def allocate_temp_result(self, code): |
+ if self.type.is_array and self.in_module_scope: |
+ self.temp_code = code.funcstate.allocate_temp( |
+ self.type, manage_ref=False, static=True) |
+ else: |
+ SequenceNode.allocate_temp_result(self, code) |
+ |
+ def release_temp_result(self, env): |
+ if self.type.is_array: |
+ # To be valid C++, we must allocate the memory on the stack |
+ # manually and be sure not to reuse it for something else. |
+ pass |
+ else: |
+ SequenceNode.release_temp_result(self, env) |
+ |
+ def calculate_constant_result(self): |
+ if self.mult_factor: |
+ raise ValueError() # may exceed the compile time memory |
+ self.constant_result = [ |
+ arg.constant_result for arg in self.args] |
+ |
+ def compile_time_value(self, denv): |
+ l = self.compile_time_value_list(denv) |
+ if self.mult_factor: |
+ l *= self.mult_factor.compile_time_value(denv) |
+ return l |
+ |
+ def generate_operation_code(self, code): |
+ if self.type.is_pyobject: |
+ for err in self.obj_conversion_errors: |
+ report_error(err) |
+ self.generate_sequence_packing_code(code) |
+ elif self.type.is_array: |
+ for i, arg in enumerate(self.args): |
+ code.putln("%s[%s] = %s;" % ( |
+ self.result(), |
+ i, |
+ arg.result())) |
+ elif self.type.is_struct: |
+ for arg, member in zip(self.args, self.type.scope.var_entries): |
+ code.putln("%s.%s = %s;" % ( |
+ self.result(), |
+ member.cname, |
+ arg.result())) |
+ else: |
+ raise InternalError("List type never specified") |
+ |
+ |
+class ScopedExprNode(ExprNode): |
+ # Abstract base class for ExprNodes that have their own local |
+ # scope, such as generator expressions. |
+ # |
+ # expr_scope Scope the inner scope of the expression |
+ |
+ subexprs = [] |
+ expr_scope = None |
+ |
+ # does this node really have a local scope, e.g. does it leak loop |
+ # variables or not? non-leaking Py3 behaviour is default, except |
+ # for list comprehensions where the behaviour differs in Py2 and |
+ # Py3 (set in Parsing.py based on parser context) |
+ has_local_scope = True |
+ |
+ def init_scope(self, outer_scope, expr_scope=None): |
+ if expr_scope is not None: |
+ self.expr_scope = expr_scope |
+ elif self.has_local_scope: |
+ self.expr_scope = Symtab.GeneratorExpressionScope(outer_scope) |
+ else: |
+ self.expr_scope = None |
+ |
+ def analyse_declarations(self, env): |
+ self.init_scope(env) |
+ |
+ def analyse_scoped_declarations(self, env): |
+ # this is called with the expr_scope as env |
+ pass |
+ |
+ def analyse_types(self, env): |
+ # no recursion here, the children will be analysed separately below |
+ return self |
+ |
+ def analyse_scoped_expressions(self, env): |
+ # this is called with the expr_scope as env |
+ return self |
+ |
+ def generate_evaluation_code(self, code): |
+ # set up local variables and free their references on exit |
+ generate_inner_evaluation_code = super(ScopedExprNode, self).generate_evaluation_code |
+ if not self.has_local_scope or not self.expr_scope.var_entries: |
+ # no local variables => delegate, done |
+ generate_inner_evaluation_code(code) |
+ return |
+ |
+ code.putln('{ /* enter inner scope */') |
+ py_entries = [] |
+ for entry in self.expr_scope.var_entries: |
+ if not entry.in_closure: |
+ code.put_var_declaration(entry) |
+ if entry.type.is_pyobject and entry.used: |
+ py_entries.append(entry) |
+ if not py_entries: |
+ # no local Python references => no cleanup required |
+ generate_inner_evaluation_code(code) |
+ code.putln('} /* exit inner scope */') |
+ return |
+ |
+ # must free all local Python references at each exit point |
+ old_loop_labels = tuple(code.new_loop_labels()) |
+ old_error_label = code.new_error_label() |
+ |
+ generate_inner_evaluation_code(code) |
+ |
+ # normal (non-error) exit |
+ for entry in py_entries: |
+ code.put_var_decref(entry) |
+ |
+ # error/loop body exit points |
+ exit_scope = code.new_label('exit_scope') |
+ code.put_goto(exit_scope) |
+ for label, old_label in ([(code.error_label, old_error_label)] + |
+ list(zip(code.get_loop_labels(), old_loop_labels))): |
+ if code.label_used(label): |
+ code.put_label(label) |
+ for entry in py_entries: |
+ code.put_var_decref(entry) |
+ code.put_goto(old_label) |
+ code.put_label(exit_scope) |
+ code.putln('} /* exit inner scope */') |
+ |
+ code.set_loop_labels(old_loop_labels) |
+ code.error_label = old_error_label |
+ |
+ |
+class ComprehensionNode(ScopedExprNode): |
+ # A list/set/dict comprehension |
+ |
+ child_attrs = ["loop"] |
+ |
+ is_temp = True |
+ |
+ def infer_type(self, env): |
+ return self.type |
+ |
+ def analyse_declarations(self, env): |
+ self.append.target = self # this is used in the PyList_Append of the inner loop |
+ self.init_scope(env) |
+ |
+ def analyse_scoped_declarations(self, env): |
+ self.loop.analyse_declarations(env) |
+ |
+ def analyse_types(self, env): |
+ if not self.has_local_scope: |
+ self.loop = self.loop.analyse_expressions(env) |
+ return self |
+ |
+ def analyse_scoped_expressions(self, env): |
+ if self.has_local_scope: |
+ self.loop = self.loop.analyse_expressions(env) |
+ return self |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ def generate_result_code(self, code): |
+ self.generate_operation_code(code) |
+ |
+ def generate_operation_code(self, code): |
+ if self.type is Builtin.list_type: |
+ create_code = 'PyList_New(0)' |
+ elif self.type is Builtin.set_type: |
+ create_code = 'PySet_New(NULL)' |
+ elif self.type is Builtin.dict_type: |
+ create_code = 'PyDict_New()' |
+ else: |
+ raise InternalError("illegal type for comprehension: %s" % self.type) |
+ code.putln('%s = %s; %s' % ( |
+ self.result(), create_code, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ |
+ code.put_gotref(self.result()) |
+ self.loop.generate_execution_code(code) |
+ |
+ def annotate(self, code): |
+ self.loop.annotate(code) |
+ |
+ |
+class ComprehensionAppendNode(Node): |
+ # Need to be careful to avoid infinite recursion: |
+ # target must not be in child_attrs/subexprs |
+ |
+ child_attrs = ['expr'] |
+ target = None |
+ |
+ type = PyrexTypes.c_int_type |
+ |
+ def analyse_expressions(self, env): |
+ self.expr = self.expr.analyse_expressions(env) |
+ if not self.expr.type.is_pyobject: |
+ self.expr = self.expr.coerce_to_pyobject(env) |
+ return self |
+ |
+ def generate_execution_code(self, code): |
+ if self.target.type is list_type: |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("ListCompAppend", "Optimize.c")) |
+ function = "__Pyx_ListComp_Append" |
+ elif self.target.type is set_type: |
+ function = "PySet_Add" |
+ else: |
+ raise InternalError( |
+ "Invalid type for comprehension node: %s" % self.target.type) |
+ |
+ self.expr.generate_evaluation_code(code) |
+ code.putln(code.error_goto_if("%s(%s, (PyObject*)%s)" % ( |
+ function, |
+ self.target.result(), |
+ self.expr.result() |
+ ), self.pos)) |
+ self.expr.generate_disposal_code(code) |
+ self.expr.free_temps(code) |
+ |
+ def generate_function_definitions(self, env, code): |
+ self.expr.generate_function_definitions(env, code) |
+ |
+ def annotate(self, code): |
+ self.expr.annotate(code) |
+ |
+class DictComprehensionAppendNode(ComprehensionAppendNode): |
+ child_attrs = ['key_expr', 'value_expr'] |
+ |
+ def analyse_expressions(self, env): |
+ self.key_expr = self.key_expr.analyse_expressions(env) |
+ if not self.key_expr.type.is_pyobject: |
+ self.key_expr = self.key_expr.coerce_to_pyobject(env) |
+ self.value_expr = self.value_expr.analyse_expressions(env) |
+ if not self.value_expr.type.is_pyobject: |
+ self.value_expr = self.value_expr.coerce_to_pyobject(env) |
+ return self |
+ |
+ def generate_execution_code(self, code): |
+ self.key_expr.generate_evaluation_code(code) |
+ self.value_expr.generate_evaluation_code(code) |
+ code.putln(code.error_goto_if("PyDict_SetItem(%s, (PyObject*)%s, (PyObject*)%s)" % ( |
+ self.target.result(), |
+ self.key_expr.result(), |
+ self.value_expr.result() |
+ ), self.pos)) |
+ self.key_expr.generate_disposal_code(code) |
+ self.key_expr.free_temps(code) |
+ self.value_expr.generate_disposal_code(code) |
+ self.value_expr.free_temps(code) |
+ |
+ def generate_function_definitions(self, env, code): |
+ self.key_expr.generate_function_definitions(env, code) |
+ self.value_expr.generate_function_definitions(env, code) |
+ |
+ def annotate(self, code): |
+ self.key_expr.annotate(code) |
+ self.value_expr.annotate(code) |
+ |
+ |
+class InlinedGeneratorExpressionNode(ScopedExprNode): |
+ # An inlined generator expression for which the result is |
+ # calculated inside of the loop. This will only be created by |
+ # transforms when replacing builtin calls on generator |
+ # expressions. |
+ # |
+ # loop ForStatNode the for-loop, not containing any YieldExprNodes |
+ # result_node ResultRefNode the reference to the result value temp |
+ # orig_func String the name of the builtin function this node replaces |
+ |
+ child_attrs = ["loop"] |
+ loop_analysed = False |
+ type = py_object_type |
+ |
+ def analyse_scoped_declarations(self, env): |
+ self.loop.analyse_declarations(env) |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ def annotate(self, code): |
+ self.loop.annotate(code) |
+ |
+ def infer_type(self, env): |
+ return self.result_node.infer_type(env) |
+ |
+ def analyse_types(self, env): |
+ if not self.has_local_scope: |
+ self.loop_analysed = True |
+ self.loop = self.loop.analyse_expressions(env) |
+ self.type = self.result_node.type |
+ self.is_temp = True |
+ return self |
+ |
+ def analyse_scoped_expressions(self, env): |
+ self.loop_analysed = True |
+ if self.has_local_scope: |
+ self.loop = self.loop.analyse_expressions(env) |
+ return self |
+ |
+ def coerce_to(self, dst_type, env): |
+ if self.orig_func == 'sum' and dst_type.is_numeric and not self.loop_analysed: |
+ # We can optimise by dropping the aggregation variable and |
+ # the add operations into C. This can only be done safely |
+ # before analysing the loop body, after that, the result |
+ # reference type will have infected expressions and |
+ # assignments. |
+ self.result_node.type = self.type = dst_type |
+ return self |
+ return super(InlinedGeneratorExpressionNode, self).coerce_to(dst_type, env) |
+ |
+ def generate_result_code(self, code): |
+ self.result_node.result_code = self.result() |
+ self.loop.generate_execution_code(code) |
+ |
+ |
+class SetNode(ExprNode): |
+ # Set constructor. |
+ |
+ type = set_type |
+ |
+ subexprs = ['args'] |
+ |
+ gil_message = "Constructing Python set" |
+ |
+ def analyse_types(self, env): |
+ for i in range(len(self.args)): |
+ arg = self.args[i] |
+ arg = arg.analyse_types(env) |
+ self.args[i] = arg.coerce_to_pyobject(env) |
+ self.type = set_type |
+ self.is_temp = 1 |
+ return self |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ def calculate_constant_result(self): |
+ self.constant_result = set([ |
+ arg.constant_result for arg in self.args]) |
+ |
+ def compile_time_value(self, denv): |
+ values = [arg.compile_time_value(denv) for arg in self.args] |
+ try: |
+ return set(values) |
+ except Exception, e: |
+ self.compile_time_value_error(e) |
+ |
+ def generate_evaluation_code(self, code): |
+ code.globalstate.use_utility_code(Builtin.py_set_utility_code) |
+ self.allocate_temp_result(code) |
+ code.putln( |
+ "%s = PySet_New(0); %s" % ( |
+ self.result(), |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ for arg in self.args: |
+ arg.generate_evaluation_code(code) |
+ code.put_error_if_neg( |
+ self.pos, |
+ "PySet_Add(%s, %s)" % (self.result(), arg.py_result())) |
+ arg.generate_disposal_code(code) |
+ arg.free_temps(code) |
+ |
+ |
+class DictNode(ExprNode): |
+ # Dictionary constructor. |
+ # |
+ # key_value_pairs [DictItemNode] |
+ # exclude_null_values [boolean] Do not add NULL values to dict |
+ # |
+ # obj_conversion_errors [PyrexError] used internally |
+ |
+ subexprs = ['key_value_pairs'] |
+ is_temp = 1 |
+ exclude_null_values = False |
+ type = dict_type |
+ |
+ obj_conversion_errors = [] |
+ |
+ @classmethod |
+ def from_pairs(cls, pos, pairs): |
+ return cls(pos, key_value_pairs=[ |
+ DictItemNode(pos, key=k, value=v) for k, v in pairs]) |
+ |
+ def calculate_constant_result(self): |
+ self.constant_result = dict([ |
+ item.constant_result for item in self.key_value_pairs]) |
+ |
+ def compile_time_value(self, denv): |
+ pairs = [(item.key.compile_time_value(denv), item.value.compile_time_value(denv)) |
+ for item in self.key_value_pairs] |
+ try: |
+ return dict(pairs) |
+ except Exception, e: |
+ self.compile_time_value_error(e) |
+ |
+ def type_dependencies(self, env): |
+ return () |
+ |
+ def infer_type(self, env): |
+ # TOOD: Infer struct constructors. |
+ return dict_type |
+ |
+ def analyse_types(self, env): |
+ hold_errors() |
+ self.key_value_pairs = [ item.analyse_types(env) |
+ for item in self.key_value_pairs ] |
+ self.obj_conversion_errors = held_errors() |
+ release_errors(ignore=True) |
+ return self |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ def coerce_to(self, dst_type, env): |
+ if dst_type.is_pyobject: |
+ self.release_errors() |
+ if not self.type.subtype_of(dst_type): |
+ error(self.pos, "Cannot interpret dict as type '%s'" % dst_type) |
+ elif dst_type.is_struct_or_union: |
+ self.type = dst_type |
+ if not dst_type.is_struct and len(self.key_value_pairs) != 1: |
+ error(self.pos, "Exactly one field must be specified to convert to union '%s'" % dst_type) |
+ elif dst_type.is_struct and len(self.key_value_pairs) < len(dst_type.scope.var_entries): |
+ warning(self.pos, "Not all members given for struct '%s'" % dst_type, 1) |
+ for item in self.key_value_pairs: |
+ if isinstance(item.key, CoerceToPyTypeNode): |
+ item.key = item.key.arg |
+ if not item.key.is_string_literal: |
+ error(item.key.pos, "Invalid struct field identifier") |
+ item.key = StringNode(item.key.pos, value="<error>") |
+ else: |
+ key = str(item.key.value) # converts string literals to unicode in Py3 |
+ member = dst_type.scope.lookup_here(key) |
+ if not member: |
+ error(item.key.pos, "struct '%s' has no field '%s'" % (dst_type, key)) |
+ else: |
+ value = item.value |
+ if isinstance(value, CoerceToPyTypeNode): |
+ value = value.arg |
+ item.value = value.coerce_to(member.type, env) |
+ else: |
+ self.type = error_type |
+ error(self.pos, "Cannot interpret dict as type '%s'" % dst_type) |
+ return self |
+ |
+ def release_errors(self): |
+ for err in self.obj_conversion_errors: |
+ report_error(err) |
+ self.obj_conversion_errors = [] |
+ |
+ gil_message = "Constructing Python dict" |
+ |
+ def generate_evaluation_code(self, code): |
+ # Custom method used here because key-value |
+ # pairs are evaluated and used one at a time. |
+ code.mark_pos(self.pos) |
+ self.allocate_temp_result(code) |
+ if self.type.is_pyobject: |
+ self.release_errors() |
+ code.putln( |
+ "%s = PyDict_New(); %s" % ( |
+ self.result(), |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ for item in self.key_value_pairs: |
+ item.generate_evaluation_code(code) |
+ if self.type.is_pyobject: |
+ if self.exclude_null_values: |
+ code.putln('if (%s) {' % item.value.py_result()) |
+ code.put_error_if_neg(self.pos, |
+ "PyDict_SetItem(%s, %s, %s)" % ( |
+ self.result(), |
+ item.key.py_result(), |
+ item.value.py_result())) |
+ if self.exclude_null_values: |
+ code.putln('}') |
+ else: |
+ code.putln("%s.%s = %s;" % ( |
+ self.result(), |
+ item.key.value, |
+ item.value.result())) |
+ item.generate_disposal_code(code) |
+ item.free_temps(code) |
+ |
+ def annotate(self, code): |
+ for item in self.key_value_pairs: |
+ item.annotate(code) |
+ |
+class DictItemNode(ExprNode): |
+ # Represents a single item in a DictNode |
+ # |
+ # key ExprNode |
+ # value ExprNode |
+ subexprs = ['key', 'value'] |
+ |
+ nogil_check = None # Parent DictNode takes care of it |
+ |
+ def calculate_constant_result(self): |
+ self.constant_result = ( |
+ self.key.constant_result, self.value.constant_result) |
+ |
+ def analyse_types(self, env): |
+ self.key = self.key.analyse_types(env) |
+ self.value = self.value.analyse_types(env) |
+ self.key = self.key.coerce_to_pyobject(env) |
+ self.value = self.value.coerce_to_pyobject(env) |
+ return self |
+ |
+ def generate_evaluation_code(self, code): |
+ self.key.generate_evaluation_code(code) |
+ self.value.generate_evaluation_code(code) |
+ |
+ def generate_disposal_code(self, code): |
+ self.key.generate_disposal_code(code) |
+ self.value.generate_disposal_code(code) |
+ |
+ def free_temps(self, code): |
+ self.key.free_temps(code) |
+ self.value.free_temps(code) |
+ |
+ def __iter__(self): |
+ return iter([self.key, self.value]) |
+ |
+ |
+class SortedDictKeysNode(ExprNode): |
+ # build sorted list of dict keys, e.g. for dir() |
+ subexprs = ['arg'] |
+ |
+ is_temp = True |
+ |
+ def __init__(self, arg): |
+ ExprNode.__init__(self, arg.pos, arg=arg) |
+ self.type = Builtin.list_type |
+ |
+ def analyse_types(self, env): |
+ arg = self.arg.analyse_types(env) |
+ if arg.type is Builtin.dict_type: |
+ arg = arg.as_none_safe_node( |
+ "'NoneType' object is not iterable") |
+ self.arg = arg |
+ return self |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ def generate_result_code(self, code): |
+ dict_result = self.arg.py_result() |
+ if self.arg.type is Builtin.dict_type: |
+ function = 'PyDict_Keys' |
+ else: |
+ function = 'PyMapping_Keys' |
+ code.putln('%s = %s(%s); %s' % ( |
+ self.result(), function, dict_result, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ code.put_error_if_neg( |
+ self.pos, 'PyList_Sort(%s)' % self.py_result()) |
+ |
+ |
+class ModuleNameMixin(object): |
+ def get_py_mod_name(self, code): |
+ return code.get_py_string_const( |
+ self.module_name, identifier=True) |
+ |
+ def get_py_qualified_name(self, code): |
+ return code.get_py_string_const( |
+ self.qualname, identifier=True) |
+ |
+ |
+class ClassNode(ExprNode, ModuleNameMixin): |
+ # Helper class used in the implementation of Python |
+ # class definitions. Constructs a class object given |
+ # a name, tuple of bases and class dictionary. |
+ # |
+ # name EncodedString Name of the class |
+ # bases ExprNode Base class tuple |
+ # dict ExprNode Class dict (not owned by this node) |
+ # doc ExprNode or None Doc string |
+ # module_name EncodedString Name of defining module |
+ |
+ subexprs = ['bases', 'doc'] |
+ |
+ def analyse_types(self, env): |
+ self.bases = self.bases.analyse_types(env) |
+ if self.doc: |
+ self.doc = self.doc.analyse_types(env) |
+ self.doc = self.doc.coerce_to_pyobject(env) |
+ self.type = py_object_type |
+ self.is_temp = 1 |
+ env.use_utility_code(UtilityCode.load_cached("CreateClass", "ObjectHandling.c")) |
+ return self |
+ |
+ def may_be_none(self): |
+ return True |
+ |
+ gil_message = "Constructing Python class" |
+ |
+ def generate_result_code(self, code): |
+ cname = code.intern_identifier(self.name) |
+ |
+ if self.doc: |
+ code.put_error_if_neg(self.pos, |
+ 'PyDict_SetItem(%s, %s, %s)' % ( |
+ self.dict.py_result(), |
+ code.intern_identifier( |
+ StringEncoding.EncodedString("__doc__")), |
+ self.doc.py_result())) |
+ py_mod_name = self.get_py_mod_name(code) |
+ qualname = self.get_py_qualified_name(code) |
+ code.putln( |
+ '%s = __Pyx_CreateClass(%s, %s, %s, %s, %s); %s' % ( |
+ self.result(), |
+ self.bases.py_result(), |
+ self.dict.py_result(), |
+ cname, |
+ qualname, |
+ py_mod_name, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ |
+ |
+class Py3ClassNode(ExprNode): |
+ # Helper class used in the implementation of Python3+ |
+ # class definitions. Constructs a class object given |
+ # a name, tuple of bases and class dictionary. |
+ # |
+ # name EncodedString Name of the class |
+ # dict ExprNode Class dict (not owned by this node) |
+ # module_name EncodedString Name of defining module |
+ # calculate_metaclass bool should call CalculateMetaclass() |
+ # allow_py2_metaclass bool should look for Py2 metaclass |
+ |
+ subexprs = [] |
+ |
+ def analyse_types(self, env): |
+ self.type = py_object_type |
+ self.is_temp = 1 |
+ return self |
+ |
+ def may_be_none(self): |
+ return True |
+ |
+ gil_message = "Constructing Python class" |
+ |
+ def generate_result_code(self, code): |
+ code.globalstate.use_utility_code(UtilityCode.load_cached("Py3ClassCreate", "ObjectHandling.c")) |
+ cname = code.intern_identifier(self.name) |
+ if self.mkw: |
+ mkw = self.mkw.py_result() |
+ else: |
+ mkw = 'NULL' |
+ if self.metaclass: |
+ metaclass = self.metaclass.result() |
+ else: |
+ metaclass = "((PyObject*)&__Pyx_DefaultClassType)" |
+ code.putln( |
+ '%s = __Pyx_Py3ClassCreate(%s, %s, %s, %s, %s, %d, %d); %s' % ( |
+ self.result(), |
+ metaclass, |
+ cname, |
+ self.bases.py_result(), |
+ self.dict.py_result(), |
+ mkw, |
+ self.calculate_metaclass, |
+ self.allow_py2_metaclass, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ |
+class KeywordArgsNode(ExprNode): |
+ # Helper class for keyword arguments. |
+ # |
+ # starstar_arg DictNode |
+ # keyword_args [DictItemNode] |
+ |
+ subexprs = ['starstar_arg', 'keyword_args'] |
+ is_temp = 1 |
+ type = dict_type |
+ |
+ def calculate_constant_result(self): |
+ result = dict(self.starstar_arg.constant_result) |
+ for item in self.keyword_args: |
+ key, value = item.constant_result |
+ if key in result: |
+ raise ValueError("duplicate keyword argument found: %s" % key) |
+ result[key] = value |
+ self.constant_result = result |
+ |
+ def compile_time_value(self, denv): |
+ result = self.starstar_arg.compile_time_value(denv) |
+ pairs = [ (item.key.compile_time_value(denv), item.value.compile_time_value(denv)) |
+ for item in self.keyword_args ] |
+ try: |
+ result = dict(result) |
+ for key, value in pairs: |
+ if key in result: |
+ raise ValueError("duplicate keyword argument found: %s" % key) |
+ result[key] = value |
+ except Exception, e: |
+ self.compile_time_value_error(e) |
+ return result |
+ |
+ def type_dependencies(self, env): |
+ return () |
+ |
+ def infer_type(self, env): |
+ return dict_type |
+ |
+ def analyse_types(self, env): |
+ arg = self.starstar_arg.analyse_types(env) |
+ arg = arg.coerce_to_pyobject(env) |
+ self.starstar_arg = arg.as_none_safe_node( |
+ # FIXME: CPython's error message starts with the runtime function name |
+ 'argument after ** must be a mapping, not NoneType') |
+ self.keyword_args = [ item.analyse_types(env) |
+ for item in self.keyword_args ] |
+ return self |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ gil_message = "Constructing Python dict" |
+ |
+ def generate_evaluation_code(self, code): |
+ code.mark_pos(self.pos) |
+ self.allocate_temp_result(code) |
+ self.starstar_arg.generate_evaluation_code(code) |
+ if self.starstar_arg.type is not Builtin.dict_type: |
+ # CPython supports calling functions with non-dicts, so do we |
+ code.putln('if (likely(PyDict_Check(%s))) {' % |
+ self.starstar_arg.py_result()) |
+ if self.keyword_args: |
+ code.putln( |
+ "%s = PyDict_Copy(%s); %s" % ( |
+ self.result(), |
+ self.starstar_arg.py_result(), |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ else: |
+ code.putln("%s = %s;" % ( |
+ self.result(), |
+ self.starstar_arg.py_result())) |
+ code.put_incref(self.result(), py_object_type) |
+ if self.starstar_arg.type is not Builtin.dict_type: |
+ code.putln('} else {') |
+ code.putln( |
+ "%s = PyObject_CallFunctionObjArgs(" |
+ "(PyObject*)&PyDict_Type, %s, NULL); %s" % ( |
+ self.result(), |
+ self.starstar_arg.py_result(), |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ code.putln('}') |
+ self.starstar_arg.generate_disposal_code(code) |
+ self.starstar_arg.free_temps(code) |
+ |
+ if not self.keyword_args: |
+ return |
+ |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("RaiseDoubleKeywords", "FunctionArguments.c")) |
+ for item in self.keyword_args: |
+ item.generate_evaluation_code(code) |
+ code.putln("if (unlikely(PyDict_GetItem(%s, %s))) {" % ( |
+ self.result(), |
+ item.key.py_result())) |
+ # FIXME: find out function name at runtime! |
+ code.putln('__Pyx_RaiseDoubleKeywordsError("function", %s); %s' % ( |
+ item.key.py_result(), |
+ code.error_goto(self.pos))) |
+ code.putln("}") |
+ code.put_error_if_neg(self.pos, |
+ "PyDict_SetItem(%s, %s, %s)" % ( |
+ self.result(), |
+ item.key.py_result(), |
+ item.value.py_result())) |
+ item.generate_disposal_code(code) |
+ item.free_temps(code) |
+ |
+ def annotate(self, code): |
+ self.starstar_arg.annotate(code) |
+ for item in self.keyword_args: |
+ item.annotate(code) |
+ |
+class PyClassMetaclassNode(ExprNode): |
+ # Helper class holds Python3 metaclass object |
+ # |
+ # bases ExprNode Base class tuple (not owned by this node) |
+ # mkw ExprNode Class keyword arguments (not owned by this node) |
+ |
+ subexprs = [] |
+ |
+ def analyse_types(self, env): |
+ self.type = py_object_type |
+ self.is_temp = True |
+ return self |
+ |
+ def may_be_none(self): |
+ return True |
+ |
+ def generate_result_code(self, code): |
+ if self.mkw: |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("Py3MetaclassGet", "ObjectHandling.c")) |
+ call = "__Pyx_Py3MetaclassGet(%s, %s)" % ( |
+ self.bases.result(), |
+ self.mkw.result()) |
+ else: |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("CalculateMetaclass", "ObjectHandling.c")) |
+ call = "__Pyx_CalculateMetaclass(NULL, %s)" % ( |
+ self.bases.result()) |
+ code.putln( |
+ "%s = %s; %s" % ( |
+ self.result(), call, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ |
+class PyClassNamespaceNode(ExprNode, ModuleNameMixin): |
+ # Helper class holds Python3 namespace object |
+ # |
+ # All this are not owned by this node |
+ # metaclass ExprNode Metaclass object |
+ # bases ExprNode Base class tuple |
+ # mkw ExprNode Class keyword arguments |
+ # doc ExprNode or None Doc string (owned) |
+ |
+ subexprs = ['doc'] |
+ |
+ def analyse_types(self, env): |
+ if self.doc: |
+ self.doc = self.doc.analyse_types(env) |
+ self.doc = self.doc.coerce_to_pyobject(env) |
+ self.type = py_object_type |
+ self.is_temp = 1 |
+ return self |
+ |
+ def may_be_none(self): |
+ return True |
+ |
+ def generate_result_code(self, code): |
+ cname = code.intern_identifier(self.name) |
+ py_mod_name = self.get_py_mod_name(code) |
+ qualname = self.get_py_qualified_name(code) |
+ if self.doc: |
+ doc_code = self.doc.result() |
+ else: |
+ doc_code = '(PyObject *) NULL' |
+ if self.mkw: |
+ mkw = self.mkw.py_result() |
+ else: |
+ mkw = '(PyObject *) NULL' |
+ if self.metaclass: |
+ metaclass = self.metaclass.result() |
+ else: |
+ metaclass = "(PyObject *) NULL" |
+ code.putln( |
+ "%s = __Pyx_Py3MetaclassPrepare(%s, %s, %s, %s, %s, %s, %s); %s" % ( |
+ self.result(), |
+ metaclass, |
+ self.bases.result(), |
+ cname, |
+ qualname, |
+ mkw, |
+ py_mod_name, |
+ doc_code, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ |
+ |
+class ClassCellInjectorNode(ExprNode): |
+ # Initialize CyFunction.func_classobj |
+ is_temp = True |
+ type = py_object_type |
+ subexprs = [] |
+ is_active = False |
+ |
+ def analyse_expressions(self, env): |
+ if self.is_active: |
+ env.use_utility_code( |
+ UtilityCode.load_cached("CyFunctionClassCell", "CythonFunction.c")) |
+ return self |
+ |
+ def generate_evaluation_code(self, code): |
+ if self.is_active: |
+ self.allocate_temp_result(code) |
+ code.putln( |
+ '%s = PyList_New(0); %s' % ( |
+ self.result(), |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.result()) |
+ |
+ def generate_injection_code(self, code, classobj_cname): |
+ if self.is_active: |
+ code.putln('__Pyx_CyFunction_InitClassCell(%s, %s);' % ( |
+ self.result(), classobj_cname)) |
+ |
+ |
+class ClassCellNode(ExprNode): |
+ # Class Cell for noargs super() |
+ subexprs = [] |
+ is_temp = True |
+ is_generator = False |
+ type = py_object_type |
+ |
+ def analyse_types(self, env): |
+ return self |
+ |
+ def generate_result_code(self, code): |
+ if not self.is_generator: |
+ code.putln('%s = __Pyx_CyFunction_GetClassObj(%s);' % ( |
+ self.result(), |
+ Naming.self_cname)) |
+ else: |
+ code.putln('%s = %s->classobj;' % ( |
+ self.result(), Naming.generator_cname)) |
+ code.putln( |
+ 'if (!%s) { PyErr_SetString(PyExc_SystemError, ' |
+ '"super(): empty __class__ cell"); %s }' % ( |
+ self.result(), |
+ code.error_goto(self.pos))) |
+ code.put_incref(self.result(), py_object_type) |
+ |
+ |
+class BoundMethodNode(ExprNode): |
+ # Helper class used in the implementation of Python |
+ # class definitions. Constructs an bound method |
+ # object from a class and a function. |
+ # |
+ # function ExprNode Function object |
+ # self_object ExprNode self object |
+ |
+ subexprs = ['function'] |
+ |
+ def analyse_types(self, env): |
+ self.function = self.function.analyse_types(env) |
+ self.type = py_object_type |
+ self.is_temp = 1 |
+ return self |
+ |
+ gil_message = "Constructing a bound method" |
+ |
+ def generate_result_code(self, code): |
+ code.putln( |
+ "%s = PyMethod_New(%s, %s, (PyObject*)%s->ob_type); %s" % ( |
+ self.result(), |
+ self.function.py_result(), |
+ self.self_object.py_result(), |
+ self.self_object.py_result(), |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ |
+class UnboundMethodNode(ExprNode): |
+ # Helper class used in the implementation of Python |
+ # class definitions. Constructs an unbound method |
+ # object from a class and a function. |
+ # |
+ # function ExprNode Function object |
+ |
+ type = py_object_type |
+ is_temp = 1 |
+ |
+ subexprs = ['function'] |
+ |
+ def analyse_types(self, env): |
+ self.function = self.function.analyse_types(env) |
+ return self |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ gil_message = "Constructing an unbound method" |
+ |
+ def generate_result_code(self, code): |
+ class_cname = code.pyclass_stack[-1].classobj.result() |
+ code.putln( |
+ "%s = PyMethod_New(%s, 0, %s); %s" % ( |
+ self.result(), |
+ self.function.py_result(), |
+ class_cname, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ |
+ |
+class PyCFunctionNode(ExprNode, ModuleNameMixin): |
+ # Helper class used in the implementation of Python |
+ # functions. Constructs a PyCFunction object |
+ # from a PyMethodDef struct. |
+ # |
+ # pymethdef_cname string PyMethodDef structure |
+ # self_object ExprNode or None |
+ # binding bool |
+ # def_node DefNode the Python function node |
+ # module_name EncodedString Name of defining module |
+ # code_object CodeObjectNode the PyCodeObject creator node |
+ |
+ subexprs = ['code_object', 'defaults_tuple', 'defaults_kwdict', |
+ 'annotations_dict'] |
+ |
+ self_object = None |
+ code_object = None |
+ binding = False |
+ def_node = None |
+ defaults = None |
+ defaults_struct = None |
+ defaults_pyobjects = 0 |
+ defaults_tuple = None |
+ defaults_kwdict = None |
+ annotations_dict = None |
+ |
+ type = py_object_type |
+ is_temp = 1 |
+ |
+ specialized_cpdefs = None |
+ is_specialization = False |
+ |
+ @classmethod |
+ def from_defnode(cls, node, binding): |
+ return cls(node.pos, |
+ def_node=node, |
+ pymethdef_cname=node.entry.pymethdef_cname, |
+ binding=binding or node.specialized_cpdefs, |
+ specialized_cpdefs=node.specialized_cpdefs, |
+ code_object=CodeObjectNode(node)) |
+ |
+ def analyse_types(self, env): |
+ if self.binding: |
+ self.analyse_default_args(env) |
+ return self |
+ |
+ def analyse_default_args(self, env): |
+ """ |
+ Handle non-literal function's default arguments. |
+ """ |
+ nonliteral_objects = [] |
+ nonliteral_other = [] |
+ default_args = [] |
+ default_kwargs = [] |
+ annotations = [] |
+ for arg in self.def_node.args: |
+ if arg.default: |
+ if not arg.default.is_literal: |
+ arg.is_dynamic = True |
+ if arg.type.is_pyobject: |
+ nonliteral_objects.append(arg) |
+ else: |
+ nonliteral_other.append(arg) |
+ else: |
+ arg.default = DefaultLiteralArgNode(arg.pos, arg.default) |
+ if arg.kw_only: |
+ default_kwargs.append(arg) |
+ else: |
+ default_args.append(arg) |
+ if arg.annotation: |
+ arg.annotation = arg.annotation.analyse_types(env) |
+ if not arg.annotation.type.is_pyobject: |
+ arg.annotation = arg.annotation.coerce_to_pyobject(env) |
+ annotations.append((arg.pos, arg.name, arg.annotation)) |
+ if self.def_node.return_type_annotation: |
+ annotations.append((self.def_node.return_type_annotation.pos, |
+ StringEncoding.EncodedString("return"), |
+ self.def_node.return_type_annotation)) |
+ |
+ if nonliteral_objects or nonliteral_other: |
+ module_scope = env.global_scope() |
+ cname = module_scope.next_id(Naming.defaults_struct_prefix) |
+ scope = Symtab.StructOrUnionScope(cname) |
+ self.defaults = [] |
+ for arg in nonliteral_objects: |
+ entry = scope.declare_var(arg.name, arg.type, None, |
+ Naming.arg_prefix + arg.name, |
+ allow_pyobject=True) |
+ self.defaults.append((arg, entry)) |
+ for arg in nonliteral_other: |
+ entry = scope.declare_var(arg.name, arg.type, None, |
+ Naming.arg_prefix + arg.name, |
+ allow_pyobject=False) |
+ self.defaults.append((arg, entry)) |
+ entry = module_scope.declare_struct_or_union( |
+ None, 'struct', scope, 1, None, cname=cname) |
+ self.defaults_struct = scope |
+ self.defaults_pyobjects = len(nonliteral_objects) |
+ for arg, entry in self.defaults: |
+ arg.default_value = '%s->%s' % ( |
+ Naming.dynamic_args_cname, entry.cname) |
+ self.def_node.defaults_struct = self.defaults_struct.name |
+ |
+ if default_args or default_kwargs: |
+ if self.defaults_struct is None: |
+ if default_args: |
+ defaults_tuple = TupleNode(self.pos, args=[ |
+ arg.default for arg in default_args]) |
+ self.defaults_tuple = defaults_tuple.analyse_types(env) |
+ if default_kwargs: |
+ defaults_kwdict = DictNode(self.pos, key_value_pairs=[ |
+ DictItemNode( |
+ arg.pos, |
+ key=IdentifierStringNode(arg.pos, value=arg.name), |
+ value=arg.default) |
+ for arg in default_kwargs]) |
+ self.defaults_kwdict = defaults_kwdict.analyse_types(env) |
+ else: |
+ if default_args: |
+ defaults_tuple = DefaultsTupleNode( |
+ self.pos, default_args, self.defaults_struct) |
+ else: |
+ defaults_tuple = NoneNode(self.pos) |
+ if default_kwargs: |
+ defaults_kwdict = DefaultsKwDictNode( |
+ self.pos, default_kwargs, self.defaults_struct) |
+ else: |
+ defaults_kwdict = NoneNode(self.pos) |
+ |
+ defaults_getter = Nodes.DefNode( |
+ self.pos, args=[], star_arg=None, starstar_arg=None, |
+ body=Nodes.ReturnStatNode( |
+ self.pos, return_type=py_object_type, |
+ value=TupleNode( |
+ self.pos, args=[defaults_tuple, defaults_kwdict])), |
+ decorators=None, |
+ name=StringEncoding.EncodedString("__defaults__")) |
+ defaults_getter.analyse_declarations(env) |
+ defaults_getter = defaults_getter.analyse_expressions(env) |
+ defaults_getter.body = defaults_getter.body.analyse_expressions( |
+ defaults_getter.local_scope) |
+ defaults_getter.py_wrapper_required = False |
+ defaults_getter.pymethdef_required = False |
+ self.def_node.defaults_getter = defaults_getter |
+ if annotations: |
+ annotations_dict = DictNode(self.pos, key_value_pairs=[ |
+ DictItemNode( |
+ pos, key=IdentifierStringNode(pos, value=name), |
+ value=value) |
+ for pos, name, value in annotations]) |
+ self.annotations_dict = annotations_dict.analyse_types(env) |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ gil_message = "Constructing Python function" |
+ |
+ def self_result_code(self): |
+ if self.self_object is None: |
+ self_result = "NULL" |
+ else: |
+ self_result = self.self_object.py_result() |
+ return self_result |
+ |
+ def generate_result_code(self, code): |
+ if self.binding: |
+ self.generate_cyfunction_code(code) |
+ else: |
+ self.generate_pycfunction_code(code) |
+ |
+ def generate_pycfunction_code(self, code): |
+ py_mod_name = self.get_py_mod_name(code) |
+ code.putln( |
+ '%s = PyCFunction_NewEx(&%s, %s, %s); %s' % ( |
+ self.result(), |
+ self.pymethdef_cname, |
+ self.self_result_code(), |
+ py_mod_name, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ |
+ code.put_gotref(self.py_result()) |
+ |
+ def generate_cyfunction_code(self, code): |
+ if self.specialized_cpdefs: |
+ def_node = self.specialized_cpdefs[0] |
+ else: |
+ def_node = self.def_node |
+ |
+ if self.specialized_cpdefs or self.is_specialization: |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("FusedFunction", "CythonFunction.c")) |
+ constructor = "__pyx_FusedFunction_NewEx" |
+ else: |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("CythonFunction", "CythonFunction.c")) |
+ constructor = "__Pyx_CyFunction_NewEx" |
+ |
+ if self.code_object: |
+ code_object_result = self.code_object.py_result() |
+ else: |
+ code_object_result = 'NULL' |
+ |
+ flags = [] |
+ if def_node.is_staticmethod: |
+ flags.append('__Pyx_CYFUNCTION_STATICMETHOD') |
+ elif def_node.is_classmethod: |
+ flags.append('__Pyx_CYFUNCTION_CLASSMETHOD') |
+ |
+ if def_node.local_scope.parent_scope.is_c_class_scope: |
+ flags.append('__Pyx_CYFUNCTION_CCLASS') |
+ |
+ if flags: |
+ flags = ' | '.join(flags) |
+ else: |
+ flags = '0' |
+ |
+ code.putln( |
+ '%s = %s(&%s, %s, %s, %s, %s, %s, %s); %s' % ( |
+ self.result(), |
+ constructor, |
+ self.pymethdef_cname, |
+ flags, |
+ self.get_py_qualified_name(code), |
+ self.self_result_code(), |
+ self.get_py_mod_name(code), |
+ "PyModule_GetDict(%s)" % Naming.module_cname, |
+ code_object_result, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ |
+ code.put_gotref(self.py_result()) |
+ |
+ if def_node.requires_classobj: |
+ assert code.pyclass_stack, "pyclass_stack is empty" |
+ class_node = code.pyclass_stack[-1] |
+ code.put_incref(self.py_result(), py_object_type) |
+ code.putln( |
+ 'PyList_Append(%s, %s);' % ( |
+ class_node.class_cell.result(), |
+ self.result())) |
+ code.put_giveref(self.py_result()) |
+ |
+ if self.defaults: |
+ code.putln( |
+ 'if (!__Pyx_CyFunction_InitDefaults(%s, sizeof(%s), %d)) %s' % ( |
+ self.result(), self.defaults_struct.name, |
+ self.defaults_pyobjects, code.error_goto(self.pos))) |
+ defaults = '__Pyx_CyFunction_Defaults(%s, %s)' % ( |
+ self.defaults_struct.name, self.result()) |
+ for arg, entry in self.defaults: |
+ arg.generate_assignment_code(code, target='%s->%s' % ( |
+ defaults, entry.cname)) |
+ |
+ if self.defaults_tuple: |
+ code.putln('__Pyx_CyFunction_SetDefaultsTuple(%s, %s);' % ( |
+ self.result(), self.defaults_tuple.py_result())) |
+ if self.defaults_kwdict: |
+ code.putln('__Pyx_CyFunction_SetDefaultsKwDict(%s, %s);' % ( |
+ self.result(), self.defaults_kwdict.py_result())) |
+ if def_node.defaults_getter: |
+ code.putln('__Pyx_CyFunction_SetDefaultsGetter(%s, %s);' % ( |
+ self.result(), def_node.defaults_getter.entry.pyfunc_cname)) |
+ if self.annotations_dict: |
+ code.putln('__Pyx_CyFunction_SetAnnotationsDict(%s, %s);' % ( |
+ self.result(), self.annotations_dict.py_result())) |
+ |
+ |
+class InnerFunctionNode(PyCFunctionNode): |
+ # Special PyCFunctionNode that depends on a closure class |
+ # |
+ |
+ binding = True |
+ needs_self_code = True |
+ |
+ def self_result_code(self): |
+ if self.needs_self_code: |
+ return "((PyObject*)%s)" % Naming.cur_scope_cname |
+ return "NULL" |
+ |
+ |
+class CodeObjectNode(ExprNode): |
+ # Create a PyCodeObject for a CyFunction instance. |
+ # |
+ # def_node DefNode the Python function node |
+ # varnames TupleNode a tuple with all local variable names |
+ |
+ subexprs = ['varnames'] |
+ is_temp = False |
+ |
+ def __init__(self, def_node): |
+ ExprNode.__init__(self, def_node.pos, def_node=def_node) |
+ args = list(def_node.args) |
+ # if we have args/kwargs, then the first two in var_entries are those |
+ local_vars = [arg for arg in def_node.local_scope.var_entries if arg.name] |
+ self.varnames = TupleNode( |
+ def_node.pos, |
+ args=[IdentifierStringNode(arg.pos, value=arg.name) |
+ for arg in args + local_vars], |
+ is_temp=0, |
+ is_literal=1) |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ def calculate_result_code(self): |
+ return self.result_code |
+ |
+ def generate_result_code(self, code): |
+ self.result_code = code.get_py_const(py_object_type, 'codeobj', cleanup_level=2) |
+ |
+ code = code.get_cached_constants_writer() |
+ code.mark_pos(self.pos) |
+ func = self.def_node |
+ func_name = code.get_py_string_const( |
+ func.name, identifier=True, is_str=False, unicode_value=func.name) |
+ # FIXME: better way to get the module file path at module init time? Encoding to use? |
+ file_path = StringEncoding.BytesLiteral(func.pos[0].get_filenametable_entry().encode('utf8')) |
+ file_path_const = code.get_py_string_const(file_path, identifier=False, is_str=True) |
+ |
+ flags = [] |
+ if self.def_node.star_arg: |
+ flags.append('CO_VARARGS') |
+ if self.def_node.starstar_arg: |
+ flags.append('CO_VARKEYWORDS') |
+ |
+ code.putln("%s = (PyObject*)__Pyx_PyCode_New(%d, %d, %d, 0, %s, %s, %s, %s, %s, %s, %s, %s, %s, %d, %s); %s" % ( |
+ self.result_code, |
+ len(func.args) - func.num_kwonly_args, # argcount |
+ func.num_kwonly_args, # kwonlyargcount (Py3 only) |
+ len(self.varnames.args), # nlocals |
+ '|'.join(flags) or '0', # flags |
+ Naming.empty_bytes, # code |
+ Naming.empty_tuple, # consts |
+ Naming.empty_tuple, # names (FIXME) |
+ self.varnames.result(), # varnames |
+ Naming.empty_tuple, # freevars (FIXME) |
+ Naming.empty_tuple, # cellvars (FIXME) |
+ file_path_const, # filename |
+ func_name, # name |
+ self.pos[1], # firstlineno |
+ Naming.empty_bytes, # lnotab |
+ code.error_goto_if_null(self.result_code, self.pos), |
+ )) |
+ |
+ |
+class DefaultLiteralArgNode(ExprNode): |
+ # CyFunction's literal argument default value |
+ # |
+ # Evaluate literal only once. |
+ |
+ subexprs = [] |
+ is_literal = True |
+ is_temp = False |
+ |
+ def __init__(self, pos, arg): |
+ super(DefaultLiteralArgNode, self).__init__(pos) |
+ self.arg = arg |
+ self.type = self.arg.type |
+ self.evaluated = False |
+ |
+ def analyse_types(self, env): |
+ return self |
+ |
+ def generate_result_code(self, code): |
+ pass |
+ |
+ def generate_evaluation_code(self, code): |
+ if not self.evaluated: |
+ self.arg.generate_evaluation_code(code) |
+ self.evaluated = True |
+ |
+ def result(self): |
+ return self.type.cast_code(self.arg.result()) |
+ |
+ |
+class DefaultNonLiteralArgNode(ExprNode): |
+ # CyFunction's non-literal argument default value |
+ |
+ subexprs = [] |
+ |
+ def __init__(self, pos, arg, defaults_struct): |
+ super(DefaultNonLiteralArgNode, self).__init__(pos) |
+ self.arg = arg |
+ self.defaults_struct = defaults_struct |
+ |
+ def analyse_types(self, env): |
+ self.type = self.arg.type |
+ self.is_temp = False |
+ return self |
+ |
+ def generate_result_code(self, code): |
+ pass |
+ |
+ def result(self): |
+ return '__Pyx_CyFunction_Defaults(%s, %s)->%s' % ( |
+ self.defaults_struct.name, Naming.self_cname, |
+ self.defaults_struct.lookup(self.arg.name).cname) |
+ |
+ |
+class DefaultsTupleNode(TupleNode): |
+ # CyFunction's __defaults__ tuple |
+ |
+ def __init__(self, pos, defaults, defaults_struct): |
+ args = [] |
+ for arg in defaults: |
+ if not arg.default.is_literal: |
+ arg = DefaultNonLiteralArgNode(pos, arg, defaults_struct) |
+ else: |
+ arg = arg.default |
+ args.append(arg) |
+ super(DefaultsTupleNode, self).__init__(pos, args=args) |
+ |
+ |
+class DefaultsKwDictNode(DictNode): |
+ # CyFunction's __kwdefaults__ dict |
+ |
+ def __init__(self, pos, defaults, defaults_struct): |
+ items = [] |
+ for arg in defaults: |
+ name = IdentifierStringNode(arg.pos, value=arg.name) |
+ if not arg.default.is_literal: |
+ arg = DefaultNonLiteralArgNode(pos, arg, defaults_struct) |
+ else: |
+ arg = arg.default |
+ items.append(DictItemNode(arg.pos, key=name, value=arg)) |
+ super(DefaultsKwDictNode, self).__init__(pos, key_value_pairs=items) |
+ |
+ |
+class LambdaNode(InnerFunctionNode): |
+ # Lambda expression node (only used as a function reference) |
+ # |
+ # args [CArgDeclNode] formal arguments |
+ # star_arg PyArgDeclNode or None * argument |
+ # starstar_arg PyArgDeclNode or None ** argument |
+ # lambda_name string a module-globally unique lambda name |
+ # result_expr ExprNode |
+ # def_node DefNode the underlying function 'def' node |
+ |
+ child_attrs = ['def_node'] |
+ |
+ name = StringEncoding.EncodedString('<lambda>') |
+ |
+ def analyse_declarations(self, env): |
+ self.def_node.no_assignment_synthesis = True |
+ self.def_node.pymethdef_required = True |
+ self.def_node.analyse_declarations(env) |
+ self.def_node.is_cyfunction = True |
+ self.pymethdef_cname = self.def_node.entry.pymethdef_cname |
+ env.add_lambda_def(self.def_node) |
+ |
+ def analyse_types(self, env): |
+ self.def_node = self.def_node.analyse_expressions(env) |
+ return super(LambdaNode, self).analyse_types(env) |
+ |
+ def generate_result_code(self, code): |
+ self.def_node.generate_execution_code(code) |
+ super(LambdaNode, self).generate_result_code(code) |
+ |
+ |
+class GeneratorExpressionNode(LambdaNode): |
+ # A generator expression, e.g. (i for i in range(10)) |
+ # |
+ # Result is a generator. |
+ # |
+ # loop ForStatNode the for-loop, containing a YieldExprNode |
+ # def_node DefNode the underlying generator 'def' node |
+ |
+ name = StringEncoding.EncodedString('genexpr') |
+ binding = False |
+ |
+ def analyse_declarations(self, env): |
+ super(GeneratorExpressionNode, self).analyse_declarations(env) |
+ # No pymethdef required |
+ self.def_node.pymethdef_required = False |
+ self.def_node.py_wrapper_required = False |
+ self.def_node.is_cyfunction = False |
+ # Force genexpr signature |
+ self.def_node.entry.signature = TypeSlots.pyfunction_noargs |
+ |
+ def generate_result_code(self, code): |
+ code.putln( |
+ '%s = %s(%s); %s' % ( |
+ self.result(), |
+ self.def_node.entry.pyfunc_cname, |
+ self.self_result_code(), |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ |
+ |
+class YieldExprNode(ExprNode): |
+ # Yield expression node |
+ # |
+ # arg ExprNode the value to return from the generator |
+ # label_num integer yield label number |
+ # is_yield_from boolean is a YieldFromExprNode to delegate to another generator |
+ |
+ subexprs = ['arg'] |
+ type = py_object_type |
+ label_num = 0 |
+ is_yield_from = False |
+ |
+ def analyse_types(self, env): |
+ if not self.label_num: |
+ error(self.pos, "'yield' not supported here") |
+ self.is_temp = 1 |
+ if self.arg is not None: |
+ self.arg = self.arg.analyse_types(env) |
+ if not self.arg.type.is_pyobject: |
+ self.coerce_yield_argument(env) |
+ return self |
+ |
+ def coerce_yield_argument(self, env): |
+ self.arg = self.arg.coerce_to_pyobject(env) |
+ |
+ def generate_evaluation_code(self, code): |
+ if self.arg: |
+ self.arg.generate_evaluation_code(code) |
+ self.arg.make_owned_reference(code) |
+ code.putln( |
+ "%s = %s;" % ( |
+ Naming.retval_cname, |
+ self.arg.result_as(py_object_type))) |
+ self.arg.generate_post_assignment_code(code) |
+ self.arg.free_temps(code) |
+ else: |
+ code.put_init_to_py_none(Naming.retval_cname, py_object_type) |
+ self.generate_yield_code(code) |
+ |
+ def generate_yield_code(self, code): |
+ """ |
+ Generate the code to return the argument in 'Naming.retval_cname' |
+ and to continue at the yield label. |
+ """ |
+ label_num, label_name = code.new_yield_label() |
+ code.use_label(label_name) |
+ |
+ saved = [] |
+ code.funcstate.closure_temps.reset() |
+ for cname, type, manage_ref in code.funcstate.temps_in_use(): |
+ save_cname = code.funcstate.closure_temps.allocate_temp(type) |
+ saved.append((cname, save_cname, type)) |
+ if type.is_pyobject: |
+ code.put_xgiveref(cname) |
+ code.putln('%s->%s = %s;' % (Naming.cur_scope_cname, save_cname, cname)) |
+ |
+ code.put_xgiveref(Naming.retval_cname) |
+ code.put_finish_refcount_context() |
+ code.putln("/* return from generator, yielding value */") |
+ code.putln("%s->resume_label = %d;" % ( |
+ Naming.generator_cname, label_num)) |
+ code.putln("return %s;" % Naming.retval_cname) |
+ |
+ code.put_label(label_name) |
+ for cname, save_cname, type in saved: |
+ code.putln('%s = %s->%s;' % (cname, Naming.cur_scope_cname, save_cname)) |
+ if type.is_pyobject: |
+ code.putln('%s->%s = 0;' % (Naming.cur_scope_cname, save_cname)) |
+ code.put_xgotref(cname) |
+ code.putln(code.error_goto_if_null(Naming.sent_value_cname, self.pos)) |
+ if self.result_is_used: |
+ self.allocate_temp_result(code) |
+ code.put('%s = %s; ' % (self.result(), Naming.sent_value_cname)) |
+ code.put_incref(self.result(), py_object_type) |
+ |
+ |
+class YieldFromExprNode(YieldExprNode): |
+ # "yield from GEN" expression |
+ is_yield_from = True |
+ |
+ def coerce_yield_argument(self, env): |
+ if not self.arg.type.is_string: |
+ # FIXME: support C arrays and C++ iterators? |
+ error(self.pos, "yielding from non-Python object not supported") |
+ self.arg = self.arg.coerce_to_pyobject(env) |
+ |
+ def generate_evaluation_code(self, code): |
+ code.globalstate.use_utility_code(UtilityCode.load_cached("YieldFrom", "Generator.c")) |
+ |
+ self.arg.generate_evaluation_code(code) |
+ code.putln("%s = __Pyx_Generator_Yield_From(%s, %s);" % ( |
+ Naming.retval_cname, |
+ Naming.generator_cname, |
+ self.arg.result_as(py_object_type))) |
+ self.arg.generate_disposal_code(code) |
+ self.arg.free_temps(code) |
+ code.put_xgotref(Naming.retval_cname) |
+ |
+ code.putln("if (likely(%s)) {" % Naming.retval_cname) |
+ self.generate_yield_code(code) |
+ code.putln("} else {") |
+ # either error or sub-generator has normally terminated: return value => node result |
+ if self.result_is_used: |
+ # YieldExprNode has allocated the result temp for us |
+ code.putln("%s = NULL;" % self.result()) |
+ code.putln("if (unlikely(__Pyx_PyGen_FetchStopIterationValue(&%s) < 0)) %s" % ( |
+ self.result(), |
+ code.error_goto(self.pos))) |
+ code.put_gotref(self.result()) |
+ else: |
+ code.putln("PyObject* exc_type = PyErr_Occurred();") |
+ code.putln("if (exc_type) {") |
+ code.putln("if (likely(exc_type == PyExc_StopIteration ||" |
+ " PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear();") |
+ code.putln("else %s" % code.error_goto(self.pos)) |
+ code.putln("}") |
+ code.putln("}") |
+ |
+class GlobalsExprNode(AtomicExprNode): |
+ type = dict_type |
+ is_temp = 1 |
+ |
+ def analyse_types(self, env): |
+ env.use_utility_code(Builtin.globals_utility_code) |
+ return self |
+ |
+ gil_message = "Constructing globals dict" |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ def generate_result_code(self, code): |
+ code.putln('%s = __Pyx_Globals(); %s' % ( |
+ self.result(), |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.result()) |
+ |
+ |
+class LocalsDictItemNode(DictItemNode): |
+ def analyse_types(self, env): |
+ self.key = self.key.analyse_types(env) |
+ self.value = self.value.analyse_types(env) |
+ self.key = self.key.coerce_to_pyobject(env) |
+ if self.value.type.can_coerce_to_pyobject(env): |
+ self.value = self.value.coerce_to_pyobject(env) |
+ else: |
+ self.value = None |
+ return self |
+ |
+ |
+class FuncLocalsExprNode(DictNode): |
+ def __init__(self, pos, env): |
+ local_vars = sorted([ |
+ entry.name for entry in env.entries.values() if entry.name]) |
+ items = [LocalsDictItemNode( |
+ pos, key=IdentifierStringNode(pos, value=var), |
+ value=NameNode(pos, name=var, allow_null=True)) |
+ for var in local_vars] |
+ DictNode.__init__(self, pos, key_value_pairs=items, |
+ exclude_null_values=True) |
+ |
+ def analyse_types(self, env): |
+ node = super(FuncLocalsExprNode, self).analyse_types(env) |
+ node.key_value_pairs = [ i for i in node.key_value_pairs |
+ if i.value is not None ] |
+ return node |
+ |
+ |
+class PyClassLocalsExprNode(AtomicExprNode): |
+ def __init__(self, pos, pyclass_dict): |
+ AtomicExprNode.__init__(self, pos) |
+ self.pyclass_dict = pyclass_dict |
+ |
+ def analyse_types(self, env): |
+ self.type = self.pyclass_dict.type |
+ self.is_temp = False |
+ return self |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ def result(self): |
+ return self.pyclass_dict.result() |
+ |
+ def generate_result_code(self, code): |
+ pass |
+ |
+ |
+def LocalsExprNode(pos, scope_node, env): |
+ if env.is_module_scope: |
+ return GlobalsExprNode(pos) |
+ if env.is_py_class_scope: |
+ return PyClassLocalsExprNode(pos, scope_node.dict) |
+ return FuncLocalsExprNode(pos, env) |
+ |
+ |
+#------------------------------------------------------------------- |
+# |
+# Unary operator nodes |
+# |
+#------------------------------------------------------------------- |
+ |
+compile_time_unary_operators = { |
+ 'not': operator.not_, |
+ '~': operator.inv, |
+ '-': operator.neg, |
+ '+': operator.pos, |
+} |
+ |
+class UnopNode(ExprNode): |
+ # operator string |
+ # operand ExprNode |
+ # |
+ # Processing during analyse_expressions phase: |
+ # |
+ # analyse_c_operation |
+ # Called when the operand is not a pyobject. |
+ # - Check operand type and coerce if needed. |
+ # - Determine result type and result code fragment. |
+ # - Allocate temporary for result if needed. |
+ |
+ subexprs = ['operand'] |
+ infix = True |
+ |
+ def calculate_constant_result(self): |
+ func = compile_time_unary_operators[self.operator] |
+ self.constant_result = func(self.operand.constant_result) |
+ |
+ def compile_time_value(self, denv): |
+ func = compile_time_unary_operators.get(self.operator) |
+ if not func: |
+ error(self.pos, |
+ "Unary '%s' not supported in compile-time expression" |
+ % self.operator) |
+ operand = self.operand.compile_time_value(denv) |
+ try: |
+ return func(operand) |
+ except Exception, e: |
+ self.compile_time_value_error(e) |
+ |
+ def infer_type(self, env): |
+ operand_type = self.operand.infer_type(env) |
+ if operand_type.is_cpp_class or operand_type.is_ptr: |
+ cpp_type = operand_type.find_cpp_operation_type(self.operator) |
+ if cpp_type is not None: |
+ return cpp_type |
+ return self.infer_unop_type(env, operand_type) |
+ |
+ def infer_unop_type(self, env, operand_type): |
+ if operand_type.is_pyobject: |
+ return py_object_type |
+ else: |
+ return operand_type |
+ |
+ def may_be_none(self): |
+ if self.operand.type and self.operand.type.is_builtin_type: |
+ if self.operand.type is not type_type: |
+ return False |
+ return ExprNode.may_be_none(self) |
+ |
+ def analyse_types(self, env): |
+ self.operand = self.operand.analyse_types(env) |
+ if self.is_py_operation(): |
+ self.coerce_operand_to_pyobject(env) |
+ self.type = py_object_type |
+ self.is_temp = 1 |
+ elif self.is_cpp_operation(): |
+ self.analyse_cpp_operation(env) |
+ else: |
+ self.analyse_c_operation(env) |
+ return self |
+ |
+ def check_const(self): |
+ return self.operand.check_const() |
+ |
+ def is_py_operation(self): |
+ return self.operand.type.is_pyobject |
+ |
+ def nogil_check(self, env): |
+ if self.is_py_operation(): |
+ self.gil_error() |
+ |
+ def is_cpp_operation(self): |
+ type = self.operand.type |
+ return type.is_cpp_class |
+ |
+ def coerce_operand_to_pyobject(self, env): |
+ self.operand = self.operand.coerce_to_pyobject(env) |
+ |
+ def generate_result_code(self, code): |
+ if self.operand.type.is_pyobject: |
+ self.generate_py_operation_code(code) |
+ |
+ def generate_py_operation_code(self, code): |
+ function = self.py_operation_function() |
+ code.putln( |
+ "%s = %s(%s); %s" % ( |
+ self.result(), |
+ function, |
+ self.operand.py_result(), |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ |
+ def type_error(self): |
+ if not self.operand.type.is_error: |
+ error(self.pos, "Invalid operand type for '%s' (%s)" % |
+ (self.operator, self.operand.type)) |
+ self.type = PyrexTypes.error_type |
+ |
+ def analyse_cpp_operation(self, env): |
+ cpp_type = self.operand.type.find_cpp_operation_type(self.operator) |
+ if cpp_type is None: |
+ error(self.pos, "'%s' operator not defined for %s" % ( |
+ self.operator, type)) |
+ self.type_error() |
+ return |
+ self.type = cpp_type |
+ |
+ |
+class NotNode(UnopNode): |
+ # 'not' operator |
+ # |
+ # operand ExprNode |
+ operator = '!' |
+ |
+ type = PyrexTypes.c_bint_type |
+ |
+ def calculate_constant_result(self): |
+ self.constant_result = not self.operand.constant_result |
+ |
+ def compile_time_value(self, denv): |
+ operand = self.operand.compile_time_value(denv) |
+ try: |
+ return not operand |
+ except Exception, e: |
+ self.compile_time_value_error(e) |
+ |
+ def infer_unop_type(self, env, operand_type): |
+ return PyrexTypes.c_bint_type |
+ |
+ def analyse_types(self, env): |
+ self.operand = self.operand.analyse_types(env) |
+ operand_type = self.operand.type |
+ if operand_type.is_cpp_class: |
+ cpp_type = operand_type.find_cpp_operation_type(self.operator) |
+ if not cpp_type: |
+ error(self.pos, "'!' operator not defined for %s" % operand_type) |
+ self.type = PyrexTypes.error_type |
+ return |
+ self.type = cpp_type |
+ else: |
+ self.operand = self.operand.coerce_to_boolean(env) |
+ return self |
+ |
+ def calculate_result_code(self): |
+ return "(!%s)" % self.operand.result() |
+ |
+ def generate_result_code(self, code): |
+ pass |
+ |
+ |
+class UnaryPlusNode(UnopNode): |
+ # unary '+' operator |
+ |
+ operator = '+' |
+ |
+ def analyse_c_operation(self, env): |
+ self.type = PyrexTypes.widest_numeric_type( |
+ self.operand.type, PyrexTypes.c_int_type) |
+ |
+ def py_operation_function(self): |
+ return "PyNumber_Positive" |
+ |
+ def calculate_result_code(self): |
+ if self.is_cpp_operation(): |
+ return "(+%s)" % self.operand.result() |
+ else: |
+ return self.operand.result() |
+ |
+ |
+class UnaryMinusNode(UnopNode): |
+ # unary '-' operator |
+ |
+ operator = '-' |
+ |
+ def analyse_c_operation(self, env): |
+ if self.operand.type.is_numeric: |
+ self.type = PyrexTypes.widest_numeric_type( |
+ self.operand.type, PyrexTypes.c_int_type) |
+ elif self.operand.type.is_enum: |
+ self.type = PyrexTypes.c_int_type |
+ else: |
+ self.type_error() |
+ if self.type.is_complex: |
+ self.infix = False |
+ |
+ def py_operation_function(self): |
+ return "PyNumber_Negative" |
+ |
+ def calculate_result_code(self): |
+ if self.infix: |
+ return "(-%s)" % self.operand.result() |
+ else: |
+ return "%s(%s)" % (self.operand.type.unary_op('-'), self.operand.result()) |
+ |
+ def get_constant_c_result_code(self): |
+ value = self.operand.get_constant_c_result_code() |
+ if value: |
+ return "(-%s)" % value |
+ |
+class TildeNode(UnopNode): |
+ # unary '~' operator |
+ |
+ def analyse_c_operation(self, env): |
+ if self.operand.type.is_int: |
+ self.type = PyrexTypes.widest_numeric_type( |
+ self.operand.type, PyrexTypes.c_int_type) |
+ elif self.operand.type.is_enum: |
+ self.type = PyrexTypes.c_int_type |
+ else: |
+ self.type_error() |
+ |
+ def py_operation_function(self): |
+ return "PyNumber_Invert" |
+ |
+ def calculate_result_code(self): |
+ return "(~%s)" % self.operand.result() |
+ |
+ |
+class CUnopNode(UnopNode): |
+ |
+ def is_py_operation(self): |
+ return False |
+ |
+class DereferenceNode(CUnopNode): |
+ # unary * operator |
+ |
+ operator = '*' |
+ |
+ def infer_unop_type(self, env, operand_type): |
+ if operand_type.is_ptr: |
+ return operand_type.base_type |
+ else: |
+ return PyrexTypes.error_type |
+ |
+ def analyse_c_operation(self, env): |
+ if self.operand.type.is_ptr: |
+ self.type = self.operand.type.base_type |
+ else: |
+ self.type_error() |
+ |
+ def calculate_result_code(self): |
+ return "(*%s)" % self.operand.result() |
+ |
+ |
+class DecrementIncrementNode(CUnopNode): |
+ # unary ++/-- operator |
+ |
+ def analyse_c_operation(self, env): |
+ if self.operand.type.is_numeric: |
+ self.type = PyrexTypes.widest_numeric_type( |
+ self.operand.type, PyrexTypes.c_int_type) |
+ elif self.operand.type.is_ptr: |
+ self.type = self.operand.type |
+ else: |
+ self.type_error() |
+ |
+ def calculate_result_code(self): |
+ if self.is_prefix: |
+ return "(%s%s)" % (self.operator, self.operand.result()) |
+ else: |
+ return "(%s%s)" % (self.operand.result(), self.operator) |
+ |
+def inc_dec_constructor(is_prefix, operator): |
+ return lambda pos, **kwds: DecrementIncrementNode(pos, is_prefix=is_prefix, operator=operator, **kwds) |
+ |
+ |
+class AmpersandNode(CUnopNode): |
+ # The C address-of operator. |
+ # |
+ # operand ExprNode |
+ operator = '&' |
+ |
+ def infer_unop_type(self, env, operand_type): |
+ return PyrexTypes.c_ptr_type(operand_type) |
+ |
+ def analyse_types(self, env): |
+ self.operand = self.operand.analyse_types(env) |
+ argtype = self.operand.type |
+ if argtype.is_cpp_class: |
+ cpp_type = argtype.find_cpp_operation_type(self.operator) |
+ if cpp_type is not None: |
+ self.type = cpp_type |
+ return self |
+ if not (argtype.is_cfunction or argtype.is_reference or self.operand.is_addressable()): |
+ if argtype.is_memoryviewslice: |
+ self.error("Cannot take address of memoryview slice") |
+ else: |
+ self.error("Taking address of non-lvalue") |
+ return self |
+ if argtype.is_pyobject: |
+ self.error("Cannot take address of Python variable") |
+ return self |
+ self.type = PyrexTypes.c_ptr_type(argtype) |
+ return self |
+ |
+ def check_const(self): |
+ return self.operand.check_const_addr() |
+ |
+ def error(self, mess): |
+ error(self.pos, mess) |
+ self.type = PyrexTypes.error_type |
+ self.result_code = "<error>" |
+ |
+ def calculate_result_code(self): |
+ return "(&%s)" % self.operand.result() |
+ |
+ def generate_result_code(self, code): |
+ pass |
+ |
+ |
+unop_node_classes = { |
+ "+": UnaryPlusNode, |
+ "-": UnaryMinusNode, |
+ "~": TildeNode, |
+} |
+ |
+def unop_node(pos, operator, operand): |
+ # Construct unnop node of appropriate class for |
+ # given operator. |
+ if isinstance(operand, IntNode) and operator == '-': |
+ return IntNode(pos = operand.pos, value = str(-Utils.str_to_number(operand.value)), |
+ longness=operand.longness, unsigned=operand.unsigned) |
+ elif isinstance(operand, UnopNode) and operand.operator == operator in '+-': |
+ warning(pos, "Python has no increment/decrement operator: %s%sx == %s(%sx) == x" % ((operator,)*4), 5) |
+ return unop_node_classes[operator](pos, |
+ operator = operator, |
+ operand = operand) |
+ |
+ |
+class TypecastNode(ExprNode): |
+ # C type cast |
+ # |
+ # operand ExprNode |
+ # base_type CBaseTypeNode |
+ # declarator CDeclaratorNode |
+ # typecheck boolean |
+ # |
+ # If used from a transform, one can if wanted specify the attribute |
+ # "type" directly and leave base_type and declarator to None |
+ |
+ subexprs = ['operand'] |
+ base_type = declarator = type = None |
+ |
+ def type_dependencies(self, env): |
+ return () |
+ |
+ def infer_type(self, env): |
+ if self.type is None: |
+ base_type = self.base_type.analyse(env) |
+ _, self.type = self.declarator.analyse(base_type, env) |
+ return self.type |
+ |
+ def analyse_types(self, env): |
+ if self.type is None: |
+ base_type = self.base_type.analyse(env) |
+ _, self.type = self.declarator.analyse(base_type, env) |
+ if self.operand.has_constant_result(): |
+ # Must be done after self.type is resolved. |
+ self.calculate_constant_result() |
+ if self.type.is_cfunction: |
+ error(self.pos, |
+ "Cannot cast to a function type") |
+ self.type = PyrexTypes.error_type |
+ self.operand = self.operand.analyse_types(env) |
+ if self.type is PyrexTypes.c_bint_type: |
+ # short circuit this to a coercion |
+ return self.operand.coerce_to_boolean(env) |
+ to_py = self.type.is_pyobject |
+ from_py = self.operand.type.is_pyobject |
+ if from_py and not to_py and self.operand.is_ephemeral(): |
+ if not self.type.is_numeric and not self.type.is_cpp_class: |
+ error(self.pos, "Casting temporary Python object to non-numeric non-Python type") |
+ if to_py and not from_py: |
+ if self.type is bytes_type and self.operand.type.is_int: |
+ return CoerceIntToBytesNode(self.operand, env) |
+ elif self.operand.type.can_coerce_to_pyobject(env): |
+ self.result_ctype = py_object_type |
+ base_type = self.base_type.analyse(env) |
+ self.operand = self.operand.coerce_to(base_type, env) |
+ else: |
+ if self.operand.type.is_ptr: |
+ if not (self.operand.type.base_type.is_void or self.operand.type.base_type.is_struct): |
+ error(self.pos, "Python objects cannot be cast from pointers of primitive types") |
+ else: |
+ # Should this be an error? |
+ warning(self.pos, "No conversion from %s to %s, python object pointer used." % (self.operand.type, self.type)) |
+ self.operand = self.operand.coerce_to_simple(env) |
+ elif from_py and not to_py: |
+ if self.type.create_from_py_utility_code(env): |
+ self.operand = self.operand.coerce_to(self.type, env) |
+ elif self.type.is_ptr: |
+ if not (self.type.base_type.is_void or self.type.base_type.is_struct): |
+ error(self.pos, "Python objects cannot be cast to pointers of primitive types") |
+ else: |
+ warning(self.pos, "No conversion from %s to %s, python object pointer used." % (self.type, self.operand.type)) |
+ elif from_py and to_py: |
+ if self.typecheck: |
+ self.operand = PyTypeTestNode(self.operand, self.type, env, notnone=True) |
+ elif isinstance(self.operand, SliceIndexNode): |
+ # This cast can influence the created type of string slices. |
+ self.operand = self.operand.coerce_to(self.type, env) |
+ elif self.type.is_complex and self.operand.type.is_complex: |
+ self.operand = self.operand.coerce_to_simple(env) |
+ elif self.operand.type.is_fused: |
+ self.operand = self.operand.coerce_to(self.type, env) |
+ #self.type = self.operand.type |
+ return self |
+ |
+ def is_simple(self): |
+ # either temp or a C cast => no side effects other than the operand's |
+ return self.operand.is_simple() |
+ |
+ def nonlocally_immutable(self): |
+ return self.is_temp or self.operand.nonlocally_immutable() |
+ |
+ def nogil_check(self, env): |
+ if self.type and self.type.is_pyobject and self.is_temp: |
+ self.gil_error() |
+ |
+ def check_const(self): |
+ return self.operand.check_const() |
+ |
+ def calculate_constant_result(self): |
+ self.constant_result = self.calculate_result_code(self.operand.constant_result) |
+ |
+ def calculate_result_code(self, operand_result = None): |
+ if operand_result is None: |
+ operand_result = self.operand.result() |
+ if self.type.is_complex: |
+ operand_result = self.operand.result() |
+ if self.operand.type.is_complex: |
+ real_part = self.type.real_type.cast_code("__Pyx_CREAL(%s)" % operand_result) |
+ imag_part = self.type.real_type.cast_code("__Pyx_CIMAG(%s)" % operand_result) |
+ else: |
+ real_part = self.type.real_type.cast_code(operand_result) |
+ imag_part = "0" |
+ return "%s(%s, %s)" % ( |
+ self.type.from_parts, |
+ real_part, |
+ imag_part) |
+ else: |
+ return self.type.cast_code(operand_result) |
+ |
+ def get_constant_c_result_code(self): |
+ operand_result = self.operand.get_constant_c_result_code() |
+ if operand_result: |
+ return self.type.cast_code(operand_result) |
+ |
+ def result_as(self, type): |
+ if self.type.is_pyobject and not self.is_temp: |
+ # Optimise away some unnecessary casting |
+ return self.operand.result_as(type) |
+ else: |
+ return ExprNode.result_as(self, type) |
+ |
+ def generate_result_code(self, code): |
+ if self.is_temp: |
+ code.putln( |
+ "%s = (PyObject *)%s;" % ( |
+ self.result(), |
+ self.operand.result())) |
+ code.put_incref(self.result(), self.ctype()) |
+ |
+ |
+ERR_START = "Start may not be given" |
+ERR_NOT_STOP = "Stop must be provided to indicate shape" |
+ERR_STEPS = ("Strides may only be given to indicate contiguity. " |
+ "Consider slicing it after conversion") |
+ERR_NOT_POINTER = "Can only create cython.array from pointer or array" |
+ERR_BASE_TYPE = "Pointer base type does not match cython.array base type" |
+ |
+class CythonArrayNode(ExprNode): |
+ """ |
+ Used when a pointer of base_type is cast to a memoryviewslice with that |
+ base type. i.e. |
+ |
+ <int[:M:1, :N]> p |
+ |
+ creates a fortran-contiguous cython.array. |
+ |
+ We leave the type set to object so coercions to object are more efficient |
+ and less work. Acquiring a memoryviewslice from this will be just as |
+ efficient. ExprNode.coerce_to() will do the additional typecheck on |
+ self.compile_time_type |
+ |
+ This also handles <int[:, :]> my_c_array |
+ |
+ |
+ operand ExprNode the thing we're casting |
+ base_type_node MemoryViewSliceTypeNode the cast expression node |
+ """ |
+ |
+ subexprs = ['operand', 'shapes'] |
+ |
+ shapes = None |
+ is_temp = True |
+ mode = "c" |
+ array_dtype = None |
+ |
+ shape_type = PyrexTypes.c_py_ssize_t_type |
+ |
+ def analyse_types(self, env): |
+ import MemoryView |
+ |
+ self.operand = self.operand.analyse_types(env) |
+ if self.array_dtype: |
+ array_dtype = self.array_dtype |
+ else: |
+ array_dtype = self.base_type_node.base_type_node.analyse(env) |
+ axes = self.base_type_node.axes |
+ |
+ MemoryView.validate_memslice_dtype(self.pos, array_dtype) |
+ |
+ self.type = error_type |
+ self.shapes = [] |
+ ndim = len(axes) |
+ |
+ # Base type of the pointer or C array we are converting |
+ base_type = self.operand.type |
+ |
+ if not self.operand.type.is_ptr and not self.operand.type.is_array: |
+ error(self.operand.pos, ERR_NOT_POINTER) |
+ return self |
+ |
+ # Dimension sizes of C array |
+ array_dimension_sizes = [] |
+ if base_type.is_array: |
+ while base_type.is_array: |
+ array_dimension_sizes.append(base_type.size) |
+ base_type = base_type.base_type |
+ elif base_type.is_ptr: |
+ base_type = base_type.base_type |
+ else: |
+ error(self.pos, "unexpected base type %s found" % base_type) |
+ return self |
+ |
+ if not (base_type.same_as(array_dtype) or base_type.is_void): |
+ error(self.operand.pos, ERR_BASE_TYPE) |
+ return self |
+ elif self.operand.type.is_array and len(array_dimension_sizes) != ndim: |
+ error(self.operand.pos, |
+ "Expected %d dimensions, array has %d dimensions" % |
+ (ndim, len(array_dimension_sizes))) |
+ return self |
+ |
+ # Verify the start, stop and step values |
+ # In case of a C array, use the size of C array in each dimension to |
+ # get an automatic cast |
+ for axis_no, axis in enumerate(axes): |
+ if not axis.start.is_none: |
+ error(axis.start.pos, ERR_START) |
+ return self |
+ |
+ if axis.stop.is_none: |
+ if array_dimension_sizes: |
+ dimsize = array_dimension_sizes[axis_no] |
+ axis.stop = IntNode(self.pos, value=str(dimsize), |
+ constant_result=dimsize, |
+ type=PyrexTypes.c_int_type) |
+ else: |
+ error(axis.pos, ERR_NOT_STOP) |
+ return self |
+ |
+ axis.stop = axis.stop.analyse_types(env) |
+ shape = axis.stop.coerce_to(self.shape_type, env) |
+ if not shape.is_literal: |
+ shape.coerce_to_temp(env) |
+ |
+ self.shapes.append(shape) |
+ |
+ first_or_last = axis_no in (0, ndim - 1) |
+ if not axis.step.is_none and first_or_last: |
+ # '1' in the first or last dimension denotes F or C contiguity |
+ axis.step = axis.step.analyse_types(env) |
+ if (not axis.step.type.is_int and axis.step.is_literal and not |
+ axis.step.type.is_error): |
+ error(axis.step.pos, "Expected an integer literal") |
+ return self |
+ |
+ if axis.step.compile_time_value(env) != 1: |
+ error(axis.step.pos, ERR_STEPS) |
+ return self |
+ |
+ if axis_no == 0: |
+ self.mode = "fortran" |
+ |
+ elif not axis.step.is_none and not first_or_last: |
+ # step provided in some other dimension |
+ error(axis.step.pos, ERR_STEPS) |
+ return self |
+ |
+ if not self.operand.is_name: |
+ self.operand = self.operand.coerce_to_temp(env) |
+ |
+ axes = [('direct', 'follow')] * len(axes) |
+ if self.mode == "fortran": |
+ axes[0] = ('direct', 'contig') |
+ else: |
+ axes[-1] = ('direct', 'contig') |
+ |
+ self.coercion_type = PyrexTypes.MemoryViewSliceType(array_dtype, axes) |
+ self.type = self.get_cython_array_type(env) |
+ MemoryView.use_cython_array_utility_code(env) |
+ env.use_utility_code(MemoryView.typeinfo_to_format_code) |
+ return self |
+ |
+ def allocate_temp_result(self, code): |
+ if self.temp_code: |
+ raise RuntimeError("temp allocated mulitple times") |
+ |
+ self.temp_code = code.funcstate.allocate_temp(self.type, True) |
+ |
+ def infer_type(self, env): |
+ return self.get_cython_array_type(env) |
+ |
+ def get_cython_array_type(self, env): |
+ return env.global_scope().context.cython_scope.viewscope.lookup("array").type |
+ |
+ def generate_result_code(self, code): |
+ import Buffer |
+ |
+ shapes = [self.shape_type.cast_code(shape.result()) |
+ for shape in self.shapes] |
+ dtype = self.coercion_type.dtype |
+ |
+ shapes_temp = code.funcstate.allocate_temp(py_object_type, True) |
+ format_temp = code.funcstate.allocate_temp(py_object_type, True) |
+ |
+ itemsize = "sizeof(%s)" % dtype.declaration_code("") |
+ type_info = Buffer.get_type_information_cname(code, dtype) |
+ |
+ if self.operand.type.is_ptr: |
+ code.putln("if (!%s) {" % self.operand.result()) |
+ code.putln( 'PyErr_SetString(PyExc_ValueError,' |
+ '"Cannot create cython.array from NULL pointer");') |
+ code.putln(code.error_goto(self.operand.pos)) |
+ code.putln("}") |
+ |
+ code.putln("%s = __pyx_format_from_typeinfo(&%s);" % |
+ (format_temp, type_info)) |
+ buildvalue_fmt = " __PYX_BUILD_PY_SSIZE_T " * len(shapes) |
+ code.putln('%s = Py_BuildValue((char*) "(" %s ")", %s);' % ( |
+ shapes_temp, buildvalue_fmt, ", ".join(shapes))) |
+ |
+ err = "!%s || !%s || !PyBytes_AsString(%s)" % (format_temp, |
+ shapes_temp, |
+ format_temp) |
+ code.putln(code.error_goto_if(err, self.pos)) |
+ code.put_gotref(format_temp) |
+ code.put_gotref(shapes_temp) |
+ |
+ tup = (self.result(), shapes_temp, itemsize, format_temp, |
+ self.mode, self.operand.result()) |
+ code.putln('%s = __pyx_array_new(' |
+ '%s, %s, PyBytes_AS_STRING(%s), ' |
+ '(char *) "%s", (char *) %s);' % tup) |
+ code.putln(code.error_goto_if_null(self.result(), self.pos)) |
+ code.put_gotref(self.result()) |
+ |
+ def dispose(temp): |
+ code.put_decref_clear(temp, py_object_type) |
+ code.funcstate.release_temp(temp) |
+ |
+ dispose(shapes_temp) |
+ dispose(format_temp) |
+ |
+ @classmethod |
+ def from_carray(cls, src_node, env): |
+ """ |
+ Given a C array type, return a CythonArrayNode |
+ """ |
+ pos = src_node.pos |
+ base_type = src_node.type |
+ |
+ none_node = NoneNode(pos) |
+ axes = [] |
+ |
+ while base_type.is_array: |
+ axes.append(SliceNode(pos, start=none_node, stop=none_node, |
+ step=none_node)) |
+ base_type = base_type.base_type |
+ axes[-1].step = IntNode(pos, value="1", is_c_literal=True) |
+ |
+ memslicenode = Nodes.MemoryViewSliceTypeNode(pos, axes=axes, |
+ base_type_node=base_type) |
+ result = CythonArrayNode(pos, base_type_node=memslicenode, |
+ operand=src_node, array_dtype=base_type) |
+ result = result.analyse_types(env) |
+ return result |
+ |
+class SizeofNode(ExprNode): |
+ # Abstract base class for sizeof(x) expression nodes. |
+ |
+ type = PyrexTypes.c_size_t_type |
+ |
+ def check_const(self): |
+ return True |
+ |
+ def generate_result_code(self, code): |
+ pass |
+ |
+ |
+class SizeofTypeNode(SizeofNode): |
+ # C sizeof function applied to a type |
+ # |
+ # base_type CBaseTypeNode |
+ # declarator CDeclaratorNode |
+ |
+ subexprs = [] |
+ arg_type = None |
+ |
+ def analyse_types(self, env): |
+ # we may have incorrectly interpreted a dotted name as a type rather than an attribute |
+ # this could be better handled by more uniformly treating types as runtime-available objects |
+ if 0 and self.base_type.module_path: |
+ path = self.base_type.module_path |
+ obj = env.lookup(path[0]) |
+ if obj.as_module is None: |
+ operand = NameNode(pos=self.pos, name=path[0]) |
+ for attr in path[1:]: |
+ operand = AttributeNode(pos=self.pos, obj=operand, attribute=attr) |
+ operand = AttributeNode(pos=self.pos, obj=operand, attribute=self.base_type.name) |
+ self.operand = operand |
+ self.__class__ = SizeofVarNode |
+ node = self.analyse_types(env) |
+ return node |
+ if self.arg_type is None: |
+ base_type = self.base_type.analyse(env) |
+ _, arg_type = self.declarator.analyse(base_type, env) |
+ self.arg_type = arg_type |
+ self.check_type() |
+ return self |
+ |
+ def check_type(self): |
+ arg_type = self.arg_type |
+ if arg_type.is_pyobject and not arg_type.is_extension_type: |
+ error(self.pos, "Cannot take sizeof Python object") |
+ elif arg_type.is_void: |
+ error(self.pos, "Cannot take sizeof void") |
+ elif not arg_type.is_complete(): |
+ error(self.pos, "Cannot take sizeof incomplete type '%s'" % arg_type) |
+ |
+ def calculate_result_code(self): |
+ if self.arg_type.is_extension_type: |
+ # the size of the pointer is boring |
+ # we want the size of the actual struct |
+ arg_code = self.arg_type.declaration_code("", deref=1) |
+ else: |
+ arg_code = self.arg_type.declaration_code("") |
+ return "(sizeof(%s))" % arg_code |
+ |
+ |
+class SizeofVarNode(SizeofNode): |
+ # C sizeof function applied to a variable |
+ # |
+ # operand ExprNode |
+ |
+ subexprs = ['operand'] |
+ |
+ def analyse_types(self, env): |
+ # We may actually be looking at a type rather than a variable... |
+ # If we are, traditional analysis would fail... |
+ operand_as_type = self.operand.analyse_as_type(env) |
+ if operand_as_type: |
+ self.arg_type = operand_as_type |
+ if self.arg_type.is_fused: |
+ self.arg_type = self.arg_type.specialize(env.fused_to_specific) |
+ self.__class__ = SizeofTypeNode |
+ self.check_type() |
+ else: |
+ self.operand = self.operand.analyse_types(env) |
+ return self |
+ |
+ def calculate_result_code(self): |
+ return "(sizeof(%s))" % self.operand.result() |
+ |
+ def generate_result_code(self, code): |
+ pass |
+ |
+class TypeofNode(ExprNode): |
+ # Compile-time type of an expression, as a string. |
+ # |
+ # operand ExprNode |
+ # literal StringNode # internal |
+ |
+ literal = None |
+ type = py_object_type |
+ |
+ subexprs = ['literal'] # 'operand' will be ignored after type analysis! |
+ |
+ def analyse_types(self, env): |
+ self.operand = self.operand.analyse_types(env) |
+ value = StringEncoding.EncodedString(str(self.operand.type)) #self.operand.type.typeof_name()) |
+ literal = StringNode(self.pos, value=value) |
+ literal = literal.analyse_types(env) |
+ self.literal = literal.coerce_to_pyobject(env) |
+ return self |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ def generate_evaluation_code(self, code): |
+ self.literal.generate_evaluation_code(code) |
+ |
+ def calculate_result_code(self): |
+ return self.literal.calculate_result_code() |
+ |
+#------------------------------------------------------------------- |
+# |
+# Binary operator nodes |
+# |
+#------------------------------------------------------------------- |
+ |
+compile_time_binary_operators = { |
+ '<': operator.lt, |
+ '<=': operator.le, |
+ '==': operator.eq, |
+ '!=': operator.ne, |
+ '>=': operator.ge, |
+ '>': operator.gt, |
+ 'is': operator.is_, |
+ 'is_not': operator.is_not, |
+ '+': operator.add, |
+ '&': operator.and_, |
+ '/': operator.truediv, |
+ '//': operator.floordiv, |
+ '<<': operator.lshift, |
+ '%': operator.mod, |
+ '*': operator.mul, |
+ '|': operator.or_, |
+ '**': operator.pow, |
+ '>>': operator.rshift, |
+ '-': operator.sub, |
+ '^': operator.xor, |
+ 'in': lambda x, seq: x in seq, |
+ 'not_in': lambda x, seq: x not in seq, |
+} |
+ |
+def get_compile_time_binop(node): |
+ func = compile_time_binary_operators.get(node.operator) |
+ if not func: |
+ error(node.pos, |
+ "Binary '%s' not supported in compile-time expression" |
+ % node.operator) |
+ return func |
+ |
+class BinopNode(ExprNode): |
+ # operator string |
+ # operand1 ExprNode |
+ # operand2 ExprNode |
+ # |
+ # Processing during analyse_expressions phase: |
+ # |
+ # analyse_c_operation |
+ # Called when neither operand is a pyobject. |
+ # - Check operand types and coerce if needed. |
+ # - Determine result type and result code fragment. |
+ # - Allocate temporary for result if needed. |
+ |
+ subexprs = ['operand1', 'operand2'] |
+ inplace = False |
+ |
+ def calculate_constant_result(self): |
+ func = compile_time_binary_operators[self.operator] |
+ self.constant_result = func( |
+ self.operand1.constant_result, |
+ self.operand2.constant_result) |
+ |
+ def compile_time_value(self, denv): |
+ func = get_compile_time_binop(self) |
+ operand1 = self.operand1.compile_time_value(denv) |
+ operand2 = self.operand2.compile_time_value(denv) |
+ try: |
+ return func(operand1, operand2) |
+ except Exception, e: |
+ self.compile_time_value_error(e) |
+ |
+ def infer_type(self, env): |
+ return self.result_type(self.operand1.infer_type(env), |
+ self.operand2.infer_type(env)) |
+ |
+ def analyse_types(self, env): |
+ self.operand1 = self.operand1.analyse_types(env) |
+ self.operand2 = self.operand2.analyse_types(env) |
+ self.analyse_operation(env) |
+ return self |
+ |
+ def analyse_operation(self, env): |
+ if self.is_py_operation(): |
+ self.coerce_operands_to_pyobjects(env) |
+ self.type = self.result_type(self.operand1.type, |
+ self.operand2.type) |
+ assert self.type.is_pyobject |
+ self.is_temp = 1 |
+ elif self.is_cpp_operation(): |
+ self.analyse_cpp_operation(env) |
+ else: |
+ self.analyse_c_operation(env) |
+ |
+ def is_py_operation(self): |
+ return self.is_py_operation_types(self.operand1.type, self.operand2.type) |
+ |
+ def is_py_operation_types(self, type1, type2): |
+ return type1.is_pyobject or type2.is_pyobject |
+ |
+ def is_cpp_operation(self): |
+ return (self.operand1.type.is_cpp_class |
+ or self.operand2.type.is_cpp_class) |
+ |
+ def analyse_cpp_operation(self, env): |
+ entry = env.lookup_operator(self.operator, [self.operand1, self.operand2]) |
+ if not entry: |
+ self.type_error() |
+ return |
+ func_type = entry.type |
+ if func_type.is_ptr: |
+ func_type = func_type.base_type |
+ if len(func_type.args) == 1: |
+ self.operand2 = self.operand2.coerce_to(func_type.args[0].type, env) |
+ else: |
+ self.operand1 = self.operand1.coerce_to(func_type.args[0].type, env) |
+ self.operand2 = self.operand2.coerce_to(func_type.args[1].type, env) |
+ self.type = func_type.return_type |
+ |
+ def result_type(self, type1, type2): |
+ if self.is_py_operation_types(type1, type2): |
+ if type2.is_string: |
+ type2 = Builtin.bytes_type |
+ elif type2.is_pyunicode_ptr: |
+ type2 = Builtin.unicode_type |
+ if type1.is_string: |
+ type1 = Builtin.bytes_type |
+ elif type1.is_pyunicode_ptr: |
+ type1 = Builtin.unicode_type |
+ if type1.is_builtin_type or type2.is_builtin_type: |
+ if type1 is type2 and self.operator in '**%+|&^': |
+ # FIXME: at least these operators should be safe - others? |
+ return type1 |
+ result_type = self.infer_builtin_types_operation(type1, type2) |
+ if result_type is not None: |
+ return result_type |
+ return py_object_type |
+ else: |
+ return self.compute_c_result_type(type1, type2) |
+ |
+ def infer_builtin_types_operation(self, type1, type2): |
+ return None |
+ |
+ def nogil_check(self, env): |
+ if self.is_py_operation(): |
+ self.gil_error() |
+ |
+ def coerce_operands_to_pyobjects(self, env): |
+ self.operand1 = self.operand1.coerce_to_pyobject(env) |
+ self.operand2 = self.operand2.coerce_to_pyobject(env) |
+ |
+ def check_const(self): |
+ return self.operand1.check_const() and self.operand2.check_const() |
+ |
+ def generate_result_code(self, code): |
+ #print "BinopNode.generate_result_code:", self.operand1, self.operand2 ### |
+ if self.operand1.type.is_pyobject: |
+ function = self.py_operation_function() |
+ if self.operator == '**': |
+ extra_args = ", Py_None" |
+ else: |
+ extra_args = "" |
+ code.putln( |
+ "%s = %s(%s, %s%s); %s" % ( |
+ self.result(), |
+ function, |
+ self.operand1.py_result(), |
+ self.operand2.py_result(), |
+ extra_args, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.py_result()) |
+ elif self.is_temp: |
+ code.putln("%s = %s;" % (self.result(), self.calculate_result_code())) |
+ |
+ def type_error(self): |
+ if not (self.operand1.type.is_error |
+ or self.operand2.type.is_error): |
+ error(self.pos, "Invalid operand types for '%s' (%s; %s)" % |
+ (self.operator, self.operand1.type, |
+ self.operand2.type)) |
+ self.type = PyrexTypes.error_type |
+ |
+ |
+class CBinopNode(BinopNode): |
+ |
+ def analyse_types(self, env): |
+ node = BinopNode.analyse_types(self, env) |
+ if node.is_py_operation(): |
+ node.type = PyrexTypes.error_type |
+ return node |
+ |
+ def py_operation_function(self): |
+ return "" |
+ |
+ def calculate_result_code(self): |
+ return "(%s %s %s)" % ( |
+ self.operand1.result(), |
+ self.operator, |
+ self.operand2.result()) |
+ |
+ def compute_c_result_type(self, type1, type2): |
+ cpp_type = None |
+ if type1.is_cpp_class or type1.is_ptr: |
+ cpp_type = type1.find_cpp_operation_type(self.operator, type2) |
+ # FIXME: handle the reversed case? |
+ #if cpp_type is None and (type2.is_cpp_class or type2.is_ptr): |
+ # cpp_type = type2.find_cpp_operation_type(self.operator, type1) |
+ # FIXME: do we need to handle other cases here? |
+ return cpp_type |
+ |
+ |
+def c_binop_constructor(operator): |
+ def make_binop_node(pos, **operands): |
+ return CBinopNode(pos, operator=operator, **operands) |
+ return make_binop_node |
+ |
+class NumBinopNode(BinopNode): |
+ # Binary operation taking numeric arguments. |
+ |
+ infix = True |
+ overflow_check = False |
+ overflow_bit_node = None |
+ |
+ def analyse_c_operation(self, env): |
+ type1 = self.operand1.type |
+ type2 = self.operand2.type |
+ self.type = self.compute_c_result_type(type1, type2) |
+ if not self.type: |
+ self.type_error() |
+ return |
+ if self.type.is_complex: |
+ self.infix = False |
+ if (self.type.is_int |
+ and env.directives['overflowcheck'] |
+ and self.operator in self.overflow_op_names): |
+ if (self.operator in ('+', '*') |
+ and self.operand1.has_constant_result() |
+ and not self.operand2.has_constant_result()): |
+ self.operand1, self.operand2 = self.operand2, self.operand1 |
+ self.overflow_check = True |
+ self.overflow_fold = env.directives['overflowcheck.fold'] |
+ self.func = self.type.overflow_check_binop( |
+ self.overflow_op_names[self.operator], |
+ env, |
+ const_rhs = self.operand2.has_constant_result()) |
+ self.is_temp = True |
+ if not self.infix or (type1.is_numeric and type2.is_numeric): |
+ self.operand1 = self.operand1.coerce_to(self.type, env) |
+ self.operand2 = self.operand2.coerce_to(self.type, env) |
+ |
+ def compute_c_result_type(self, type1, type2): |
+ if self.c_types_okay(type1, type2): |
+ widest_type = PyrexTypes.widest_numeric_type(type1, type2) |
+ if widest_type is PyrexTypes.c_bint_type: |
+ if self.operator not in '|^&': |
+ # False + False == 0 # not False! |
+ widest_type = PyrexTypes.c_int_type |
+ else: |
+ widest_type = PyrexTypes.widest_numeric_type( |
+ widest_type, PyrexTypes.c_int_type) |
+ return widest_type |
+ else: |
+ return None |
+ |
+ def may_be_none(self): |
+ if self.type and self.type.is_builtin_type: |
+ # if we know the result type, we know the operation, so it can't be None |
+ return False |
+ type1 = self.operand1.type |
+ type2 = self.operand2.type |
+ if type1 and type1.is_builtin_type and type2 and type2.is_builtin_type: |
+ # XXX: I can't think of any case where a binary operation |
+ # on builtin types evaluates to None - add a special case |
+ # here if there is one. |
+ return False |
+ return super(NumBinopNode, self).may_be_none() |
+ |
+ def get_constant_c_result_code(self): |
+ value1 = self.operand1.get_constant_c_result_code() |
+ value2 = self.operand2.get_constant_c_result_code() |
+ if value1 and value2: |
+ return "(%s %s %s)" % (value1, self.operator, value2) |
+ else: |
+ return None |
+ |
+ def c_types_okay(self, type1, type2): |
+ #print "NumBinopNode.c_types_okay:", type1, type2 ### |
+ return (type1.is_numeric or type1.is_enum) \ |
+ and (type2.is_numeric or type2.is_enum) |
+ |
+ def generate_evaluation_code(self, code): |
+ if self.overflow_check: |
+ self.overflow_bit_node = self |
+ self.overflow_bit = code.funcstate.allocate_temp(PyrexTypes.c_int_type, manage_ref=False) |
+ code.putln("%s = 0;" % self.overflow_bit) |
+ super(NumBinopNode, self).generate_evaluation_code(code) |
+ if self.overflow_check: |
+ code.putln("if (unlikely(%s)) {" % self.overflow_bit) |
+ code.putln('PyErr_SetString(PyExc_OverflowError, "value too large");') |
+ code.putln(code.error_goto(self.pos)) |
+ code.putln("}") |
+ code.funcstate.release_temp(self.overflow_bit) |
+ |
+ def calculate_result_code(self): |
+ if self.overflow_bit_node is not None: |
+ return "%s(%s, %s, &%s)" % ( |
+ self.func, |
+ self.operand1.result(), |
+ self.operand2.result(), |
+ self.overflow_bit_node.overflow_bit) |
+ elif self.infix: |
+ return "(%s %s %s)" % ( |
+ self.operand1.result(), |
+ self.operator, |
+ self.operand2.result()) |
+ else: |
+ func = self.type.binary_op(self.operator) |
+ if func is None: |
+ error(self.pos, "binary operator %s not supported for %s" % (self.operator, self.type)) |
+ return "%s(%s, %s)" % ( |
+ func, |
+ self.operand1.result(), |
+ self.operand2.result()) |
+ |
+ def is_py_operation_types(self, type1, type2): |
+ return (type1.is_unicode_char or |
+ type2.is_unicode_char or |
+ BinopNode.is_py_operation_types(self, type1, type2)) |
+ |
+ def py_operation_function(self): |
+ function_name = self.py_functions[self.operator] |
+ if self.inplace: |
+ function_name = function_name.replace('PyNumber_', 'PyNumber_InPlace') |
+ return function_name |
+ |
+ py_functions = { |
+ "|": "PyNumber_Or", |
+ "^": "PyNumber_Xor", |
+ "&": "PyNumber_And", |
+ "<<": "PyNumber_Lshift", |
+ ">>": "PyNumber_Rshift", |
+ "+": "PyNumber_Add", |
+ "-": "PyNumber_Subtract", |
+ "*": "PyNumber_Multiply", |
+ "/": "__Pyx_PyNumber_Divide", |
+ "//": "PyNumber_FloorDivide", |
+ "%": "PyNumber_Remainder", |
+ "**": "PyNumber_Power" |
+ } |
+ |
+ overflow_op_names = { |
+ "+": "add", |
+ "-": "sub", |
+ "*": "mul", |
+ "<<": "lshift", |
+ } |
+ |
+ |
+class IntBinopNode(NumBinopNode): |
+ # Binary operation taking integer arguments. |
+ |
+ def c_types_okay(self, type1, type2): |
+ #print "IntBinopNode.c_types_okay:", type1, type2 ### |
+ return (type1.is_int or type1.is_enum) \ |
+ and (type2.is_int or type2.is_enum) |
+ |
+ |
+class AddNode(NumBinopNode): |
+ # '+' operator. |
+ |
+ def is_py_operation_types(self, type1, type2): |
+ if type1.is_string and type2.is_string or type1.is_pyunicode_ptr and type2.is_pyunicode_ptr: |
+ return 1 |
+ else: |
+ return NumBinopNode.is_py_operation_types(self, type1, type2) |
+ |
+ def infer_builtin_types_operation(self, type1, type2): |
+ # b'abc' + 'abc' raises an exception in Py3, |
+ # so we can safely infer the Py2 type for bytes here |
+ string_types = [bytes_type, str_type, basestring_type, unicode_type] # Py2.4 lacks tuple.index() |
+ if type1 in string_types and type2 in string_types: |
+ return string_types[max(string_types.index(type1), |
+ string_types.index(type2))] |
+ return None |
+ |
+ def compute_c_result_type(self, type1, type2): |
+ #print "AddNode.compute_c_result_type:", type1, self.operator, type2 ### |
+ if (type1.is_ptr or type1.is_array) and (type2.is_int or type2.is_enum): |
+ return type1 |
+ elif (type2.is_ptr or type2.is_array) and (type1.is_int or type1.is_enum): |
+ return type2 |
+ else: |
+ return NumBinopNode.compute_c_result_type( |
+ self, type1, type2) |
+ |
+ def py_operation_function(self): |
+ type1, type2 = self.operand1.type, self.operand2.type |
+ if type1 is unicode_type or type2 is unicode_type: |
+ if type1.is_builtin_type and type2.is_builtin_type: |
+ if self.operand1.may_be_none() or self.operand2.may_be_none(): |
+ return '__Pyx_PyUnicode_ConcatSafe' |
+ else: |
+ return '__Pyx_PyUnicode_Concat' |
+ return super(AddNode, self).py_operation_function() |
+ |
+ |
+class SubNode(NumBinopNode): |
+ # '-' operator. |
+ |
+ def compute_c_result_type(self, type1, type2): |
+ if (type1.is_ptr or type1.is_array) and (type2.is_int or type2.is_enum): |
+ return type1 |
+ elif (type1.is_ptr or type1.is_array) and (type2.is_ptr or type2.is_array): |
+ return PyrexTypes.c_ptrdiff_t_type |
+ else: |
+ return NumBinopNode.compute_c_result_type( |
+ self, type1, type2) |
+ |
+ |
+class MulNode(NumBinopNode): |
+ # '*' operator. |
+ |
+ def is_py_operation_types(self, type1, type2): |
+ if ((type1.is_string and type2.is_int) or |
+ (type2.is_string and type1.is_int)): |
+ return 1 |
+ else: |
+ return NumBinopNode.is_py_operation_types(self, type1, type2) |
+ |
+ def infer_builtin_types_operation(self, type1, type2): |
+ # let's assume that whatever builtin type you multiply a string with |
+ # will either return a string of the same type or fail with an exception |
+ string_types = (bytes_type, str_type, basestring_type, unicode_type) |
+ if type1 in string_types and type2.is_builtin_type: |
+ return type1 |
+ if type2 in string_types and type1.is_builtin_type: |
+ return type2 |
+ # multiplication of containers/numbers with an integer value |
+ # always (?) returns the same type |
+ if type1.is_int: |
+ return type2 |
+ if type2.is_int: |
+ return type1 |
+ return None |
+ |
+ |
+class DivNode(NumBinopNode): |
+ # '/' or '//' operator. |
+ |
+ cdivision = None |
+ truedivision = None # == "unknown" if operator == '/' |
+ ctruedivision = False |
+ cdivision_warnings = False |
+ zerodivision_check = None |
+ |
+ def find_compile_time_binary_operator(self, op1, op2): |
+ func = compile_time_binary_operators[self.operator] |
+ if self.operator == '/' and self.truedivision is None: |
+ # => true div for floats, floor div for integers |
+ if isinstance(op1, (int,long)) and isinstance(op2, (int,long)): |
+ func = compile_time_binary_operators['//'] |
+ return func |
+ |
+ def calculate_constant_result(self): |
+ op1 = self.operand1.constant_result |
+ op2 = self.operand2.constant_result |
+ func = self.find_compile_time_binary_operator(op1, op2) |
+ self.constant_result = func( |
+ self.operand1.constant_result, |
+ self.operand2.constant_result) |
+ |
+ def compile_time_value(self, denv): |
+ operand1 = self.operand1.compile_time_value(denv) |
+ operand2 = self.operand2.compile_time_value(denv) |
+ try: |
+ func = self.find_compile_time_binary_operator( |
+ operand1, operand2) |
+ return func(operand1, operand2) |
+ except Exception, e: |
+ self.compile_time_value_error(e) |
+ |
+ def analyse_operation(self, env): |
+ if self.cdivision or env.directives['cdivision']: |
+ self.ctruedivision = False |
+ else: |
+ self.ctruedivision = self.truedivision |
+ NumBinopNode.analyse_operation(self, env) |
+ if self.is_cpp_operation(): |
+ self.cdivision = True |
+ if not self.type.is_pyobject: |
+ self.zerodivision_check = ( |
+ self.cdivision is None and not env.directives['cdivision'] |
+ and (not self.operand2.has_constant_result() or |
+ self.operand2.constant_result == 0)) |
+ if self.zerodivision_check or env.directives['cdivision_warnings']: |
+ # Need to check ahead of time to warn or raise zero division error |
+ self.operand1 = self.operand1.coerce_to_simple(env) |
+ self.operand2 = self.operand2.coerce_to_simple(env) |
+ |
+ def compute_c_result_type(self, type1, type2): |
+ if self.operator == '/' and self.ctruedivision: |
+ if not type1.is_float and not type2.is_float: |
+ widest_type = PyrexTypes.widest_numeric_type(type1, PyrexTypes.c_double_type) |
+ widest_type = PyrexTypes.widest_numeric_type(type2, widest_type) |
+ return widest_type |
+ return NumBinopNode.compute_c_result_type(self, type1, type2) |
+ |
+ def zero_division_message(self): |
+ if self.type.is_int: |
+ return "integer division or modulo by zero" |
+ else: |
+ return "float division" |
+ |
+ def generate_evaluation_code(self, code): |
+ if not self.type.is_pyobject and not self.type.is_complex: |
+ if self.cdivision is None: |
+ self.cdivision = (code.globalstate.directives['cdivision'] |
+ or not self.type.signed |
+ or self.type.is_float) |
+ if not self.cdivision: |
+ code.globalstate.use_utility_code(div_int_utility_code.specialize(self.type)) |
+ NumBinopNode.generate_evaluation_code(self, code) |
+ self.generate_div_warning_code(code) |
+ |
+ def generate_div_warning_code(self, code): |
+ if not self.type.is_pyobject: |
+ if self.zerodivision_check: |
+ if not self.infix: |
+ zero_test = "%s(%s)" % (self.type.unary_op('zero'), self.operand2.result()) |
+ else: |
+ zero_test = "%s == 0" % self.operand2.result() |
+ code.putln("if (unlikely(%s)) {" % zero_test) |
+ code.put_ensure_gil() |
+ code.putln('PyErr_SetString(PyExc_ZeroDivisionError, "%s");' % self.zero_division_message()) |
+ code.put_release_ensured_gil() |
+ code.putln(code.error_goto(self.pos)) |
+ code.putln("}") |
+ if self.type.is_int and self.type.signed and self.operator != '%': |
+ code.globalstate.use_utility_code(division_overflow_test_code) |
+ if self.operand2.type.signed == 2: |
+ # explicitly signed, no runtime check needed |
+ minus1_check = 'unlikely(%s == -1)' % self.operand2.result() |
+ else: |
+ type_of_op2 = self.operand2.type.declaration_code('') |
+ minus1_check = '(!(((%s)-1) > 0)) && unlikely(%s == (%s)-1)' % ( |
+ type_of_op2, self.operand2.result(), type_of_op2) |
+ code.putln("else if (sizeof(%s) == sizeof(long) && %s " |
+ " && unlikely(UNARY_NEG_WOULD_OVERFLOW(%s))) {" % ( |
+ self.type.declaration_code(''), |
+ minus1_check, |
+ self.operand1.result())) |
+ code.put_ensure_gil() |
+ code.putln('PyErr_SetString(PyExc_OverflowError, "value too large to perform division");') |
+ code.put_release_ensured_gil() |
+ code.putln(code.error_goto(self.pos)) |
+ code.putln("}") |
+ if code.globalstate.directives['cdivision_warnings'] and self.operator != '/': |
+ code.globalstate.use_utility_code(cdivision_warning_utility_code) |
+ code.putln("if (unlikely((%s < 0) ^ (%s < 0))) {" % ( |
+ self.operand1.result(), |
+ self.operand2.result())) |
+ code.put_ensure_gil() |
+ code.putln(code.set_error_info(self.pos, used=True)) |
+ code.putln("if (__Pyx_cdivision_warning(%(FILENAME)s, " |
+ "%(LINENO)s)) {" % { |
+ 'FILENAME': Naming.filename_cname, |
+ 'LINENO': Naming.lineno_cname, |
+ }) |
+ code.put_release_ensured_gil() |
+ code.put_goto(code.error_label) |
+ code.putln("}") |
+ code.put_release_ensured_gil() |
+ code.putln("}") |
+ |
+ def calculate_result_code(self): |
+ if self.type.is_complex: |
+ return NumBinopNode.calculate_result_code(self) |
+ elif self.type.is_float and self.operator == '//': |
+ return "floor(%s / %s)" % ( |
+ self.operand1.result(), |
+ self.operand2.result()) |
+ elif self.truedivision or self.cdivision: |
+ op1 = self.operand1.result() |
+ op2 = self.operand2.result() |
+ if self.truedivision: |
+ if self.type != self.operand1.type: |
+ op1 = self.type.cast_code(op1) |
+ if self.type != self.operand2.type: |
+ op2 = self.type.cast_code(op2) |
+ return "(%s / %s)" % (op1, op2) |
+ else: |
+ return "__Pyx_div_%s(%s, %s)" % ( |
+ self.type.specialization_name(), |
+ self.operand1.result(), |
+ self.operand2.result()) |
+ |
+ |
+class ModNode(DivNode): |
+ # '%' operator. |
+ |
+ def is_py_operation_types(self, type1, type2): |
+ return (type1.is_string |
+ or type2.is_string |
+ or NumBinopNode.is_py_operation_types(self, type1, type2)) |
+ |
+ def infer_builtin_types_operation(self, type1, type2): |
+ # b'%s' % xyz raises an exception in Py3, so it's safe to infer the type for Py2 |
+ if type1 is unicode_type: |
+ # None + xyz may be implemented by RHS |
+ if type2.is_builtin_type or not self.operand1.may_be_none(): |
+ return type1 |
+ elif type1 in (bytes_type, str_type, basestring_type): |
+ if type2 is unicode_type: |
+ return type2 |
+ elif type2.is_numeric: |
+ return type1 |
+ elif type1 is bytes_type and not type2.is_builtin_type: |
+ return None # RHS might implement '% operator differently in Py3 |
+ else: |
+ return basestring_type # either str or unicode, can't tell |
+ return None |
+ |
+ def zero_division_message(self): |
+ if self.type.is_int: |
+ return "integer division or modulo by zero" |
+ else: |
+ return "float divmod()" |
+ |
+ def analyse_operation(self, env): |
+ DivNode.analyse_operation(self, env) |
+ if not self.type.is_pyobject: |
+ if self.cdivision is None: |
+ self.cdivision = env.directives['cdivision'] or not self.type.signed |
+ if not self.cdivision and not self.type.is_int and not self.type.is_float: |
+ error(self.pos, "mod operator not supported for type '%s'" % self.type) |
+ |
+ def generate_evaluation_code(self, code): |
+ if not self.type.is_pyobject and not self.cdivision: |
+ if self.type.is_int: |
+ code.globalstate.use_utility_code( |
+ mod_int_utility_code.specialize(self.type)) |
+ else: # float |
+ code.globalstate.use_utility_code( |
+ mod_float_utility_code.specialize( |
+ self.type, math_h_modifier=self.type.math_h_modifier)) |
+ # note: skipping over DivNode here |
+ NumBinopNode.generate_evaluation_code(self, code) |
+ self.generate_div_warning_code(code) |
+ |
+ def calculate_result_code(self): |
+ if self.cdivision: |
+ if self.type.is_float: |
+ return "fmod%s(%s, %s)" % ( |
+ self.type.math_h_modifier, |
+ self.operand1.result(), |
+ self.operand2.result()) |
+ else: |
+ return "(%s %% %s)" % ( |
+ self.operand1.result(), |
+ self.operand2.result()) |
+ else: |
+ return "__Pyx_mod_%s(%s, %s)" % ( |
+ self.type.specialization_name(), |
+ self.operand1.result(), |
+ self.operand2.result()) |
+ |
+ def py_operation_function(self): |
+ if self.operand1.type is unicode_type: |
+ if self.operand1.may_be_none(): |
+ return '__Pyx_PyUnicode_FormatSafe' |
+ else: |
+ return 'PyUnicode_Format' |
+ elif self.operand1.type is str_type: |
+ if self.operand1.may_be_none(): |
+ return '__Pyx_PyString_FormatSafe' |
+ else: |
+ return '__Pyx_PyString_Format' |
+ return super(ModNode, self).py_operation_function() |
+ |
+ |
+class PowNode(NumBinopNode): |
+ # '**' operator. |
+ |
+ def analyse_c_operation(self, env): |
+ NumBinopNode.analyse_c_operation(self, env) |
+ if self.type.is_complex: |
+ if self.type.real_type.is_float: |
+ self.operand1 = self.operand1.coerce_to(self.type, env) |
+ self.operand2 = self.operand2.coerce_to(self.type, env) |
+ self.pow_func = "__Pyx_c_pow" + self.type.real_type.math_h_modifier |
+ else: |
+ error(self.pos, "complex int powers not supported") |
+ self.pow_func = "<error>" |
+ elif self.type.is_float: |
+ self.pow_func = "pow" + self.type.math_h_modifier |
+ elif self.type.is_int: |
+ self.pow_func = "__Pyx_pow_%s" % self.type.declaration_code('').replace(' ', '_') |
+ env.use_utility_code( |
+ int_pow_utility_code.specialize( |
+ func_name=self.pow_func, |
+ type=self.type.declaration_code(''), |
+ signed=self.type.signed and 1 or 0)) |
+ elif not self.type.is_error: |
+ error(self.pos, "got unexpected types for C power operator: %s, %s" % |
+ (self.operand1.type, self.operand2.type)) |
+ |
+ def calculate_result_code(self): |
+ # Work around MSVC overloading ambiguity. |
+ def typecast(operand): |
+ if self.type == operand.type: |
+ return operand.result() |
+ else: |
+ return self.type.cast_code(operand.result()) |
+ return "%s(%s, %s)" % ( |
+ self.pow_func, |
+ typecast(self.operand1), |
+ typecast(self.operand2)) |
+ |
+ |
+# Note: This class is temporarily "shut down" into an ineffective temp |
+# allocation mode. |
+# |
+# More sophisticated temp reuse was going on before, one could have a |
+# look at adding this again after /all/ classes are converted to the |
+# new temp scheme. (The temp juggling cannot work otherwise). |
+class BoolBinopNode(ExprNode): |
+ # Short-circuiting boolean operation. |
+ # |
+ # operator string |
+ # operand1 ExprNode |
+ # operand2 ExprNode |
+ |
+ subexprs = ['operand1', 'operand2'] |
+ |
+ def infer_type(self, env): |
+ type1 = self.operand1.infer_type(env) |
+ type2 = self.operand2.infer_type(env) |
+ return PyrexTypes.independent_spanning_type(type1, type2) |
+ |
+ def may_be_none(self): |
+ if self.operator == 'or': |
+ return self.operand2.may_be_none() |
+ else: |
+ return self.operand1.may_be_none() or self.operand2.may_be_none() |
+ |
+ def calculate_constant_result(self): |
+ if self.operator == 'and': |
+ self.constant_result = \ |
+ self.operand1.constant_result and \ |
+ self.operand2.constant_result |
+ else: |
+ self.constant_result = \ |
+ self.operand1.constant_result or \ |
+ self.operand2.constant_result |
+ |
+ def compile_time_value(self, denv): |
+ if self.operator == 'and': |
+ return self.operand1.compile_time_value(denv) \ |
+ and self.operand2.compile_time_value(denv) |
+ else: |
+ return self.operand1.compile_time_value(denv) \ |
+ or self.operand2.compile_time_value(denv) |
+ |
+ def coerce_to_boolean(self, env): |
+ return BoolBinopNode( |
+ self.pos, |
+ operator = self.operator, |
+ operand1 = self.operand1.coerce_to_boolean(env), |
+ operand2 = self.operand2.coerce_to_boolean(env), |
+ type = PyrexTypes.c_bint_type, |
+ is_temp = self.is_temp) |
+ |
+ def analyse_types(self, env): |
+ self.operand1 = self.operand1.analyse_types(env) |
+ self.operand2 = self.operand2.analyse_types(env) |
+ self.type = PyrexTypes.independent_spanning_type(self.operand1.type, self.operand2.type) |
+ self.operand1 = self.operand1.coerce_to(self.type, env) |
+ self.operand2 = self.operand2.coerce_to(self.type, env) |
+ |
+ # For what we're about to do, it's vital that |
+ # both operands be temp nodes. |
+ self.operand1 = self.operand1.coerce_to_simple(env) |
+ self.operand2 = self.operand2.coerce_to_simple(env) |
+ self.is_temp = 1 |
+ return self |
+ |
+ gil_message = "Truth-testing Python object" |
+ |
+ def check_const(self): |
+ return self.operand1.check_const() and self.operand2.check_const() |
+ |
+ def generate_evaluation_code(self, code): |
+ code.mark_pos(self.pos) |
+ self.operand1.generate_evaluation_code(code) |
+ test_result, uses_temp = self.generate_operand1_test(code) |
+ if self.operator == 'and': |
+ sense = "" |
+ else: |
+ sense = "!" |
+ code.putln( |
+ "if (%s%s) {" % ( |
+ sense, |
+ test_result)) |
+ if uses_temp: |
+ code.funcstate.release_temp(test_result) |
+ self.operand1.generate_disposal_code(code) |
+ self.operand2.generate_evaluation_code(code) |
+ self.allocate_temp_result(code) |
+ self.operand2.make_owned_reference(code) |
+ code.putln("%s = %s;" % (self.result(), self.operand2.result())) |
+ self.operand2.generate_post_assignment_code(code) |
+ self.operand2.free_temps(code) |
+ code.putln("} else {") |
+ self.operand1.make_owned_reference(code) |
+ code.putln("%s = %s;" % (self.result(), self.operand1.result())) |
+ self.operand1.generate_post_assignment_code(code) |
+ self.operand1.free_temps(code) |
+ code.putln("}") |
+ |
+ def generate_operand1_test(self, code): |
+ # Generate code to test the truth of the first operand. |
+ if self.type.is_pyobject: |
+ test_result = code.funcstate.allocate_temp(PyrexTypes.c_bint_type, |
+ manage_ref=False) |
+ code.putln( |
+ "%s = __Pyx_PyObject_IsTrue(%s); %s" % ( |
+ test_result, |
+ self.operand1.py_result(), |
+ code.error_goto_if_neg(test_result, self.pos))) |
+ else: |
+ test_result = self.operand1.result() |
+ return (test_result, self.type.is_pyobject) |
+ |
+ |
+class CondExprNode(ExprNode): |
+ # Short-circuiting conditional expression. |
+ # |
+ # test ExprNode |
+ # true_val ExprNode |
+ # false_val ExprNode |
+ |
+ true_val = None |
+ false_val = None |
+ |
+ subexprs = ['test', 'true_val', 'false_val'] |
+ |
+ def type_dependencies(self, env): |
+ return self.true_val.type_dependencies(env) + self.false_val.type_dependencies(env) |
+ |
+ def infer_type(self, env): |
+ return PyrexTypes.independent_spanning_type( |
+ self.true_val.infer_type(env), |
+ self.false_val.infer_type(env)) |
+ |
+ def calculate_constant_result(self): |
+ if self.test.constant_result: |
+ self.constant_result = self.true_val.constant_result |
+ else: |
+ self.constant_result = self.false_val.constant_result |
+ |
+ def analyse_types(self, env): |
+ self.test = self.test.analyse_types(env).coerce_to_boolean(env) |
+ self.true_val = self.true_val.analyse_types(env) |
+ self.false_val = self.false_val.analyse_types(env) |
+ self.is_temp = 1 |
+ return self.analyse_result_type(env) |
+ |
+ def analyse_result_type(self, env): |
+ self.type = PyrexTypes.independent_spanning_type( |
+ self.true_val.type, self.false_val.type) |
+ if self.type.is_pyobject: |
+ self.result_ctype = py_object_type |
+ if self.true_val.type.is_pyobject or self.false_val.type.is_pyobject: |
+ self.true_val = self.true_val.coerce_to(self.type, env) |
+ self.false_val = self.false_val.coerce_to(self.type, env) |
+ if self.type == PyrexTypes.error_type: |
+ self.type_error() |
+ return self |
+ |
+ def coerce_to(self, dst_type, env): |
+ self.true_val = self.true_val.coerce_to(dst_type, env) |
+ self.false_val = self.false_val.coerce_to(dst_type, env) |
+ self.result_ctype = None |
+ return self.analyse_result_type(env) |
+ |
+ def type_error(self): |
+ if not (self.true_val.type.is_error or self.false_val.type.is_error): |
+ error(self.pos, "Incompatible types in conditional expression (%s; %s)" % |
+ (self.true_val.type, self.false_val.type)) |
+ self.type = PyrexTypes.error_type |
+ |
+ def check_const(self): |
+ return (self.test.check_const() |
+ and self.true_val.check_const() |
+ and self.false_val.check_const()) |
+ |
+ def generate_evaluation_code(self, code): |
+ # Because subexprs may not be evaluated we can use a more optimal |
+ # subexpr allocation strategy than the default, so override evaluation_code. |
+ |
+ code.mark_pos(self.pos) |
+ self.allocate_temp_result(code) |
+ self.test.generate_evaluation_code(code) |
+ code.putln("if (%s) {" % self.test.result() ) |
+ self.eval_and_get(code, self.true_val) |
+ code.putln("} else {") |
+ self.eval_and_get(code, self.false_val) |
+ code.putln("}") |
+ self.test.generate_disposal_code(code) |
+ self.test.free_temps(code) |
+ |
+ def eval_and_get(self, code, expr): |
+ expr.generate_evaluation_code(code) |
+ expr.make_owned_reference(code) |
+ code.putln('%s = %s;' % (self.result(), expr.result_as(self.ctype()))) |
+ expr.generate_post_assignment_code(code) |
+ expr.free_temps(code) |
+ |
+richcmp_constants = { |
+ "<" : "Py_LT", |
+ "<=": "Py_LE", |
+ "==": "Py_EQ", |
+ "!=": "Py_NE", |
+ "<>": "Py_NE", |
+ ">" : "Py_GT", |
+ ">=": "Py_GE", |
+ # the following are faked by special compare functions |
+ "in" : "Py_EQ", |
+ "not_in": "Py_NE", |
+} |
+ |
+class CmpNode(object): |
+ # Mixin class containing code common to PrimaryCmpNodes |
+ # and CascadedCmpNodes. |
+ |
+ special_bool_cmp_function = None |
+ special_bool_cmp_utility_code = None |
+ |
+ def infer_type(self, env): |
+ # TODO: Actually implement this (after merging with -unstable). |
+ return py_object_type |
+ |
+ def calculate_cascaded_constant_result(self, operand1_result): |
+ func = compile_time_binary_operators[self.operator] |
+ operand2_result = self.operand2.constant_result |
+ if (isinstance(operand1_result, (bytes, unicode)) and |
+ isinstance(operand2_result, (bytes, unicode)) and |
+ type(operand1_result) != type(operand2_result)): |
+ # string comparison of different types isn't portable |
+ return |
+ |
+ if self.operator in ('in', 'not_in'): |
+ if isinstance(self.operand2, (ListNode, TupleNode, SetNode)): |
+ if not self.operand2.args: |
+ self.constant_result = self.operator == 'not_in' |
+ return |
+ elif isinstance(self.operand2, ListNode) and not self.cascade: |
+ # tuples are more efficient to store than lists |
+ self.operand2 = self.operand2.as_tuple() |
+ elif isinstance(self.operand2, DictNode): |
+ if not self.operand2.key_value_pairs: |
+ self.constant_result = self.operator == 'not_in' |
+ return |
+ |
+ self.constant_result = func(operand1_result, operand2_result) |
+ |
+ def cascaded_compile_time_value(self, operand1, denv): |
+ func = get_compile_time_binop(self) |
+ operand2 = self.operand2.compile_time_value(denv) |
+ try: |
+ result = func(operand1, operand2) |
+ except Exception, e: |
+ self.compile_time_value_error(e) |
+ result = None |
+ if result: |
+ cascade = self.cascade |
+ if cascade: |
+ result = result and cascade.cascaded_compile_time_value(operand2, denv) |
+ return result |
+ |
+ def is_cpp_comparison(self): |
+ return self.operand1.type.is_cpp_class or self.operand2.type.is_cpp_class |
+ |
+ def find_common_int_type(self, env, op, operand1, operand2): |
+ # type1 != type2 and at least one of the types is not a C int |
+ type1 = operand1.type |
+ type2 = operand2.type |
+ type1_can_be_int = False |
+ type2_can_be_int = False |
+ |
+ if operand1.is_string_literal and operand1.can_coerce_to_char_literal(): |
+ type1_can_be_int = True |
+ if operand2.is_string_literal and operand2.can_coerce_to_char_literal(): |
+ type2_can_be_int = True |
+ |
+ if type1.is_int: |
+ if type2_can_be_int: |
+ return type1 |
+ elif type2.is_int: |
+ if type1_can_be_int: |
+ return type2 |
+ elif type1_can_be_int: |
+ if type2_can_be_int: |
+ if Builtin.unicode_type in (type1, type2): |
+ return PyrexTypes.c_py_ucs4_type |
+ else: |
+ return PyrexTypes.c_uchar_type |
+ |
+ return None |
+ |
+ def find_common_type(self, env, op, operand1, common_type=None): |
+ operand2 = self.operand2 |
+ type1 = operand1.type |
+ type2 = operand2.type |
+ |
+ new_common_type = None |
+ |
+ # catch general errors |
+ if type1 == str_type and (type2.is_string or type2 in (bytes_type, unicode_type)) or \ |
+ type2 == str_type and (type1.is_string or type1 in (bytes_type, unicode_type)): |
+ error(self.pos, "Comparisons between bytes/unicode and str are not portable to Python 3") |
+ new_common_type = error_type |
+ |
+ # try to use numeric comparisons where possible |
+ elif type1.is_complex or type2.is_complex: |
+ if op not in ('==', '!=') \ |
+ and (type1.is_complex or type1.is_numeric) \ |
+ and (type2.is_complex or type2.is_numeric): |
+ error(self.pos, "complex types are unordered") |
+ new_common_type = error_type |
+ elif type1.is_pyobject: |
+ new_common_type = type1 |
+ elif type2.is_pyobject: |
+ new_common_type = type2 |
+ else: |
+ new_common_type = PyrexTypes.widest_numeric_type(type1, type2) |
+ elif type1.is_numeric and type2.is_numeric: |
+ new_common_type = PyrexTypes.widest_numeric_type(type1, type2) |
+ elif common_type is None or not common_type.is_pyobject: |
+ new_common_type = self.find_common_int_type(env, op, operand1, operand2) |
+ |
+ if new_common_type is None: |
+ # fall back to generic type compatibility tests |
+ if type1 == type2: |
+ new_common_type = type1 |
+ elif type1.is_pyobject or type2.is_pyobject: |
+ if type2.is_numeric or type2.is_string: |
+ if operand2.check_for_coercion_error(type1, env): |
+ new_common_type = error_type |
+ else: |
+ new_common_type = py_object_type |
+ elif type1.is_numeric or type1.is_string: |
+ if operand1.check_for_coercion_error(type2, env): |
+ new_common_type = error_type |
+ else: |
+ new_common_type = py_object_type |
+ elif py_object_type.assignable_from(type1) and py_object_type.assignable_from(type2): |
+ new_common_type = py_object_type |
+ else: |
+ # one Python type and one non-Python type, not assignable |
+ self.invalid_types_error(operand1, op, operand2) |
+ new_common_type = error_type |
+ elif type1.assignable_from(type2): |
+ new_common_type = type1 |
+ elif type2.assignable_from(type1): |
+ new_common_type = type2 |
+ else: |
+ # C types that we couldn't handle up to here are an error |
+ self.invalid_types_error(operand1, op, operand2) |
+ new_common_type = error_type |
+ |
+ if new_common_type.is_string and (isinstance(operand1, BytesNode) or |
+ isinstance(operand2, BytesNode)): |
+ # special case when comparing char* to bytes literal: must |
+ # compare string values! |
+ new_common_type = bytes_type |
+ |
+ # recursively merge types |
+ if common_type is None or new_common_type.is_error: |
+ common_type = new_common_type |
+ else: |
+ # we could do a lot better by splitting the comparison |
+ # into a non-Python part and a Python part, but this is |
+ # safer for now |
+ common_type = PyrexTypes.spanning_type(common_type, new_common_type) |
+ |
+ if self.cascade: |
+ common_type = self.cascade.find_common_type(env, self.operator, operand2, common_type) |
+ |
+ return common_type |
+ |
+ def invalid_types_error(self, operand1, op, operand2): |
+ error(self.pos, "Invalid types for '%s' (%s, %s)" % |
+ (op, operand1.type, operand2.type)) |
+ |
+ def is_python_comparison(self): |
+ return (not self.is_ptr_contains() |
+ and not self.is_c_string_contains() |
+ and (self.has_python_operands() |
+ or (self.cascade and self.cascade.is_python_comparison()) |
+ or self.operator in ('in', 'not_in'))) |
+ |
+ def coerce_operands_to(self, dst_type, env): |
+ operand2 = self.operand2 |
+ if operand2.type != dst_type: |
+ self.operand2 = operand2.coerce_to(dst_type, env) |
+ if self.cascade: |
+ self.cascade.coerce_operands_to(dst_type, env) |
+ |
+ def is_python_result(self): |
+ return ((self.has_python_operands() and |
+ self.special_bool_cmp_function is None and |
+ self.operator not in ('is', 'is_not', 'in', 'not_in') and |
+ not self.is_c_string_contains() and |
+ not self.is_ptr_contains()) |
+ or (self.cascade and self.cascade.is_python_result())) |
+ |
+ def is_c_string_contains(self): |
+ return self.operator in ('in', 'not_in') and \ |
+ ((self.operand1.type.is_int |
+ and (self.operand2.type.is_string or self.operand2.type is bytes_type)) or |
+ (self.operand1.type.is_unicode_char |
+ and self.operand2.type is unicode_type)) |
+ |
+ def is_ptr_contains(self): |
+ if self.operator in ('in', 'not_in'): |
+ container_type = self.operand2.type |
+ return (container_type.is_ptr or container_type.is_array) \ |
+ and not container_type.is_string |
+ |
+ def find_special_bool_compare_function(self, env, operand1, result_is_bool=False): |
+ # note: currently operand1 must get coerced to a Python object if we succeed here! |
+ if self.operator in ('==', '!='): |
+ type1, type2 = operand1.type, self.operand2.type |
+ if result_is_bool or (type1.is_builtin_type and type2.is_builtin_type): |
+ if type1 is Builtin.unicode_type or type2 is Builtin.unicode_type: |
+ self.special_bool_cmp_utility_code = UtilityCode.load_cached("UnicodeEquals", "StringTools.c") |
+ self.special_bool_cmp_function = "__Pyx_PyUnicode_Equals" |
+ return True |
+ elif type1 is Builtin.bytes_type or type2 is Builtin.bytes_type: |
+ self.special_bool_cmp_utility_code = UtilityCode.load_cached("BytesEquals", "StringTools.c") |
+ self.special_bool_cmp_function = "__Pyx_PyBytes_Equals" |
+ return True |
+ elif type1 is Builtin.basestring_type or type2 is Builtin.basestring_type: |
+ self.special_bool_cmp_utility_code = UtilityCode.load_cached("UnicodeEquals", "StringTools.c") |
+ self.special_bool_cmp_function = "__Pyx_PyUnicode_Equals" |
+ return True |
+ elif type1 is Builtin.str_type or type2 is Builtin.str_type: |
+ self.special_bool_cmp_utility_code = UtilityCode.load_cached("StrEquals", "StringTools.c") |
+ self.special_bool_cmp_function = "__Pyx_PyString_Equals" |
+ return True |
+ elif self.operator in ('in', 'not_in'): |
+ if self.operand2.type is Builtin.dict_type: |
+ self.operand2 = self.operand2.as_none_safe_node("'NoneType' object is not iterable") |
+ self.special_bool_cmp_utility_code = UtilityCode.load_cached("PyDictContains", "ObjectHandling.c") |
+ self.special_bool_cmp_function = "__Pyx_PyDict_Contains" |
+ return True |
+ elif self.operand2.type is Builtin.unicode_type: |
+ self.operand2 = self.operand2.as_none_safe_node("'NoneType' object is not iterable") |
+ self.special_bool_cmp_utility_code = UtilityCode.load_cached("PyUnicodeContains", "StringTools.c") |
+ self.special_bool_cmp_function = "__Pyx_PyUnicode_Contains" |
+ return True |
+ else: |
+ if not self.operand2.type.is_pyobject: |
+ self.operand2 = self.operand2.coerce_to_pyobject(env) |
+ self.special_bool_cmp_utility_code = UtilityCode.load_cached("PySequenceContains", "ObjectHandling.c") |
+ self.special_bool_cmp_function = "__Pyx_PySequence_Contains" |
+ return True |
+ return False |
+ |
+ def generate_operation_code(self, code, result_code, |
+ operand1, op , operand2): |
+ if self.type.is_pyobject: |
+ error_clause = code.error_goto_if_null |
+ got_ref = "__Pyx_XGOTREF(%s); " % result_code |
+ if self.special_bool_cmp_function: |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("PyBoolOrNullFromLong", "ObjectHandling.c")) |
+ coerce_result = "__Pyx_PyBoolOrNull_FromLong" |
+ else: |
+ coerce_result = "__Pyx_PyBool_FromLong" |
+ else: |
+ error_clause = code.error_goto_if_neg |
+ got_ref = "" |
+ coerce_result = "" |
+ |
+ if self.special_bool_cmp_function: |
+ if operand1.type.is_pyobject: |
+ result1 = operand1.py_result() |
+ else: |
+ result1 = operand1.result() |
+ if operand2.type.is_pyobject: |
+ result2 = operand2.py_result() |
+ else: |
+ result2 = operand2.result() |
+ if self.special_bool_cmp_utility_code: |
+ code.globalstate.use_utility_code(self.special_bool_cmp_utility_code) |
+ code.putln( |
+ "%s = %s(%s(%s, %s, %s)); %s%s" % ( |
+ result_code, |
+ coerce_result, |
+ self.special_bool_cmp_function, |
+ result1, result2, richcmp_constants[op], |
+ got_ref, |
+ error_clause(result_code, self.pos))) |
+ |
+ elif operand1.type.is_pyobject and op not in ('is', 'is_not'): |
+ assert op not in ('in', 'not_in'), op |
+ code.putln("%s = PyObject_RichCompare(%s, %s, %s); %s%s" % ( |
+ result_code, |
+ operand1.py_result(), |
+ operand2.py_result(), |
+ richcmp_constants[op], |
+ got_ref, |
+ error_clause(result_code, self.pos))) |
+ |
+ elif operand1.type.is_complex: |
+ code.putln("%s = %s(%s%s(%s, %s));" % ( |
+ result_code, |
+ coerce_result, |
+ op == "!=" and "!" or "", |
+ operand1.type.unary_op('eq'), |
+ operand1.result(), |
+ operand2.result())) |
+ |
+ else: |
+ type1 = operand1.type |
+ type2 = operand2.type |
+ if (type1.is_extension_type or type2.is_extension_type) \ |
+ and not type1.same_as(type2): |
+ common_type = py_object_type |
+ elif type1.is_numeric: |
+ common_type = PyrexTypes.widest_numeric_type(type1, type2) |
+ else: |
+ common_type = type1 |
+ code1 = operand1.result_as(common_type) |
+ code2 = operand2.result_as(common_type) |
+ code.putln("%s = %s(%s %s %s);" % ( |
+ result_code, |
+ coerce_result, |
+ code1, |
+ self.c_operator(op), |
+ code2)) |
+ |
+ def c_operator(self, op): |
+ if op == 'is': |
+ return "==" |
+ elif op == 'is_not': |
+ return "!=" |
+ else: |
+ return op |
+ |
+class PrimaryCmpNode(ExprNode, CmpNode): |
+ # Non-cascaded comparison or first comparison of |
+ # a cascaded sequence. |
+ # |
+ # operator string |
+ # operand1 ExprNode |
+ # operand2 ExprNode |
+ # cascade CascadedCmpNode |
+ |
+ # We don't use the subexprs mechanism, because |
+ # things here are too complicated for it to handle. |
+ # Instead, we override all the framework methods |
+ # which use it. |
+ |
+ child_attrs = ['operand1', 'operand2', 'coerced_operand2', 'cascade'] |
+ |
+ cascade = None |
+ coerced_operand2 = None |
+ is_memslice_nonecheck = False |
+ |
+ def infer_type(self, env): |
+ # TODO: Actually implement this (after merging with -unstable). |
+ return py_object_type |
+ |
+ def type_dependencies(self, env): |
+ return () |
+ |
+ def calculate_constant_result(self): |
+ assert not self.cascade |
+ self.calculate_cascaded_constant_result(self.operand1.constant_result) |
+ |
+ def compile_time_value(self, denv): |
+ operand1 = self.operand1.compile_time_value(denv) |
+ return self.cascaded_compile_time_value(operand1, denv) |
+ |
+ def analyse_types(self, env): |
+ self.operand1 = self.operand1.analyse_types(env) |
+ self.operand2 = self.operand2.analyse_types(env) |
+ if self.is_cpp_comparison(): |
+ self.analyse_cpp_comparison(env) |
+ if self.cascade: |
+ error(self.pos, "Cascading comparison not yet supported for cpp types.") |
+ return self |
+ |
+ if self.analyse_memoryviewslice_comparison(env): |
+ return self |
+ |
+ if self.cascade: |
+ self.cascade = self.cascade.analyse_types(env) |
+ |
+ if self.operator in ('in', 'not_in'): |
+ if self.is_c_string_contains(): |
+ self.is_pycmp = False |
+ common_type = None |
+ if self.cascade: |
+ error(self.pos, "Cascading comparison not yet supported for 'int_val in string'.") |
+ return self |
+ if self.operand2.type is unicode_type: |
+ env.use_utility_code(UtilityCode.load_cached("PyUCS4InUnicode", "StringTools.c")) |
+ else: |
+ if self.operand1.type is PyrexTypes.c_uchar_type: |
+ self.operand1 = self.operand1.coerce_to(PyrexTypes.c_char_type, env) |
+ if self.operand2.type is not bytes_type: |
+ self.operand2 = self.operand2.coerce_to(bytes_type, env) |
+ env.use_utility_code(UtilityCode.load_cached("BytesContains", "StringTools.c")) |
+ self.operand2 = self.operand2.as_none_safe_node( |
+ "argument of type 'NoneType' is not iterable") |
+ elif self.is_ptr_contains(): |
+ if self.cascade: |
+ error(self.pos, "Cascading comparison not supported for 'val in sliced pointer'.") |
+ self.type = PyrexTypes.c_bint_type |
+ # Will be transformed by IterationTransform |
+ return self |
+ elif self.find_special_bool_compare_function(env, self.operand1): |
+ if not self.operand1.type.is_pyobject: |
+ self.operand1 = self.operand1.coerce_to_pyobject(env) |
+ common_type = None # if coercion needed, the method call above has already done it |
+ self.is_pycmp = False # result is bint |
+ else: |
+ common_type = py_object_type |
+ self.is_pycmp = True |
+ elif self.find_special_bool_compare_function(env, self.operand1): |
+ if not self.operand1.type.is_pyobject: |
+ self.operand1 = self.operand1.coerce_to_pyobject(env) |
+ common_type = None # if coercion needed, the method call above has already done it |
+ self.is_pycmp = False # result is bint |
+ else: |
+ common_type = self.find_common_type(env, self.operator, self.operand1) |
+ self.is_pycmp = common_type.is_pyobject |
+ |
+ if common_type is not None and not common_type.is_error: |
+ if self.operand1.type != common_type: |
+ self.operand1 = self.operand1.coerce_to(common_type, env) |
+ self.coerce_operands_to(common_type, env) |
+ |
+ if self.cascade: |
+ self.operand2 = self.operand2.coerce_to_simple(env) |
+ self.cascade.coerce_cascaded_operands_to_temp(env) |
+ operand2 = self.cascade.optimise_comparison(self.operand2, env) |
+ if operand2 is not self.operand2: |
+ self.coerced_operand2 = operand2 |
+ if self.is_python_result(): |
+ self.type = PyrexTypes.py_object_type |
+ else: |
+ self.type = PyrexTypes.c_bint_type |
+ cdr = self.cascade |
+ while cdr: |
+ cdr.type = self.type |
+ cdr = cdr.cascade |
+ if self.is_pycmp or self.cascade or self.special_bool_cmp_function: |
+ # 1) owned reference, 2) reused value, 3) potential function error return value |
+ self.is_temp = 1 |
+ return self |
+ |
+ def analyse_cpp_comparison(self, env): |
+ type1 = self.operand1.type |
+ type2 = self.operand2.type |
+ entry = env.lookup_operator(self.operator, [self.operand1, self.operand2]) |
+ if entry is None: |
+ error(self.pos, "Invalid types for '%s' (%s, %s)" % |
+ (self.operator, type1, type2)) |
+ self.type = PyrexTypes.error_type |
+ self.result_code = "<error>" |
+ return |
+ func_type = entry.type |
+ if func_type.is_ptr: |
+ func_type = func_type.base_type |
+ if len(func_type.args) == 1: |
+ self.operand2 = self.operand2.coerce_to(func_type.args[0].type, env) |
+ else: |
+ self.operand1 = self.operand1.coerce_to(func_type.args[0].type, env) |
+ self.operand2 = self.operand2.coerce_to(func_type.args[1].type, env) |
+ self.is_pycmp = False |
+ self.type = func_type.return_type |
+ |
+ def analyse_memoryviewslice_comparison(self, env): |
+ have_none = self.operand1.is_none or self.operand2.is_none |
+ have_slice = (self.operand1.type.is_memoryviewslice or |
+ self.operand2.type.is_memoryviewslice) |
+ ops = ('==', '!=', 'is', 'is_not') |
+ if have_slice and have_none and self.operator in ops: |
+ self.is_pycmp = False |
+ self.type = PyrexTypes.c_bint_type |
+ self.is_memslice_nonecheck = True |
+ return True |
+ |
+ return False |
+ |
+ def coerce_to_boolean(self, env): |
+ if self.is_pycmp: |
+ # coercing to bool => may allow for more efficient comparison code |
+ if self.find_special_bool_compare_function( |
+ env, self.operand1, result_is_bool=True): |
+ self.is_pycmp = False |
+ self.type = PyrexTypes.c_bint_type |
+ self.is_temp = 1 |
+ if self.cascade: |
+ operand2 = self.cascade.optimise_comparison( |
+ self.operand2, env, result_is_bool=True) |
+ if operand2 is not self.operand2: |
+ self.coerced_operand2 = operand2 |
+ return self |
+ # TODO: check if we can optimise parts of the cascade here |
+ return ExprNode.coerce_to_boolean(self, env) |
+ |
+ def has_python_operands(self): |
+ return (self.operand1.type.is_pyobject |
+ or self.operand2.type.is_pyobject) |
+ |
+ def check_const(self): |
+ if self.cascade: |
+ self.not_const() |
+ return False |
+ else: |
+ return self.operand1.check_const() and self.operand2.check_const() |
+ |
+ def calculate_result_code(self): |
+ if self.operand1.type.is_complex: |
+ if self.operator == "!=": |
+ negation = "!" |
+ else: |
+ negation = "" |
+ return "(%s%s(%s, %s))" % ( |
+ negation, |
+ self.operand1.type.binary_op('=='), |
+ self.operand1.result(), |
+ self.operand2.result()) |
+ elif self.is_c_string_contains(): |
+ if self.operand2.type is unicode_type: |
+ method = "__Pyx_UnicodeContainsUCS4" |
+ else: |
+ method = "__Pyx_BytesContains" |
+ if self.operator == "not_in": |
+ negation = "!" |
+ else: |
+ negation = "" |
+ return "(%s%s(%s, %s))" % ( |
+ negation, |
+ method, |
+ self.operand2.result(), |
+ self.operand1.result()) |
+ else: |
+ result1 = self.operand1.result() |
+ result2 = self.operand2.result() |
+ if self.is_memslice_nonecheck: |
+ if self.operand1.type.is_memoryviewslice: |
+ result1 = "((PyObject *) %s.memview)" % result1 |
+ else: |
+ result2 = "((PyObject *) %s.memview)" % result2 |
+ |
+ return "(%s %s %s)" % ( |
+ result1, |
+ self.c_operator(self.operator), |
+ result2) |
+ |
+ def generate_evaluation_code(self, code): |
+ self.operand1.generate_evaluation_code(code) |
+ self.operand2.generate_evaluation_code(code) |
+ if self.is_temp: |
+ self.allocate_temp_result(code) |
+ self.generate_operation_code(code, self.result(), |
+ self.operand1, self.operator, self.operand2) |
+ if self.cascade: |
+ self.cascade.generate_evaluation_code( |
+ code, self.result(), self.coerced_operand2 or self.operand2, |
+ needs_evaluation=self.coerced_operand2 is not None) |
+ self.operand1.generate_disposal_code(code) |
+ self.operand1.free_temps(code) |
+ self.operand2.generate_disposal_code(code) |
+ self.operand2.free_temps(code) |
+ |
+ def generate_subexpr_disposal_code(self, code): |
+ # If this is called, it is a non-cascaded cmp, |
+ # so only need to dispose of the two main operands. |
+ self.operand1.generate_disposal_code(code) |
+ self.operand2.generate_disposal_code(code) |
+ |
+ def free_subexpr_temps(self, code): |
+ # If this is called, it is a non-cascaded cmp, |
+ # so only need to dispose of the two main operands. |
+ self.operand1.free_temps(code) |
+ self.operand2.free_temps(code) |
+ |
+ def annotate(self, code): |
+ self.operand1.annotate(code) |
+ self.operand2.annotate(code) |
+ if self.cascade: |
+ self.cascade.annotate(code) |
+ |
+ |
+class CascadedCmpNode(Node, CmpNode): |
+ # A CascadedCmpNode is not a complete expression node. It |
+ # hangs off the side of another comparison node, shares |
+ # its left operand with that node, and shares its result |
+ # with the PrimaryCmpNode at the head of the chain. |
+ # |
+ # operator string |
+ # operand2 ExprNode |
+ # cascade CascadedCmpNode |
+ |
+ child_attrs = ['operand2', 'coerced_operand2', 'cascade'] |
+ |
+ cascade = None |
+ coerced_operand2 = None |
+ constant_result = constant_value_not_set # FIXME: where to calculate this? |
+ |
+ def infer_type(self, env): |
+ # TODO: Actually implement this (after merging with -unstable). |
+ return py_object_type |
+ |
+ def type_dependencies(self, env): |
+ return () |
+ |
+ def has_constant_result(self): |
+ return self.constant_result is not constant_value_not_set and \ |
+ self.constant_result is not not_a_constant |
+ |
+ def analyse_types(self, env): |
+ self.operand2 = self.operand2.analyse_types(env) |
+ if self.cascade: |
+ self.cascade = self.cascade.analyse_types(env) |
+ return self |
+ |
+ def has_python_operands(self): |
+ return self.operand2.type.is_pyobject |
+ |
+ def optimise_comparison(self, operand1, env, result_is_bool=False): |
+ if self.find_special_bool_compare_function(env, operand1, result_is_bool): |
+ self.is_pycmp = False |
+ self.type = PyrexTypes.c_bint_type |
+ if not operand1.type.is_pyobject: |
+ operand1 = operand1.coerce_to_pyobject(env) |
+ if self.cascade: |
+ operand2 = self.cascade.optimise_comparison(self.operand2, env, result_is_bool) |
+ if operand2 is not self.operand2: |
+ self.coerced_operand2 = operand2 |
+ return operand1 |
+ |
+ def coerce_operands_to_pyobjects(self, env): |
+ self.operand2 = self.operand2.coerce_to_pyobject(env) |
+ if self.operand2.type is dict_type and self.operator in ('in', 'not_in'): |
+ self.operand2 = self.operand2.as_none_safe_node("'NoneType' object is not iterable") |
+ if self.cascade: |
+ self.cascade.coerce_operands_to_pyobjects(env) |
+ |
+ def coerce_cascaded_operands_to_temp(self, env): |
+ if self.cascade: |
+ #self.operand2 = self.operand2.coerce_to_temp(env) #CTT |
+ self.operand2 = self.operand2.coerce_to_simple(env) |
+ self.cascade.coerce_cascaded_operands_to_temp(env) |
+ |
+ def generate_evaluation_code(self, code, result, operand1, needs_evaluation=False): |
+ if self.type.is_pyobject: |
+ code.putln("if (__Pyx_PyObject_IsTrue(%s)) {" % result) |
+ code.put_decref(result, self.type) |
+ else: |
+ code.putln("if (%s) {" % result) |
+ if needs_evaluation: |
+ operand1.generate_evaluation_code(code) |
+ self.operand2.generate_evaluation_code(code) |
+ self.generate_operation_code(code, result, |
+ operand1, self.operator, self.operand2) |
+ if self.cascade: |
+ self.cascade.generate_evaluation_code( |
+ code, result, self.coerced_operand2 or self.operand2, |
+ needs_evaluation=self.coerced_operand2 is not None) |
+ if needs_evaluation: |
+ operand1.generate_disposal_code(code) |
+ operand1.free_temps(code) |
+ # Cascaded cmp result is always temp |
+ self.operand2.generate_disposal_code(code) |
+ self.operand2.free_temps(code) |
+ code.putln("}") |
+ |
+ def annotate(self, code): |
+ self.operand2.annotate(code) |
+ if self.cascade: |
+ self.cascade.annotate(code) |
+ |
+ |
+binop_node_classes = { |
+ "or": BoolBinopNode, |
+ "and": BoolBinopNode, |
+ "|": IntBinopNode, |
+ "^": IntBinopNode, |
+ "&": IntBinopNode, |
+ "<<": IntBinopNode, |
+ ">>": IntBinopNode, |
+ "+": AddNode, |
+ "-": SubNode, |
+ "*": MulNode, |
+ "/": DivNode, |
+ "//": DivNode, |
+ "%": ModNode, |
+ "**": PowNode |
+} |
+ |
+def binop_node(pos, operator, operand1, operand2, inplace=False): |
+ # Construct binop node of appropriate class for |
+ # given operator. |
+ return binop_node_classes[operator](pos, |
+ operator = operator, |
+ operand1 = operand1, |
+ operand2 = operand2, |
+ inplace = inplace) |
+ |
+#------------------------------------------------------------------- |
+# |
+# Coercion nodes |
+# |
+# Coercion nodes are special in that they are created during |
+# the analyse_types phase of parse tree processing. |
+# Their __init__ methods consequently incorporate some aspects |
+# of that phase. |
+# |
+#------------------------------------------------------------------- |
+ |
+class CoercionNode(ExprNode): |
+ # Abstract base class for coercion nodes. |
+ # |
+ # arg ExprNode node being coerced |
+ |
+ subexprs = ['arg'] |
+ constant_result = not_a_constant |
+ |
+ def __init__(self, arg): |
+ super(CoercionNode, self).__init__(arg.pos) |
+ self.arg = arg |
+ if debug_coercion: |
+ print("%s Coercing %s" % (self, self.arg)) |
+ |
+ def calculate_constant_result(self): |
+ # constant folding can break type coercion, so this is disabled |
+ pass |
+ |
+ def annotate(self, code): |
+ self.arg.annotate(code) |
+ if self.arg.type != self.type: |
+ file, line, col = self.pos |
+ code.annotate((file, line, col-1), AnnotationItem( |
+ style='coerce', tag='coerce', text='[%s] to [%s]' % (self.arg.type, self.type))) |
+ |
+class CoerceToMemViewSliceNode(CoercionNode): |
+ """ |
+ Coerce an object to a memoryview slice. This holds a new reference in |
+ a managed temp. |
+ """ |
+ |
+ def __init__(self, arg, dst_type, env): |
+ assert dst_type.is_memoryviewslice |
+ assert not arg.type.is_memoryviewslice |
+ CoercionNode.__init__(self, arg) |
+ self.type = dst_type |
+ self.is_temp = 1 |
+ self.env = env |
+ self.use_managed_ref = True |
+ self.arg = arg |
+ |
+ def generate_result_code(self, code): |
+ self.type.create_from_py_utility_code(self.env) |
+ code.putln("%s = %s(%s);" % (self.result(), |
+ self.type.from_py_function, |
+ self.arg.py_result())) |
+ |
+ error_cond = self.type.error_condition(self.result()) |
+ code.putln(code.error_goto_if(error_cond, self.pos)) |
+ |
+ |
+class CastNode(CoercionNode): |
+ # Wrap a node in a C type cast. |
+ |
+ def __init__(self, arg, new_type): |
+ CoercionNode.__init__(self, arg) |
+ self.type = new_type |
+ |
+ def may_be_none(self): |
+ return self.arg.may_be_none() |
+ |
+ def calculate_result_code(self): |
+ return self.arg.result_as(self.type) |
+ |
+ def generate_result_code(self, code): |
+ self.arg.generate_result_code(code) |
+ |
+ |
+class PyTypeTestNode(CoercionNode): |
+ # This node is used to check that a generic Python |
+ # object is an instance of a particular extension type. |
+ # This node borrows the result of its argument node. |
+ |
+ exact_builtin_type = True |
+ |
+ def __init__(self, arg, dst_type, env, notnone=False): |
+ # The arg is know to be a Python object, and |
+ # the dst_type is known to be an extension type. |
+ assert dst_type.is_extension_type or dst_type.is_builtin_type, "PyTypeTest on non extension type" |
+ CoercionNode.__init__(self, arg) |
+ self.type = dst_type |
+ self.result_ctype = arg.ctype() |
+ self.notnone = notnone |
+ |
+ nogil_check = Node.gil_error |
+ gil_message = "Python type test" |
+ |
+ def analyse_types(self, env): |
+ return self |
+ |
+ def may_be_none(self): |
+ if self.notnone: |
+ return False |
+ return self.arg.may_be_none() |
+ |
+ def is_simple(self): |
+ return self.arg.is_simple() |
+ |
+ def result_in_temp(self): |
+ return self.arg.result_in_temp() |
+ |
+ def is_ephemeral(self): |
+ return self.arg.is_ephemeral() |
+ |
+ def nonlocally_immutable(self): |
+ return self.arg.nonlocally_immutable() |
+ |
+ def calculate_constant_result(self): |
+ # FIXME |
+ pass |
+ |
+ def calculate_result_code(self): |
+ return self.arg.result() |
+ |
+ def generate_result_code(self, code): |
+ if self.type.typeobj_is_available(): |
+ if self.type.is_builtin_type: |
+ type_test = self.type.type_test_code( |
+ self.arg.py_result(), |
+ self.notnone, exact=self.exact_builtin_type) |
+ else: |
+ type_test = self.type.type_test_code( |
+ self.arg.py_result(), self.notnone) |
+ code.globalstate.use_utility_code( |
+ UtilityCode.load_cached("ExtTypeTest", "ObjectHandling.c")) |
+ code.putln("if (!(%s)) %s" % ( |
+ type_test, code.error_goto(self.pos))) |
+ else: |
+ error(self.pos, "Cannot test type of extern C class " |
+ "without type object name specification") |
+ |
+ def generate_post_assignment_code(self, code): |
+ self.arg.generate_post_assignment_code(code) |
+ |
+ def free_temps(self, code): |
+ self.arg.free_temps(code) |
+ |
+ |
+class NoneCheckNode(CoercionNode): |
+ # This node is used to check that a Python object is not None and |
+ # raises an appropriate exception (as specified by the creating |
+ # transform). |
+ |
+ is_nonecheck = True |
+ |
+ def __init__(self, arg, exception_type_cname, exception_message, |
+ exception_format_args): |
+ CoercionNode.__init__(self, arg) |
+ self.type = arg.type |
+ self.result_ctype = arg.ctype() |
+ self.exception_type_cname = exception_type_cname |
+ self.exception_message = exception_message |
+ self.exception_format_args = tuple(exception_format_args or ()) |
+ |
+ nogil_check = None # this node only guards an operation that would fail already |
+ |
+ def analyse_types(self, env): |
+ return self |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ def is_simple(self): |
+ return self.arg.is_simple() |
+ |
+ def result_in_temp(self): |
+ return self.arg.result_in_temp() |
+ |
+ def nonlocally_immutable(self): |
+ return self.arg.nonlocally_immutable() |
+ |
+ def calculate_result_code(self): |
+ return self.arg.result() |
+ |
+ def condition(self): |
+ if self.type.is_pyobject: |
+ return self.arg.py_result() |
+ elif self.type.is_memoryviewslice: |
+ return "((PyObject *) %s.memview)" % self.arg.result() |
+ else: |
+ raise Exception("unsupported type") |
+ |
+ def put_nonecheck(self, code): |
+ code.putln( |
+ "if (unlikely(%s == Py_None)) {" % self.condition()) |
+ |
+ if self.in_nogil_context: |
+ code.put_ensure_gil() |
+ |
+ escape = StringEncoding.escape_byte_string |
+ if self.exception_format_args: |
+ code.putln('PyErr_Format(%s, "%s", %s);' % ( |
+ self.exception_type_cname, |
+ StringEncoding.escape_byte_string( |
+ self.exception_message.encode('UTF-8')), |
+ ', '.join([ '"%s"' % escape(str(arg).encode('UTF-8')) |
+ for arg in self.exception_format_args ]))) |
+ else: |
+ code.putln('PyErr_SetString(%s, "%s");' % ( |
+ self.exception_type_cname, |
+ escape(self.exception_message.encode('UTF-8')))) |
+ |
+ if self.in_nogil_context: |
+ code.put_release_ensured_gil() |
+ |
+ code.putln(code.error_goto(self.pos)) |
+ code.putln("}") |
+ |
+ def generate_result_code(self, code): |
+ self.put_nonecheck(code) |
+ |
+ def generate_post_assignment_code(self, code): |
+ self.arg.generate_post_assignment_code(code) |
+ |
+ def free_temps(self, code): |
+ self.arg.free_temps(code) |
+ |
+ |
+class CoerceToPyTypeNode(CoercionNode): |
+ # This node is used to convert a C data type |
+ # to a Python object. |
+ |
+ type = py_object_type |
+ is_temp = 1 |
+ |
+ def __init__(self, arg, env, type=py_object_type): |
+ if not arg.type.create_to_py_utility_code(env): |
+ error(arg.pos, "Cannot convert '%s' to Python object" % arg.type) |
+ elif arg.type.is_complex: |
+ # special case: complex coercion is so complex that it |
+ # uses a macro ("__pyx_PyComplex_FromComplex()"), for |
+ # which the argument must be simple |
+ arg = arg.coerce_to_simple(env) |
+ CoercionNode.__init__(self, arg) |
+ if type is py_object_type: |
+ # be specific about some known types |
+ if arg.type.is_string or arg.type.is_cpp_string: |
+ self.type = default_str_type(env) |
+ elif arg.type.is_pyunicode_ptr or arg.type.is_unicode_char: |
+ self.type = unicode_type |
+ elif arg.type.is_complex: |
+ self.type = Builtin.complex_type |
+ elif arg.type.is_string or arg.type.is_cpp_string: |
+ if (type not in (bytes_type, bytearray_type) |
+ and not env.directives['c_string_encoding']): |
+ error(arg.pos, |
+ "default encoding required for conversion from '%s' to '%s'" % |
+ (arg.type, type)) |
+ self.type = type |
+ else: |
+ # FIXME: check that the target type and the resulting type are compatible |
+ pass |
+ |
+ if arg.type.is_memoryviewslice: |
+ # Register utility codes at this point |
+ arg.type.get_to_py_function(env, arg) |
+ |
+ self.env = env |
+ |
+ gil_message = "Converting to Python object" |
+ |
+ def may_be_none(self): |
+ # FIXME: is this always safe? |
+ return False |
+ |
+ def coerce_to_boolean(self, env): |
+ arg_type = self.arg.type |
+ if (arg_type == PyrexTypes.c_bint_type or |
+ (arg_type.is_pyobject and arg_type.name == 'bool')): |
+ return self.arg.coerce_to_temp(env) |
+ else: |
+ return CoerceToBooleanNode(self, env) |
+ |
+ def coerce_to_integer(self, env): |
+ # If not already some C integer type, coerce to longint. |
+ if self.arg.type.is_int: |
+ return self.arg |
+ else: |
+ return self.arg.coerce_to(PyrexTypes.c_long_type, env) |
+ |
+ def analyse_types(self, env): |
+ # The arg is always already analysed |
+ return self |
+ |
+ def generate_result_code(self, code): |
+ arg_type = self.arg.type |
+ if arg_type.is_memoryviewslice: |
+ funccall = arg_type.get_to_py_function(self.env, self.arg) |
+ else: |
+ func = arg_type.to_py_function |
+ if arg_type.is_string or arg_type.is_cpp_string: |
+ if self.type in (bytes_type, str_type, unicode_type): |
+ func = func.replace("Object", self.type.name.title()) |
+ elif self.type is bytearray_type: |
+ func = func.replace("Object", "ByteArray") |
+ funccall = "%s(%s)" % (func, self.arg.result()) |
+ |
+ code.putln('%s = %s; %s' % ( |
+ self.result(), |
+ funccall, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ |
+ code.put_gotref(self.py_result()) |
+ |
+ |
+class CoerceIntToBytesNode(CoerceToPyTypeNode): |
+ # This node is used to convert a C int type to a Python bytes |
+ # object. |
+ |
+ is_temp = 1 |
+ |
+ def __init__(self, arg, env): |
+ arg = arg.coerce_to_simple(env) |
+ CoercionNode.__init__(self, arg) |
+ self.type = Builtin.bytes_type |
+ |
+ def generate_result_code(self, code): |
+ arg = self.arg |
+ arg_result = arg.result() |
+ if arg.type not in (PyrexTypes.c_char_type, |
+ PyrexTypes.c_uchar_type, |
+ PyrexTypes.c_schar_type): |
+ if arg.type.signed: |
+ code.putln("if ((%s < 0) || (%s > 255)) {" % ( |
+ arg_result, arg_result)) |
+ else: |
+ code.putln("if (%s > 255) {" % arg_result) |
+ code.putln('PyErr_SetString(PyExc_OverflowError, ' |
+ '"value too large to pack into a byte"); %s' % ( |
+ code.error_goto(self.pos))) |
+ code.putln('}') |
+ temp = None |
+ if arg.type is not PyrexTypes.c_char_type: |
+ temp = code.funcstate.allocate_temp(PyrexTypes.c_char_type, manage_ref=False) |
+ code.putln("%s = (char)%s;" % (temp, arg_result)) |
+ arg_result = temp |
+ code.putln('%s = PyBytes_FromStringAndSize(&%s, 1); %s' % ( |
+ self.result(), |
+ arg_result, |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ if temp is not None: |
+ code.funcstate.release_temp(temp) |
+ code.put_gotref(self.py_result()) |
+ |
+ |
+class CoerceFromPyTypeNode(CoercionNode): |
+ # This node is used to convert a Python object |
+ # to a C data type. |
+ |
+ def __init__(self, result_type, arg, env): |
+ CoercionNode.__init__(self, arg) |
+ self.type = result_type |
+ self.is_temp = 1 |
+ if not result_type.create_from_py_utility_code(env): |
+ error(arg.pos, |
+ "Cannot convert Python object to '%s'" % result_type) |
+ if self.type.is_string or self.type.is_pyunicode_ptr: |
+ if self.arg.is_ephemeral(): |
+ error(arg.pos, |
+ "Obtaining '%s' from temporary Python value" % result_type) |
+ elif self.arg.is_name and self.arg.entry and self.arg.entry.is_pyglobal: |
+ warning(arg.pos, |
+ "Obtaining '%s' from externally modifiable global Python value" % result_type, |
+ level=1) |
+ |
+ def analyse_types(self, env): |
+ # The arg is always already analysed |
+ return self |
+ |
+ def generate_result_code(self, code): |
+ function = self.type.from_py_function |
+ operand = self.arg.py_result() |
+ rhs = "%s(%s)" % (function, operand) |
+ if self.type.is_enum: |
+ rhs = typecast(self.type, c_long_type, rhs) |
+ code.putln('%s = %s; %s' % ( |
+ self.result(), |
+ rhs, |
+ code.error_goto_if(self.type.error_condition(self.result()), self.pos))) |
+ if self.type.is_pyobject: |
+ code.put_gotref(self.py_result()) |
+ |
+ def nogil_check(self, env): |
+ error(self.pos, "Coercion from Python not allowed without the GIL") |
+ |
+ |
+class CoerceToBooleanNode(CoercionNode): |
+ # This node is used when a result needs to be used |
+ # in a boolean context. |
+ |
+ type = PyrexTypes.c_bint_type |
+ |
+ _special_builtins = { |
+ Builtin.list_type : 'PyList_GET_SIZE', |
+ Builtin.tuple_type : 'PyTuple_GET_SIZE', |
+ Builtin.bytes_type : 'PyBytes_GET_SIZE', |
+ Builtin.unicode_type : 'PyUnicode_GET_SIZE', |
+ } |
+ |
+ def __init__(self, arg, env): |
+ CoercionNode.__init__(self, arg) |
+ if arg.type.is_pyobject: |
+ self.is_temp = 1 |
+ |
+ def nogil_check(self, env): |
+ if self.arg.type.is_pyobject and self._special_builtins.get(self.arg.type) is None: |
+ self.gil_error() |
+ |
+ gil_message = "Truth-testing Python object" |
+ |
+ def check_const(self): |
+ if self.is_temp: |
+ self.not_const() |
+ return False |
+ return self.arg.check_const() |
+ |
+ def calculate_result_code(self): |
+ return "(%s != 0)" % self.arg.result() |
+ |
+ def generate_result_code(self, code): |
+ if not self.is_temp: |
+ return |
+ test_func = self._special_builtins.get(self.arg.type) |
+ if test_func is not None: |
+ code.putln("%s = (%s != Py_None) && (%s(%s) != 0);" % ( |
+ self.result(), |
+ self.arg.py_result(), |
+ test_func, |
+ self.arg.py_result())) |
+ else: |
+ code.putln( |
+ "%s = __Pyx_PyObject_IsTrue(%s); %s" % ( |
+ self.result(), |
+ self.arg.py_result(), |
+ code.error_goto_if_neg(self.result(), self.pos))) |
+ |
+class CoerceToComplexNode(CoercionNode): |
+ |
+ def __init__(self, arg, dst_type, env): |
+ if arg.type.is_complex: |
+ arg = arg.coerce_to_simple(env) |
+ self.type = dst_type |
+ CoercionNode.__init__(self, arg) |
+ dst_type.create_declaration_utility_code(env) |
+ |
+ def calculate_result_code(self): |
+ if self.arg.type.is_complex: |
+ real_part = "__Pyx_CREAL(%s)" % self.arg.result() |
+ imag_part = "__Pyx_CIMAG(%s)" % self.arg.result() |
+ else: |
+ real_part = self.arg.result() |
+ imag_part = "0" |
+ return "%s(%s, %s)" % ( |
+ self.type.from_parts, |
+ real_part, |
+ imag_part) |
+ |
+ def generate_result_code(self, code): |
+ pass |
+ |
+class CoerceToTempNode(CoercionNode): |
+ # This node is used to force the result of another node |
+ # to be stored in a temporary. It is only used if the |
+ # argument node's result is not already in a temporary. |
+ |
+ def __init__(self, arg, env): |
+ CoercionNode.__init__(self, arg) |
+ self.type = self.arg.type.as_argument_type() |
+ self.constant_result = self.arg.constant_result |
+ self.is_temp = 1 |
+ if self.type.is_pyobject: |
+ self.result_ctype = py_object_type |
+ |
+ gil_message = "Creating temporary Python reference" |
+ |
+ def analyse_types(self, env): |
+ # The arg is always already analysed |
+ return self |
+ |
+ def coerce_to_boolean(self, env): |
+ self.arg = self.arg.coerce_to_boolean(env) |
+ if self.arg.is_simple(): |
+ return self.arg |
+ self.type = self.arg.type |
+ self.result_ctype = self.type |
+ return self |
+ |
+ def generate_result_code(self, code): |
+ #self.arg.generate_evaluation_code(code) # Already done |
+ # by generic generate_subexpr_evaluation_code! |
+ code.putln("%s = %s;" % ( |
+ self.result(), self.arg.result_as(self.ctype()))) |
+ if self.use_managed_ref: |
+ if self.type.is_pyobject: |
+ code.put_incref(self.result(), self.ctype()) |
+ elif self.type.is_memoryviewslice: |
+ code.put_incref_memoryviewslice(self.result(), |
+ not self.in_nogil_context) |
+ |
+class ProxyNode(CoercionNode): |
+ """ |
+ A node that should not be replaced by transforms or other means, |
+ and hence can be useful to wrap the argument to a clone node |
+ |
+ MyNode -> ProxyNode -> ArgNode |
+ CloneNode -^ |
+ """ |
+ |
+ nogil_check = None |
+ |
+ def __init__(self, arg): |
+ super(ProxyNode, self).__init__(arg) |
+ self.constant_result = arg.constant_result |
+ self._proxy_type() |
+ |
+ def analyse_expressions(self, env): |
+ self.arg = self.arg.analyse_expressions(env) |
+ self._proxy_type() |
+ return self |
+ |
+ def _proxy_type(self): |
+ if hasattr(self.arg, 'type'): |
+ self.type = self.arg.type |
+ self.result_ctype = self.arg.result_ctype |
+ if hasattr(self.arg, 'entry'): |
+ self.entry = self.arg.entry |
+ |
+ def generate_result_code(self, code): |
+ self.arg.generate_result_code(code) |
+ |
+ def result(self): |
+ return self.arg.result() |
+ |
+ def is_simple(self): |
+ return self.arg.is_simple() |
+ |
+ def may_be_none(self): |
+ return self.arg.may_be_none() |
+ |
+ def generate_evaluation_code(self, code): |
+ self.arg.generate_evaluation_code(code) |
+ |
+ def generate_result_code(self, code): |
+ self.arg.generate_result_code(code) |
+ |
+ def generate_disposal_code(self, code): |
+ self.arg.generate_disposal_code(code) |
+ |
+ def free_temps(self, code): |
+ self.arg.free_temps(code) |
+ |
+class CloneNode(CoercionNode): |
+ # This node is employed when the result of another node needs |
+ # to be used multiple times. The argument node's result must |
+ # be in a temporary. This node "borrows" the result from the |
+ # argument node, and does not generate any evaluation or |
+ # disposal code for it. The original owner of the argument |
+ # node is responsible for doing those things. |
+ |
+ subexprs = [] # Arg is not considered a subexpr |
+ nogil_check = None |
+ |
+ def __init__(self, arg): |
+ CoercionNode.__init__(self, arg) |
+ self.constant_result = arg.constant_result |
+ if hasattr(arg, 'type'): |
+ self.type = arg.type |
+ self.result_ctype = arg.result_ctype |
+ if hasattr(arg, 'entry'): |
+ self.entry = arg.entry |
+ |
+ def result(self): |
+ return self.arg.result() |
+ |
+ def may_be_none(self): |
+ return self.arg.may_be_none() |
+ |
+ def type_dependencies(self, env): |
+ return self.arg.type_dependencies(env) |
+ |
+ def infer_type(self, env): |
+ return self.arg.infer_type(env) |
+ |
+ def analyse_types(self, env): |
+ self.type = self.arg.type |
+ self.result_ctype = self.arg.result_ctype |
+ self.is_temp = 1 |
+ if hasattr(self.arg, 'entry'): |
+ self.entry = self.arg.entry |
+ return self |
+ |
+ def is_simple(self): |
+ return True # result is always in a temp (or a name) |
+ |
+ def generate_evaluation_code(self, code): |
+ pass |
+ |
+ def generate_result_code(self, code): |
+ pass |
+ |
+ def generate_disposal_code(self, code): |
+ pass |
+ |
+ def free_temps(self, code): |
+ pass |
+ |
+ |
+class CMethodSelfCloneNode(CloneNode): |
+ # Special CloneNode for the self argument of builtin C methods |
+ # that accepts subtypes of the builtin type. This is safe only |
+ # for 'final' subtypes, as subtypes of the declared type may |
+ # override the C method. |
+ |
+ def coerce_to(self, dst_type, env): |
+ if dst_type.is_builtin_type and self.type.subtype_of(dst_type): |
+ return self |
+ return CloneNode.coerce_to(self, dst_type, env) |
+ |
+ |
+class ModuleRefNode(ExprNode): |
+ # Simple returns the module object |
+ |
+ type = py_object_type |
+ is_temp = False |
+ subexprs = [] |
+ |
+ def analyse_types(self, env): |
+ return self |
+ |
+ def may_be_none(self): |
+ return False |
+ |
+ def calculate_result_code(self): |
+ return Naming.module_cname |
+ |
+ def generate_result_code(self, code): |
+ pass |
+ |
+class DocstringRefNode(ExprNode): |
+ # Extracts the docstring of the body element |
+ |
+ subexprs = ['body'] |
+ type = py_object_type |
+ is_temp = True |
+ |
+ def __init__(self, pos, body): |
+ ExprNode.__init__(self, pos) |
+ assert body.type.is_pyobject |
+ self.body = body |
+ |
+ def analyse_types(self, env): |
+ return self |
+ |
+ def generate_result_code(self, code): |
+ code.putln('%s = __Pyx_GetAttr(%s, %s); %s' % ( |
+ self.result(), self.body.result(), |
+ code.intern_identifier(StringEncoding.EncodedString("__doc__")), |
+ code.error_goto_if_null(self.result(), self.pos))) |
+ code.put_gotref(self.result()) |
+ |
+ |
+ |
+#------------------------------------------------------------------------------------ |
+# |
+# Runtime support code |
+# |
+#------------------------------------------------------------------------------------ |
+ |
+pyerr_occurred_withgil_utility_code= UtilityCode( |
+proto = """ |
+static CYTHON_INLINE int __Pyx_ErrOccurredWithGIL(void); /* proto */ |
+""", |
+impl = """ |
+static CYTHON_INLINE int __Pyx_ErrOccurredWithGIL(void) { |
+ int err; |
+ #ifdef WITH_THREAD |
+ PyGILState_STATE _save = PyGILState_Ensure(); |
+ #endif |
+ err = !!PyErr_Occurred(); |
+ #ifdef WITH_THREAD |
+ PyGILState_Release(_save); |
+ #endif |
+ return err; |
+} |
+""" |
+) |
+ |
+#------------------------------------------------------------------------------------ |
+ |
+raise_unbound_local_error_utility_code = UtilityCode( |
+proto = """ |
+static CYTHON_INLINE void __Pyx_RaiseUnboundLocalError(const char *varname); |
+""", |
+impl = """ |
+static CYTHON_INLINE void __Pyx_RaiseUnboundLocalError(const char *varname) { |
+ PyErr_Format(PyExc_UnboundLocalError, "local variable '%s' referenced before assignment", varname); |
+} |
+""") |
+ |
+raise_closure_name_error_utility_code = UtilityCode( |
+proto = """ |
+static CYTHON_INLINE void __Pyx_RaiseClosureNameError(const char *varname); |
+""", |
+impl = """ |
+static CYTHON_INLINE void __Pyx_RaiseClosureNameError(const char *varname) { |
+ PyErr_Format(PyExc_NameError, "free variable '%s' referenced before assignment in enclosing scope", varname); |
+} |
+""") |
+ |
+# Don't inline the function, it should really never be called in production |
+raise_unbound_memoryview_utility_code_nogil = UtilityCode( |
+proto = """ |
+static void __Pyx_RaiseUnboundMemoryviewSliceNogil(const char *varname); |
+""", |
+impl = """ |
+static void __Pyx_RaiseUnboundMemoryviewSliceNogil(const char *varname) { |
+ #ifdef WITH_THREAD |
+ PyGILState_STATE gilstate = PyGILState_Ensure(); |
+ #endif |
+ __Pyx_RaiseUnboundLocalError(varname); |
+ #ifdef WITH_THREAD |
+ PyGILState_Release(gilstate); |
+ #endif |
+} |
+""", |
+requires = [raise_unbound_local_error_utility_code]) |
+ |
+#------------------------------------------------------------------------------------ |
+ |
+raise_too_many_values_to_unpack = UtilityCode.load_cached("RaiseTooManyValuesToUnpack", "ObjectHandling.c") |
+raise_need_more_values_to_unpack = UtilityCode.load_cached("RaiseNeedMoreValuesToUnpack", "ObjectHandling.c") |
+tuple_unpacking_error_code = UtilityCode.load_cached("UnpackTupleError", "ObjectHandling.c") |
+ |
+#------------------------------------------------------------------------------------ |
+ |
+int_pow_utility_code = UtilityCode( |
+proto=""" |
+static CYTHON_INLINE %(type)s %(func_name)s(%(type)s, %(type)s); /* proto */ |
+""", |
+impl=""" |
+static CYTHON_INLINE %(type)s %(func_name)s(%(type)s b, %(type)s e) { |
+ %(type)s t = b; |
+ switch (e) { |
+ case 3: |
+ t *= b; |
+ case 2: |
+ t *= b; |
+ case 1: |
+ return t; |
+ case 0: |
+ return 1; |
+ } |
+ #if %(signed)s |
+ if (unlikely(e<0)) return 0; |
+ #endif |
+ t = 1; |
+ while (likely(e)) { |
+ t *= (b * (e&1)) | ((~e)&1); /* 1 or b */ |
+ b *= b; |
+ e >>= 1; |
+ } |
+ return t; |
+} |
+""") |
+ |
+# ------------------------------ Division ------------------------------------ |
+ |
+div_int_utility_code = UtilityCode( |
+proto=""" |
+static CYTHON_INLINE %(type)s __Pyx_div_%(type_name)s(%(type)s, %(type)s); /* proto */ |
+""", |
+impl=""" |
+static CYTHON_INLINE %(type)s __Pyx_div_%(type_name)s(%(type)s a, %(type)s b) { |
+ %(type)s q = a / b; |
+ %(type)s r = a - q*b; |
+ q -= ((r != 0) & ((r ^ b) < 0)); |
+ return q; |
+} |
+""") |
+ |
+mod_int_utility_code = UtilityCode( |
+proto=""" |
+static CYTHON_INLINE %(type)s __Pyx_mod_%(type_name)s(%(type)s, %(type)s); /* proto */ |
+""", |
+impl=""" |
+static CYTHON_INLINE %(type)s __Pyx_mod_%(type_name)s(%(type)s a, %(type)s b) { |
+ %(type)s r = a %% b; |
+ r += ((r != 0) & ((r ^ b) < 0)) * b; |
+ return r; |
+} |
+""") |
+ |
+mod_float_utility_code = UtilityCode( |
+proto=""" |
+static CYTHON_INLINE %(type)s __Pyx_mod_%(type_name)s(%(type)s, %(type)s); /* proto */ |
+""", |
+impl=""" |
+static CYTHON_INLINE %(type)s __Pyx_mod_%(type_name)s(%(type)s a, %(type)s b) { |
+ %(type)s r = fmod%(math_h_modifier)s(a, b); |
+ r += ((r != 0) & ((r < 0) ^ (b < 0))) * b; |
+ return r; |
+} |
+""") |
+ |
+cdivision_warning_utility_code = UtilityCode( |
+proto=""" |
+static int __Pyx_cdivision_warning(const char *, int); /* proto */ |
+""", |
+impl=""" |
+static int __Pyx_cdivision_warning(const char *filename, int lineno) { |
+#if CYTHON_COMPILING_IN_PYPY |
+ filename++; // avoid compiler warnings |
+ lineno++; |
+ return PyErr_Warn(PyExc_RuntimeWarning, |
+ "division with oppositely signed operands, C and Python semantics differ"); |
+#else |
+ return PyErr_WarnExplicit(PyExc_RuntimeWarning, |
+ "division with oppositely signed operands, C and Python semantics differ", |
+ filename, |
+ lineno, |
+ __Pyx_MODULE_NAME, |
+ NULL); |
+#endif |
+} |
+""") |
+ |
+# from intobject.c |
+division_overflow_test_code = UtilityCode( |
+proto=""" |
+#define UNARY_NEG_WOULD_OVERFLOW(x) \ |
+ (((x) < 0) & ((unsigned long)(x) == 0-(unsigned long)(x))) |
+""") |