def visit_UnaryOp(self, node): """ Update range with given unary operation. >>> import gast as ast >>> from pythran import passmanager, backend >>> node = ast.parse(''' ... def foo(): ... a = 2 ... c = -a ... d = ~a ... f = +a ... e = not a''') >>> pm = passmanager.PassManager("test") >>> res = pm.gather(RangeValues, node) >>> res['f'] Interval(low=2, high=2) >>> res['c'] Interval(low=-2, high=-2) >>> res['d'] Interval(low=-3, high=-3) >>> res['e'] Interval(low=0, high=1) """ res = self.visit(node.operand) if isinstance(node.op, ast.Not): return Interval(0, 1) elif (isinstance(node.op, ast.Invert) and isinstance(res.high, int) and isinstance(res.low, int)): return Interval(~res.high, ~res.low) elif isinstance(node.op, ast.UAdd): return res elif isinstance(node.op, ast.USub): return Interval(-res.high, -res.low) else: return UNKNOWN_RANGE
def visit_Compare(self, node): """ Boolean are possible index. >>> import gast as ast >>> from pythran import passmanager, backend >>> node = ast.parse(''' ... def foo(): ... a = 2 or 3 ... b = 4 or 5 ... c = a < b ... d = b < 3 ... e = b == 4''') >>> pm = passmanager.PassManager("test") >>> res = pm.gather(RangeValues, node) >>> res['c'] Interval(low=1, high=1) >>> res['d'] Interval(low=0, high=0) >>> res['e'] Interval(low=0, high=1) """ if any( isinstance(op, (ast.In, ast.NotIn, ast.Is, ast.IsNot)) for op in node.ops): self.generic_visit(node) return self.add(node, Interval(0, 1)) curr = self.visit(node.left) res = [] for op, comparator in zip(node.ops, node.comparators): comparator = self.visit(comparator) fake = ast.Compare(ast.Name('x', ast.Load(), None), [op], [ast.Name('y', ast.Load(), None)]) fake = ast.Expression(fake) ast.fix_missing_locations(fake) expr = compile(ast.gast_to_ast(fake), '<range_values>', 'eval') res.append(eval(expr, {'x': curr, 'y': comparator})) if all(res): return self.add(node, Interval(1, 1)) elif any(r.low == r.high == 0 for r in res): return self.add(node, Interval(0, 0)) else: return self.add(node, Interval(0, 1))
def visit_Compare(_): """ Boolean are possible index. >>> import gast as ast >>> from pythran import passmanager, backend >>> node = ast.parse(''' ... def foo(): ... a = 2 or 3 ... b = 4 or 5 ... c = a < b''') >>> pm = passmanager.PassManager("test") >>> res = pm.gather(RangeValues, node) >>> res['c'] Interval(low=0, high=1) """ return Interval(0, 1)
def visit_BoolOp(self, node): """ Merge right and left operands ranges. TODO : We could exclude some operand with this range information... >>> import gast as ast >>> from pythran import passmanager, backend >>> node = ast.parse(''' ... def foo(): ... a = 2 ... c = 3 ... d = a or c''') >>> pm = passmanager.PassManager("test") >>> res = pm.gather(RangeValues, node) >>> res['d'] Interval(low=2, high=3) """ res = list(zip(*[self.visit(elt).bounds() for elt in node.values])) return self.add(node, Interval(min(res[0]), max(res[1])))
def visit_Num(self, node): """ Handle literals integers values. """ if isinstance(node.n, int): return self.add(node, Interval(node.n, node.n)) return UNKNOWN_RANGE
def visit_Constant(self, node): """ Handle literals integers values. """ if isinstance(node.value, (bool, int)): return self.add(node, Interval(node.value, node.value)) return UNKNOWN_RANGE
def bound_range(mapping, aliases, node, modified=None): """ Bound the idenifier in `mapping' with the expression in `node'. `aliases' is the result of aliasing analysis and `modified' is updated with the set of identifiers possibly `bounded' as the result of the call. Returns `modified' or a fresh set of modified identifiers. """ if modified is None: modified = set() if isinstance(node, ast.Name): # could be anything not just an integral pass elif isinstance(node, ast.UnaryOp): try: negated = negate(node.operand) bound_range(mapping, aliases, negated, modified) except UnsupportedExpression: pass elif isinstance(node, ast.BoolOp): if isinstance(node.op, ast.And): for value in node.values: bound_range(mapping, aliases, value, modified) elif isinstance(node.op, ast.Or): mappings = [mapping.copy() for _ in node.values] for value, mapping_cpy in zip(node.values, mappings): bound_range(mapping_cpy, aliases, value, modified) for k in modified: mapping[k] = reduce(lambda x, y: x.union(y[k]), mappings[1:], mappings[0][k]) elif isinstance(node, ast.Compare): left = node.left if isinstance(node.left, ast.Name): modified.add(node.left.id) for op, right in zip(node.ops, node.comparators): if isinstance(right, ast.Name): modified.add(right.id) if isinstance(left, ast.Name): left_interval = mapping[left.id] else: left_interval = mapping[left] if isinstance(right, ast.Name): right_interval = mapping[right.id] else: right_interval = mapping[right] l_l, l_h = left_interval.low, left_interval.high r_l, r_h = right_interval.low, right_interval.high r_i = l_i = None if isinstance(op, ast.Eq): low, high = max(l_l, r_l), min(l_h, r_h) if low <= high: l_i = r_i = Interval(max(l_l, r_l), min(l_h, r_h)) elif isinstance(op, ast.Lt): # l < r => l.low < r.high & l.high < r.high l_i = Interval(min(l_l, r_h - 1), min(l_h, r_h - 1)) # l < r => r.low < l.low & r.high < l.low r_i = Interval(max(r_l, l_l + 1), max(r_h, l_l + 1)) elif isinstance(op, ast.LtE): # l <= r => l.low <= r.high & l.high <= r.high l_i = Interval(min(l_l, r_h), min(l_h, r_h)) # l <= r => r.low <= l.low & r.high <= l.low r_i = Interval(max(r_l, l_l), max(r_h, l_l)) elif isinstance(op, ast.Gt): # l > r => l.low > r.low & l.high > r.low l_i = Interval(max(l_l, r_l + 1), max(l_h, r_l + 1)) # l > r => r.low > l.high & r.high > l.high r_i = Interval(min(r_l, l_h - 1), min(r_h, l_h - 1)) elif isinstance(op, ast.GtE): # l >= r => l.high >= r.low & l.low >= r.low l_i = Interval(max(l_l, r_l), max(l_h, r_l)) # l >= r => r.low > l.high & r.high >= l.high r_i = Interval(min(r_l, l_h), min(r_h, l_h)) elif isinstance(op, ast.In): if isinstance(right, (ast.List, ast.Tuple, ast.Set)): if right.elts: low = min(mapping[elt].low for elt in right.elts) high = max(mapping[elt].high for elt in right.elts) l_i = Interval(low, high) elif isinstance(right, ast.Call): for alias in aliases[right.func]: if not hasattr(alias, 'return_range_content'): l_i = None break rrc = alias.return_range_content([mapping[arg] for arg in right.args]) if l_i is None: l_i = rrc else: l_i = l_i.union(alias.return_range(right)) if l_i is not None and isinstance(left, ast.Name): mapping[left.id] = l_i if r_i is not None and isinstance(right, ast.Name): mapping[right.id] = r_i left = right
def visit_Num(node): """ Handle literals integers values. """ if isinstance(node.n, int): return Interval(node.n, node.n) else: return UNKNOWN_RANGE