def __init__(self, variables: Set[VariableIdentifier], precursory: State = None): """Map each program variable to the type representing its value. :param variables: set of program variables """ lattices = defaultdict(lambda: TypeLattice) arguments = defaultdict( lambda: {'type_status': TypeLattice.Status.String}) arguments[BooleanLyraType()] = { 'type_status': TypeLattice.Status.Boolean } arguments[ListLyraType(BooleanLyraType())] = { 'type_status': TypeLattice.Status.Boolean } arguments[IntegerLyraType()] = { 'type_status': TypeLattice.Status.Integer } arguments[ListLyraType(IntegerLyraType())] = { 'type_status': TypeLattice.Status.Integer } arguments[FloatLyraType()] = {'type_status': TypeLattice.Status.Float} arguments[ListLyraType(FloatLyraType())] = { 'type_status': TypeLattice.Status.Float } super().__init__(variables, lattices, arguments) InputMixin.__init__(self, precursory)
def range_call_semantics(self, stmt: Call, state: State) -> State: result = set() if len(stmt.arguments) == 1: start = Literal(IntegerLyraType(), "0") stops = self.semantics(stmt.arguments[0], state).result step = Literal(IntegerLyraType(), "1") for stop in stops: range = Range(stmt.typ, start, stop, step) result.add(range) state.result = result return state elif len(stmt.arguments) == 2: starts = self.semantics(stmt.arguments[0], state).result stops = self.semantics(stmt.arguments[1], state).result step = Literal(IntegerLyraType(), "1") for start in starts: for stop in stops: range = Range(stmt.typ, start, stop, step) result.add(range) state.result = result return state elif len(stmt.arguments) == 3: starts = self.semantics(stmt.arguments[0], state).result stops = self.semantics(stmt.arguments[1], state).result steps = self.semantics(stmt.arguments[2], state).result for start in starts: for stop in stops: for step in steps: range = Range(stmt.typ, start, stop, step) result.add(range) state.result = result return state error = f"Call to {stmt.name} with unexpected number of arguments!" raise ValueError(error)
def _join(self, other: InputLattice) -> InputLattice: # TODO: """``Ɣ(self) ⋃ Ɣ(other) ⊆ Ɣ(self \/ other)``.""" def do(constraint1, constraint2): if isinstance(constraint1, tuple) and isinstance(constraint2, tuple): # the constraints are StarConstraints or BasicConstraints if not constraint1 and not constraint2: # the constraints are StarConstraints return () else: # the constraints are BasicConstraints pp1: ProgramPoint = constraint1[0] pp2: ProgramPoint = constraint2[0] pp: ProgramPoint = pp1 if pp1.line <= pp2.line else pp2 l1: Tuple[JSONMixin, ...] = constraint1[1] l2: Tuple[JSONMixin, ...] = constraint2[1] return pp, tuple(x.join(y) for x, y in zip(l1, l2)) else: # the constraints are InputLattices m1: Expression = constraint1.multiplier m2: Expression = constraint2.multiplier c1 = constraint1.constraints c2 = constraint2.constraints if isinstance(m1, Literal) and isinstance(m2, Literal): assert isinstance(m1.typ, IntegerLyraType) assert isinstance(m2.typ, IntegerLyraType) val = str(min(int(m1.val), int(m2.val))) m: Expression = Literal(IntegerLyraType(), val) else: m: Expression = m1 if m1 == m2 else one r: bool = m1 != m2 # are the multipliers different? c = [do(x, y) for x, y in zip(c1, c2)] # do the list of constraints have different length? r = r or len(c1) != len(c2) if r: # multipliers of lengths of list of constraints are different c.append(()) # add a star constraint return AssumptionState.InputStack.InputLattice(m, c) multiplier1 = self.multiplier multiplier2 = other.multiplier assert isinstance(multiplier1, Literal) and multiplier1.val == "1" assert isinstance(multiplier2, Literal) and multiplier2.val == "1" if len(self.constraints) == len(other.constraints): constraints = [do(x, y) for x, y in zip(self.constraints, other.constraints)] one = Literal(IntegerLyraType(), "1") return self._replace(AssumptionState.InputStack.InputLattice(one, constraints)) else: # the join is happening at a loop head assert abs(len(self.constraints) - len(other.constraints)) == 1 one = Literal(IntegerLyraType(), "1") if len(self.constraints) < len(other.constraints): tail = other.constraints[1:] constraints = [do(x, y) for x, y in zip(self.constraints, tail)] other.constraints[1:] = constraints join = AssumptionState.InputStack.InputLattice(one, other.constraints) self._replace(join) else: tail = self.constraints[1:] constraints = [do(x, y) for x, y in zip(tail, other.constraints)] self.constraints[1:] = constraints join = AssumptionState.InputStack.InputLattice(one, self.constraints) self._replace(join) return self
def visit_Call(self, node, types=None, typ=None): """Visitor function for a call. The attribute func stores the function being called (often a Name or Attribute object). The attribute args stores a list fo the arguments passed by position.""" pp = ProgramPoint(node.lineno, node.col_offset) if isinstance(node.func, ast.Name): name: str = node.func.id if name == 'bool' or name == 'int': arguments = [self.visit(arg, types, typ) for arg in node.args] return Call(pp, name, arguments, typ) if name == 'input': arguments = [self.visit(arg, types, StringLyraType()) for arg in node.args] return Call(pp, name, arguments, StringLyraType()) if name == 'range': arguments = [self.visit(arg, types, IntegerLyraType()) for arg in node.args] return Call(pp, name, arguments, ListLyraType(IntegerLyraType())) arguments = [self.visit(arg, types, None) for arg in node.args] return Call(pp, name, arguments, typ) elif isinstance(node.func, ast.Attribute): name: str = node.func.attr if name == 'append': arguments = [self.visit(node.func.value, types, None)] # target of the call args = [self.visit(arg, types, None) for arg in node.args] arguments.extend(args) assert isinstance(arguments[0].typ, ListLyraType) return Call(pp, name, arguments, arguments[0].typ) if name == 'items': arguments = [self.visit(node.func.value, types, None)] # target of the call args = [self.visit(arg, types, None) for arg in node.args] arguments.extend(args) assert isinstance(arguments[0].typ, DictLyraType) tuple_typ = TupleLyraType([arguments[0].typ.key_typ, arguments[0].typ.val_typ]) return Call(pp, name, arguments, SetLyraType(tuple_typ)) if name == 'keys': arguments = [self.visit(node.func.value, types, None)] # target of the call args = [self.visit(arg, types, None) for arg in node.args] arguments.extend(args) assert isinstance(arguments[0].typ, DictLyraType) return Call(pp, name, arguments, SetLyraType(arguments[0].typ.key_typ)) if name == 'split': # str.split([sep[, maxsplit]]) assert isinstance(typ, ListLyraType) # we expect type to be a ListLyraType arguments = [self.visit(node.func.value, types, typ.typ)] # target of the call args_typs = zip(node.args, [typ.typ, IntegerLyraType()]) args = [self.visit(arg, types, arg_typ) for arg, arg_typ in args_typs] arguments.extend(args) return Call(pp, name, arguments, typ) if name == 'values': arguments = [self.visit(node.func.value, types, None)] # target of the call args = [self.visit(arg, types, None) for arg in node.args] arguments.extend(args) assert isinstance(arguments[0].typ, DictLyraType) return Call(pp, name, arguments, SetLyraType(arguments[0].typ.val_typ)) arguments = [self.visit(node.func.value, types, None)] # target of the call arguments.extend([self.visit(arg, types, None) for arg in node.args]) return Call(pp, name, arguments, typ)
def visit_For(self, node, types=None, typ=None): header_node = Loop(self._id_gen.next) cfg = self._translate_body(node.body, types, typ) body_in_node = cfg.in_node body_out_node = cfg.out_node pp = ProgramPoint(node.target.lineno, node.target.col_offset) iteration = self.visit(node.iter, types, ListLyraType(IntegerLyraType())) if isinstance(iteration, Call) and iteration.name == "range": target_type = IntegerLyraType() else: error = f"The for loop iteration statment {node.iter} is not yet translatable to CFG!" raise NotImplementedError(error) target = self.visit(node.target, types, target_type) test = Call(pp, "in", [target, iteration], BooleanLyraType()) neg_test = Call(pp, "not", [test], BooleanLyraType()) cfg.add_node(header_node) cfg.in_node = header_node cfg.add_edge( Conditional(header_node, test, body_in_node, Edge.Kind.LOOP_IN)) cfg.add_edge(Conditional(header_node, neg_test, None)) if body_out_node: # if control flow can exit the body at all, add an unconditional LOOP_OUT edge cfg.add_edge( Unconditional(body_out_node, header_node, Edge.Kind.LOOP_OUT)) if node.orelse: # if there is else branch orelse_cfg = self._translate_body(node.orelse, types) if orelse_cfg.out_node: # if control flow can exit the else at all, add an unconditional DEFAULT edge orelse_cfg.add_edge( Unconditional(orelse_cfg.out_node, None, Edge.Kind.DEFAULT)) cfg.append(orelse_cfg) for special_edge, edge_type in cfg.special_edges: if edge_type == LooseControlFlowGraph.SpecialEdgeType.CONTINUE: cfg.add_edge( Unconditional(special_edge.source, header_node, Edge.Kind.LOOP_OUT)) elif edge_type == LooseControlFlowGraph.SpecialEdgeType.BREAK: cfg.add_edge( Unconditional(special_edge.source, None, Edge.Kind.LOOP_OUT)) cfg.special_edges.clear() return cfg
def __init__(self, multiplier: Expression = Literal( IntegerLyraType(), "1"), constraints: List[InputConstraint] = list()): super().__init__() self._multiplier = multiplier self._constraints = constraints
def do(constraint1, constraint2): if isinstance(constraint1, tuple) and isinstance(constraint2, tuple): # the constraints are StarConstraints or BasicConstraints if not constraint1 and not constraint2: # the constraints are StarConstraints return () else: # the constraints are BasicConstraints pp1: ProgramPoint = constraint1[0] pp2: ProgramPoint = constraint2[0] pp: ProgramPoint = pp1 if pp1.line <= pp2.line else pp2 l1: Tuple[JSONMixin, ...] = constraint1[1] l2: Tuple[JSONMixin, ...] = constraint2[1] return pp, tuple(x.join(y) for x, y in zip(l1, l2)) else: # the constraints are InputLattices m1: Expression = constraint1.multiplier m2: Expression = constraint2.multiplier c1 = constraint1.constraints c2 = constraint2.constraints if isinstance(m1, Literal) and isinstance(m2, Literal): assert isinstance(m1.typ, IntegerLyraType) assert isinstance(m2.typ, IntegerLyraType) val = str(min(int(m1.val), int(m2.val))) m: Expression = Literal(IntegerLyraType(), val) else: m: Expression = m1 if m1 == m2 else one r: bool = m1 != m2 # are the multipliers different? c = [do(x, y) for x, y in zip(c1, c2)] # do the list of constraints have different length? r = r or len(c1) != len(c2) if r: # multipliers of lengths of list of constraints are different c.append(()) # add a star constraint return AssumptionState.InputStack.InputLattice(m, c)
def visit_Range(self, expr: Range, state=None): literal1 = isinstance(expr.start, Literal) literal2 = isinstance(expr.stop, Literal) variable2 = isinstance(expr.stop, VariableIdentifier) literal3 = isinstance(expr.step, Literal) if literal1 and literal2 and literal3: start = int(expr.start.val) stop = int(expr.stop.val) step = int(expr.step.val) length = len(range(start, stop, step)) return state.lattices[IntegerLyraType()](lower=length, upper=length) elif literal1 and variable2 and literal3: start = int(expr.start.val) stop = state.store[expr.stop] return state.lattices[IntegerLyraType()](lower=start).meet(stop) raise ValueError(f"Unexpected expression during sequence length computation.")
def visit_Slicing(self, expr: Slicing, state=None): lattice: IntervalLattice = state.lattices[IntegerLyraType()] def is_one(stride): literal = isinstance(stride, Literal) return literal and lattice(stride).less_equal(lattice(lower=1, upper=1)) if isinstance(expr.lower, Literal): lower = IntervalLattice.from_literal(expr.lower) if not lower.less_equal(lattice(lower=0)): lower = lower.add(state.store[LengthIdentifier(expr.target)]) if not expr.upper: upper = deepcopy(state.store[LengthIdentifier(expr.target)]) if not expr.stride or is_one(expr.stride): # [l:_:(1)] length = lattice(lower=0).meet(upper.sub(lower)) if length.is_bottom(): return lattice(lower=0, upper=0) return length elif isinstance(expr.upper, Literal): upper = IntervalLattice.from_literal(expr.upper) if not upper.less_equal(lattice(lower=0)): upper = upper.add(state.store[LengthIdentifier(expr.target)]) if not expr.stride or is_one(expr.stride): # [l:u:(1)] length = lattice(lower=0).meet(upper.sub(lower)) if length.is_bottom(): return lattice(lower=0, upper=0) return length return deepcopy(state.store[LengthIdentifier(expr.target)]) # over-approximation
def __init__(self, variable: VariableIdentifier): """Sequence or collection length construction. :param variable: sequence or collection the length of which is being constructed """ name = "len({0.name})".format(variable) super().__init__(IntegerLyraType(), name)
def int_call_semantics(self, stmt: Call, state: State) -> State: """Semantics of a call to 'int'. :param stmt: call to 'int' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ return self._cast_call_semantics(stmt, state, IntegerLyraType())
def visit_Num(self, node, types=None, typ=None): pp = ProgramPoint(node.lineno, node.col_offset) if isinstance(node.n, int): expr = Literal(IntegerLyraType(), str(node.n)) return LiteralEvaluation(pp, expr) elif isinstance(node.n, float): expr = Literal(FloatLyraType(), str(node.n)) return LiteralEvaluation(pp, expr) raise NotImplementedError(f"Num {node.n.__class__.__name__} is not yet supported!")
def range_call_semantics(self, stmt: Call, state: State) -> State: arguments = [ self.semantics(arg, state).result.pop() for arg in stmt.arguments ] start = Literal(IntegerLyraType(), "0") step = Literal(IntegerLyraType(), "1") if len(arguments) == 1: end = arguments[0] elif len(arguments) in [2, 3]: start = arguments[0] end = arguments[1] if len(arguments) == 3: step = arguments[2] else: error = f"Semantics for range call with {len(arguments)} arguments is not implemented!" raise NotImplementedError(error) state.result = {Range(stmt.typ, start, end, step)} return state
def _join(self, other: InputLattice) -> InputLattice: def do(constraint1, constraint2): if isinstance(constraint1, tuple) and isinstance( constraint2, tuple): # the constraints are StarConstraints or BasicConstraints if not constraint1 and not constraint2: # the constraints are StarConstraints return () else: # the constraints are BasicConstraints pp1: ProgramPoint = constraint1[0] pp2: ProgramPoint = constraint2[0] pp: ProgramPoint = pp1 if pp1.line <= pp2.line else pp2 l1: Tuple[JSONMixin, ...] = constraint1[1] l2: Tuple[JSONMixin, ...] = constraint2[1] return pp, tuple(x.join(y) for x, y in zip(l1, l2)) else: # the constraints are InputLattices assert isinstance( constraint1, AssumptionState.InputStack.InputLattice) assert isinstance( constraint2, AssumptionState.InputStack.InputLattice) m1: Expression = constraint1.multiplier m2: Expression = constraint2.multiplier c1 = constraint1.constraints c2 = constraint2.constraints is_one1 = isinstance(m1, Literal) and m1.val == "1" is_one2 = isinstance(m2, Literal) and m2.val == "1" if is_one1 and is_one2: m = Literal(IntegerLyraType(), "1") c = [do(x, y) for x, y in zip(c1, c2)] if len(c1) != len( c2 ): # lengths of list of constraints are different c.append(()) # add a star constraint else: assert m1 == m2 m: Expression = m1 c = [do(x, y) for x, y in zip(c1, c2)] c.extend(c1[len(c2):]) c.extend(c2[len(c1):]) return AssumptionState.InputStack.InputLattice(m, c) multiplier1 = self.multiplier multiplier2 = other.multiplier assert isinstance(multiplier1, Literal) and multiplier1.val == "1" assert isinstance(multiplier2, Literal) and multiplier2.val == "1" constraints = [ do(x, y) for x, y in zip(self.constraints, other.constraints) ] one = Literal(IntegerLyraType(), "1") return self._replace( AssumptionState.InputStack.InputLattice(one, constraints))
def __init__(self, variables: Set[VariableIdentifier], precursory: State = None): """Map each program variable to the interval representing its value. :param variables: set of program variables """ lattices = defaultdict(lambda: IntervalLattice) super().__init__(variables, lattices, precursory=precursory) for v in self.variables: if isinstance(v.typ, SequenceLyraType): self.store[LengthIdentifier(v)] = lattices[IntegerLyraType()](lower=0)
def __init__(self, variables: Set[VariableIdentifier], precursory: InputMixin = None): """Map each program variable to the interval representing its value. :param variables: set of program variables """ lattices = defaultdict(lambda: RangeLattice) super(IntervalStateWithSummarization, self).__init__(variables, lattices) InputMixin.__init__(self, precursory) for v in self.variables: if isinstance(v.typ, SequenceLyraType): self.lengths[LengthIdentifier(v)] = lattices[IntegerLyraType()](lower=0)
def visit_Num(self, node, types=None, typ=None): """Visitor function for a number (integer, float, or complex). The n attribute stores the value, already converted to the relevant type.""" pp = ProgramPoint(node.lineno, node.col_offset) if isinstance(node.n, int): expr = Literal(IntegerLyraType(), str(node.n)) return LiteralEvaluation(pp, expr) elif isinstance(node.n, float): expr = Literal(FloatLyraType(), str(node.n)) return LiteralEvaluation(pp, expr) raise NotImplementedError(f"Num of type {node.n.__class__.__name__} is unsupported!")
def __init__(self, key_domain: Type[KeyWrapper], precursory: FularaState, scalar_vars: Set[VariableIdentifier] = None, map_vars: Set[VariableIdentifier] = None, k_pre_k_conv: Callable[[KeyWrapper], KeyWrapper] = lambda x: x): """Map each program variable/dictionary segment to its liveness status. :param key_domain: domain for abstraction of dictionary keys, ranges over the scalar variables and the special key variable v_k, should support backward assignments with _substitute :param precursory: Forward analysis (Fulara analysis) result above the current statement :param scalar_vars: list of scalar variables, whose liveness should be abstracted :param map_vars: list of map variables, whose usage should be abstracted :param k_pre_k_conv: Conversion function to convert from key domain elements of the precursory analysis to key domain elements of this analysis (if the domains differ) """ super().__init__(precursory) self._s_vars = scalar_vars or set() self._m_vars = map_vars or set() self._k_domain = key_domain self._scalar_liveness = StrongLivenessState(scalar_vars) arguments = {} for dv in map_vars: typ = dv.typ if isinstance(typ, DictLyraType): k_var = VariableIdentifier(typ.key_typ, k_name) elif isinstance(typ, ListLyraType): k_var = VariableIdentifier(IntegerLyraType(), k_name) else: raise TypeError("Map variables should be of type DictLyraType or ListLyraType") if typ not in arguments: arguments[typ] = {'key_domain': key_domain, 'value_domain': LivenessLattice, 'key_d_args': {'scalar_variables': scalar_vars, 'k_var': k_var}} lattices = defaultdict(lambda: FularaLattice) self._dict_liveness = Store(map_vars, lattices, arguments) # start with 'dead' for var in map_vars: self._dict_liveness.store[var].empty() # self._length_usage self._k_pre_k_conv = k_pre_k_conv
def __init__(self, key_domain: Type[Union[KeyWrapper, State]], scalar_vars: Set[VariableIdentifier] = None, map_vars: Set[VariableIdentifier] = None): """Map each program variable/dictionary segment to its usage status. :param key_domain: domain for abstraction of dictionary keys, ranges over the scalar variables and the special key variable v_k :param scalar_vars: list of scalar variables, whose usage should be abstracted :param map_vars: list of map variables, whose usage should be abstracted """ super().__init__() self._s_vars = scalar_vars or set() self._m_vars = map_vars or set() self._k_domain = key_domain self._scalar_usage = SimpleUsageStore(scalar_vars) arguments = {} for dv in map_vars: typ = dv.typ if isinstance(typ, DictLyraType): k_var = VariableIdentifier(typ.key_typ, k_name) elif isinstance(typ, ListLyraType): k_var = VariableIdentifier(IntegerLyraType(), k_name) else: raise TypeError( "Map variables should be of type DictLyraType or ListLyraType" ) if typ not in arguments: arguments[typ] = { 'key_domain': key_domain, 'value_domain': UsageLattice, 'key_d_args': { 'scalar_variables': scalar_vars, 'k_var': k_var } } lattices = defaultdict(lambda: FularaLattice) self._dict_usage = Store(map_vars, lattices, arguments) # start with 'not used' for var in map_vars: self._dict_usage.store[var].empty() self._loop_flag = False
def visit_BinaryArithmeticOperation(self, expr, evaluation=None, value=None, state=None): updated = super().visit_BinaryArithmeticOperation( expr, evaluation, value, state) if expr.operator == BinaryArithmeticOperation.Operator.Div: left = expr.right operator = BinaryComparisonOperation.Operator.NotEq right = Literal(IntegerLyraType(), "0") condition = BinaryComparisonOperation(BooleanLyraType(), left, operator, right) return updated.assume({condition}) return updated
def visit_Literal(self, expr: Literal, state=None): if isinstance(expr.typ, StringLyraType): return state.lattices[IntegerLyraType()](len(expr.val), len(expr.val)) raise ValueError(f"Unexpected expression during sequence length computation.")
def visit_BinaryComparisonOperation(self, expr: 'BinaryComparisonOperation', invert=False): left = expr.left operator = expr.operator.reverse_operator( ) if invert else expr.operator right = expr.right if operator == BinaryComparisonOperation.Operator.Eq: # left = right -> left - right <= 0 && right - left <= 0 zero = Literal(IntegerLyraType(), "0") minus = BinaryArithmeticOperation.Operator.Sub operator = BinaryComparisonOperation.Operator.LtE expr1 = BinaryArithmeticOperation(left.typ, left, minus, right) expr1 = BinaryComparisonOperation(expr.typ, expr1, operator, zero) expr2 = BinaryArithmeticOperation(right.typ, right, minus, left) expr2 = BinaryComparisonOperation(expr.typ, expr2, operator, zero) conjunction = BinaryBooleanOperation.Operator.And return BinaryBooleanOperation(expr.typ, expr1, conjunction, expr2) elif operator == BinaryComparisonOperation.Operator.NotEq: # left != right -> left - (right - 1) <= 0 || right - (left - 1) <= 0 zero = Literal(IntegerLyraType(), "0") one = Literal(IntegerLyraType(), "1") minus = BinaryArithmeticOperation.Operator.Sub operator = BinaryComparisonOperation.Operator.LtE expr1 = BinaryArithmeticOperation(right.typ, right, minus, one) expr1 = BinaryArithmeticOperation(left.typ, left, minus, expr1) expr1 = BinaryComparisonOperation(expr.typ, expr1, operator, zero) expr2 = BinaryArithmeticOperation(left.typ, left, minus, one) expr2 = BinaryArithmeticOperation(right.typ, right, minus, expr2) expr2 = BinaryComparisonOperation(expr.typ, expr2, operator, zero) disjunction = BinaryBooleanOperation.Operator.Or return BinaryBooleanOperation(expr.typ, expr1, disjunction, expr2) elif operator == BinaryComparisonOperation.Operator.Lt: # left < right -> left - (right - 1) <= 0 zero = Literal(IntegerLyraType(), "0") one = Literal(IntegerLyraType(), "1") minus = BinaryArithmeticOperation.Operator.Sub right = BinaryArithmeticOperation(right.typ, right, minus, one) left = BinaryArithmeticOperation(left.typ, left, minus, right) operator = BinaryComparisonOperation.Operator.LtE return BinaryComparisonOperation(expr.typ, left, operator, zero) elif operator == BinaryComparisonOperation.Operator.LtE: # left <= right -> left - right <= 0 zero = Literal(IntegerLyraType(), "0") minus = BinaryArithmeticOperation.Operator.Sub left = BinaryArithmeticOperation(left.typ, left, minus, right) operator = BinaryComparisonOperation.Operator.LtE return BinaryComparisonOperation(expr.typ, left, operator, zero) elif operator == BinaryComparisonOperation.Operator.Gt: # left > right -> right - (left - 1) <= 0 zero = Literal(IntegerLyraType(), "0") one = Literal(IntegerLyraType(), "1") minus = BinaryArithmeticOperation.Operator.Sub left = BinaryArithmeticOperation(left.typ, left, minus, one) right = BinaryArithmeticOperation(right.typ, right, minus, left) operator = BinaryComparisonOperation.Operator.LtE return BinaryComparisonOperation(expr.typ, right, operator, zero) elif operator == BinaryComparisonOperation.Operator.GtE: # left >= right -> right - left <= 0 zero = Literal(IntegerLyraType(), "0") minus = BinaryArithmeticOperation.Operator.Sub right = BinaryArithmeticOperation(right.typ, right, minus, left) operator = BinaryComparisonOperation.Operator.LtE return BinaryComparisonOperation(expr.typ, right, operator, zero) elif operator == BinaryComparisonOperation.Operator.In: return BinaryComparisonOperation(expr.typ, left, operator, right) elif operator == BinaryComparisonOperation.Operator.NotIn: return BinaryComparisonOperation(expr.typ, left, operator, right) raise ValueError(f"Boolean comparison operator {expr} is unsupported!")
def visit_SetDisplay(self, expr: SetDisplay, state=None): return state.lattices[IntegerLyraType()](len(expr.items), len(expr.items))
def visit_Subscription(self, expr: Subscription, state=None): return state.lattices[IntegerLyraType()](lower=1, upper=1)
def visit_Input(self, expr: Input, state=None): return state.lattices[IntegerLyraType()](lower=0)
def visit_Values(self, expr: Values, state=None): return state.lattices[IntegerLyraType()](lower=0)
def top(self): """The top lattice element is ``1 * [★]``.""" one = Literal(IntegerLyraType(), "1") return self._replace( AssumptionState.InputStack.InputLattice(one, [()]))